diff options
Diffstat (limited to 'pyload')
| -rw-r--r-- | pyload/Core.py | 75 | ||||
| -rw-r--r-- | pyload/api/CoreApi.py | 21 | ||||
| -rw-r--r-- | pyload/config/default.py | 12 | ||||
| -rw-r--r-- | pyload/plugins/addons/ClickAndLoad.py | 13 | ||||
| -rw-r--r-- | pyload/remote/RemoteManager.py | 4 | ||||
| -rw-r--r-- | pyload/remote/WebSocketBackend.py | 16 | ||||
| -rw-r--r-- | pyload/remote/wsbackend/AbstractHandler.py | 13 | ||||
| -rw-r--r-- | pyload/remote/wsbackend/Server.py | 5 | ||||
| -rw-r--r-- | pyload/web/Gruntfile.js | 8 | ||||
| -rw-r--r-- | pyload/web/ServerThread.py | 10 | ||||
| -rw-r--r-- | pyload/web/app/index.html | 8 | ||||
| -rw-r--r-- | pyload/web/app/scripts/app.js | 7 | ||||
| -rw-r--r-- | pyload/web/app/scripts/views/headerView.js | 36 | ||||
| -rw-r--r-- | pyload/web/package.json | 4 | ||||
| -rw-r--r-- | pyload/web/pyload_app.py | 9 | ||||
| -rw-r--r-- | pyload/web/servers.py | 2 | ||||
| -rw-r--r-- | pyload/web/webinterface.py | 6 | 
17 files changed, 148 insertions, 101 deletions
| diff --git a/pyload/Core.py b/pyload/Core.py index 4893283a8..a13346567 100644 --- a/pyload/Core.py +++ b/pyload/Core.py @@ -35,9 +35,11 @@ from time import time, sleep  from traceback import print_exc  import locale +  locale.locale_alias = locale.windows_locale = {} #save ~100kb ram, no known sideeffects for now  import subprocess +  subprocess.__doc__ = None # the module with the largest doc we are using  import InitHomeDir @@ -60,8 +62,10 @@ from utils.fs import free_space, exists, makedirs, join, chmod  from codecs import getwriter  # test runner overwrites sys.stdout -if hasattr(sys.stdout, "encoding"): enc = get_console_encoding(sys.stdout.encoding) -else: enc = "utf8" +if hasattr(sys.stdout, "encoding"): +    enc = get_console_encoding(sys.stdout.encoding) +else: +    enc = "utf8"  sys._stdout = sys.stdout  sys.stdout = getwriter(enc)(sys.stdout, errors="replace") @@ -100,9 +104,9 @@ class Core(object):          if len(argv) > 1:              try:                  options, args = getopt(argv[1:], 'vchdusqp:', -                    ["version", "clear", "clean", "help", "debug", "user", -                     "setup", "configdir=", "changedir", "daemon", -                     "quit", "status", "no-remote","pidfile="]) +                                       ["version", "clear", "clean", "help", "debug", "user", +                                        "setup", "configdir=", "changedir", "daemon", +                                        "quit", "status", "no-remote", "pidfile="])                  for option, argument in options:                      if option in ("-v", "--version"): @@ -299,14 +303,16 @@ class Core(object):              exit() -        try: signal.signal(signal.SIGQUIT, self.quit) -        except: pass +        try: +            signal.signal(signal.SIGQUIT, self.quit) +        except: +            pass          self.config = ConfigParser()          gettext.setpaths([join(os.sep, "usr", "share", "pyload", "locale"), None])          translation = gettext.translation("pyLoad", self.path("locale"), -                                          languages=[self.config['general']['language'],"en"],fallback=True) +                                          languages=[self.config['general']['language'], "en"], fallback=True)          translation.install(True)          # load again so translations are propagated @@ -448,19 +454,19 @@ class Core(object):          self.eventManager.dispatchEvent("core:ready")          #test api -#        from pyload.common.APIExerciser import startApiExerciser -#        startApiExerciser(self, 3) +        #        from pyload.common.APIExerciser import startApiExerciser +        #        startApiExerciser(self, 3)          #some memory stats -#        from guppy import hpy -#        hp=hpy() -#        print hp.heap() -#        import objgraph -#        objgraph.show_most_common_types(limit=30) -#        import memdebug -#        memdebug.start(8002) -#        from meliae import scanner -#        scanner.dump_all_objects(self.path('objs.json')) +        #        from guppy import hpy +        #        hp=hpy() +        #        print hp.heap() +        #        import objgraph +        #        objgraph.show_most_common_types(limit=30) +        #        import memdebug +        #        memdebug.start(8002) +        #        from meliae import scanner +        #        scanner.dump_all_objects(self.path('objs.json'))          locals().clear() @@ -491,9 +497,8 @@ class Core(object):          self.db.manager = self.files #ugly?      def init_webserver(self): -        if self.config['webinterface']['activated']: -            self.webserver = WebServer(self) -            self.webserver.start() +        self.webserver = WebServer(self) +        self.webserver.start()      def init_logger(self, level):          console = logging.StreamHandler(sys.stdout) @@ -526,18 +531,18 @@ class Core(object):              if self.config['log']['color_theme'] == "full":                  cfmt = "%(asctime)s %(log_color)s%(bold)s%(white)s %(levelname)-8s %(reset)s %(message)s"                  clr = { -                    'DEBUG':    'bg_cyan', -                    'INFO':     'bg_green', -                    'WARNING':  'bg_yellow', -                    'ERROR':    'bg_red', +                    'DEBUG': 'bg_cyan', +                    'INFO': 'bg_green', +                    'WARNING': 'bg_yellow', +                    'ERROR': 'bg_red',                      'CRITICAL': 'bg_purple',                  }              else: #light theme                  cfmt = "%(log_color)s%(asctime)s %(levelname)-8s  %(message)s"                  clr = { -                    'DEBUG':    'cyan', -                    'WARNING':  'yellow', -                    'ERROR':    'red', +                    'DEBUG': 'cyan', +                    'WARNING': 'yellow', +                    'ERROR': 'red',                      'CRITICAL': 'purple',                  } @@ -578,10 +583,10 @@ class Core(object):          self.shutdown()          chdir(owd)          # close some open fds -        for i in range(3,50): +        for i in range(3, 50):              try:                  close(i) -            except : +            except:                  pass          execl(executable, executable, *sys.argv) @@ -591,9 +596,9 @@ class Core(object):          self.log.info(_("shutting down..."))          self.eventManager.dispatchEvent("coreShutdown")          try: -            if self.config['webinterface']['activated'] and hasattr(self, "webserver"): +            if hasattr(self, "webserver"):                  pass # TODO: quit webserver? -#                self.webserver.quit() +                #                self.webserver.quit()              for thread in self.threadManager.threads:                  thread.put("quit") @@ -616,12 +621,14 @@ class Core(object):          """ stop and open an ipython shell inplace"""          if self.debug:              from IPython import embed +              sys.stdout = sys._stdout              embed()      def breakpoint(self):          if self.debug:              from IPython.core.debugger import Pdb +              sys.stdout = sys._stdout              if not self.pdb: self.pdb = Pdb()              self.pdb.set_trace() @@ -678,7 +685,7 @@ def main():      #from module.lib.rename_process import renameProcess      #renameProcess('pyLoadCore')      if "--daemon" in sys.argv: -            deamon() +        deamon()      else:          pyload_core = Core()          try: diff --git a/pyload/api/CoreApi.py b/pyload/api/CoreApi.py index ebb194134..187286b48 100644 --- a/pyload/api/CoreApi.py +++ b/pyload/api/CoreApi.py @@ -2,7 +2,7 @@  # -*- coding: utf-8 -*-  from pyload.Api import Api, RequirePerm, Permission, ServerStatus, Interaction -from pyload.utils.fs import join, free_space +from pyload.utils.fs import join, free_space, exists  from pyload.utils import compare_time  from ApiComponent import ApiComponent @@ -15,11 +15,26 @@ class CoreApi(ApiComponent):          """pyLoad Core version """          return self.core.version +    def isWSSecure(self): +        # needs to use TLS when either requested or webUI is also using encryption +        if not self.core.config['ssl']['activated'] or self.core.config['webUI']['https']: +            return False + +        if not exists(self.core.config['ssl']['cert']) or not exists(self.core.config['ssl']['key']): +            self.core.log.warning(_('SSL key or certificate not found')) +            return False + +        return True +      @RequirePerm(Permission.All)      def getWSAddress(self):          """Gets and address for the websocket based on configuration""" -        # TODO SSL (wss) -        return "ws://%%s:%d" % self.core.config['remote']['port'] +        if self.isWSSecure(): +            ws = "wss" +        else: +            ws = "ws" + +        return "%s://%%s:%d" % (ws, self.core.config['webUI']['wsPort'])      @RequirePerm(Permission.All)      def getServerStatus(self): diff --git a/pyload/config/default.py b/pyload/config/default.py index 5a02ef758..26152a09a 100644 --- a/pyload/config/default.py +++ b/pyload/config/default.py @@ -11,12 +11,6 @@ def make_config(config):      # Check if gettext is installed      _ = lambda x: x -    config.addConfigSection("remote", _("Remote"), _("Description"), _("Long description"), -                            [ -                                ("port", "int", _("Port"), 7227), -                                ("listenaddr", "ip", _("Address"), "0.0.0.0"), -                            ]) -      config.addConfigSection("log", _("Log"), _("Description"), _("Long description"),                              [                                  ("log_size", "int", _("Size in kb"), 100), @@ -58,16 +52,18 @@ def make_config(config):                                  ("key", "file", _("SSL Key"), "ssl.key"),                              ]) -    config.addConfigSection("webinterface", _("Webinterface"), _("Description"), _("Long description"), +    config.addConfigSection("webUI", _("webUI"), _("Description"), _("Long description"),                              [                                  ("template", "str", _("Template"), "default"), -                                ("activated", "bool", _("Activated"), True),                                  ("prefix", "str", _("Path Prefix"), ""), +                                ("external", "bool", _("Served external"), False),                                  ("server", "auto;threaded;fallback;fastcgi", _("Server"), "auto"),                                  ("force_server", "str", _("Favor specific server"), ""),                                  ("host", "ip", _("IP"), "0.0.0.0"),                                  ("https", "bool", _("Use HTTPS"), False),                                  ("port", "int", _("Port"), 8001), +                                ("wsHost", "ip", _("IP"), "0.0.0.0"), +                                ("wsPort", "int", _("Port"), 7227),                                  ("develop", "bool", _("Development mode"), False),                              ]) diff --git a/pyload/plugins/addons/ClickAndLoad.py b/pyload/plugins/addons/ClickAndLoad.py index 0d9538543..be360c30c 100644 --- a/pyload/plugins/addons/ClickAndLoad.py +++ b/pyload/plugins/addons/ClickAndLoad.py @@ -33,14 +33,13 @@ class ClickAndLoad(Addon):      __author_mail__ = ("RaNaN@pyload.de", "mkaay@mkaay.de")      def coreReady(self): -        self.port = int(self.config['webinterface']['port']) -        if self.config['webinterface']['activated']: -            if self.getConfig("extern"): -                ip = "0.0.0.0" -            else: -                ip = "127.0.0.1" +        self.port = int(self.config['webUI']['port']) +        if self.getConfig("extern"): +            ip = "0.0.0.0" +        else: +            ip = "127.0.0.1" -            thread.start_new_thread(proxy, (self, ip, self.port, 9666)) +        thread.start_new_thread(proxy, (self, ip, self.port, 9666))  def proxy(self, *settings): diff --git a/pyload/remote/RemoteManager.py b/pyload/remote/RemoteManager.py index 7aeeb8a7a..b66b8b10a 100644 --- a/pyload/remote/RemoteManager.py +++ b/pyload/remote/RemoteManager.py @@ -67,8 +67,8 @@ class RemoteManager():      def startBackends(self): -        host = self.core.config["remote"]["listenaddr"] -        port = self.core.config["remote"]["port"] +        host = self.core.config["webUI"]["wsHost"] +        port = self.core.config["webUI"]["wsPort"]          for b in self.available:              klass = getattr(__import__("pyload.remote.%s" % b, globals(), locals(), [b], -1), b) diff --git a/pyload/remote/WebSocketBackend.py b/pyload/remote/WebSocketBackend.py index d29470067..7238af679 100644 --- a/pyload/remote/WebSocketBackend.py +++ b/pyload/remote/WebSocketBackend.py @@ -21,12 +21,15 @@ import logging  from RemoteManager import BackendBase  from mod_pywebsocket import util + +  def get_class_logger(o=None):      return logging.getLogger('log')  # Monkey patch for our logger  util.get_class_logger = get_class_logger +  class WebSocketBackend(BackendBase):      def setup(self, host, port): @@ -42,8 +45,19 @@ class WebSocketBackend(BackendBase):          options.dispatcher.addHandler(ApiHandler.PATH, ApiHandler(self.core.api))          options.dispatcher.addHandler(AsyncHandler.PATH, AsyncHandler(self.core.api)) -        self.server = WebSocketServer(options) +        # tls is needed when requested or webUI is also on tls +        if self.core.api.isWSSecure(): +            from wsbackend.Server import import_ssl +            if import_ssl(): +                options.use_tls = True +                options.certificate = self.core.config['ssl']['cert'] +                options.ca_certificate = options.certificate +                options.private_key = self.core.config['ssl']['key'] +                self.core.log.info(_('Using secure WebSocket')) +            else: +                self.core.log.warning(_('SSL could not be imported')) +        self.server = WebSocketServer(options)      def serve(self):          self.server.serve_forever() diff --git a/pyload/remote/wsbackend/AbstractHandler.py b/pyload/remote/wsbackend/AbstractHandler.py index 842d87473..e69ff2573 100644 --- a/pyload/remote/wsbackend/AbstractHandler.py +++ b/pyload/remote/wsbackend/AbstractHandler.py @@ -46,13 +46,12 @@ class AbstractHandler:          req.api = None #when api is set client is logged in          # allow login via session when webinterface is active -        if self.core.config['webinterface']['activated']: -            cookie = req.headers_in.getheader('Cookie') -            s = self.load_session(cookie) -            if s: -                uid = s.get('uid', None) -                req.api = self.api.withUserContext(uid) -                self.log.debug("WS authenticated user with cookie: %d" % uid) +        cookie = req.headers_in.getheader('Cookie') +        s = self.load_session(cookie) +        if s: +            uid = s.get('uid', None) +            req.api = self.api.withUserContext(uid) +            self.log.debug("WS authenticated user with cookie: %d" % uid)          self.on_open(req) diff --git a/pyload/remote/wsbackend/Server.py b/pyload/remote/wsbackend/Server.py index 02da44f04..9a6649ca9 100644 --- a/pyload/remote/wsbackend/Server.py +++ b/pyload/remote/wsbackend/Server.py @@ -67,6 +67,7 @@ _MAX_MEMORIZED_LINES = 1024  def import_ssl():      global _HAS_SSL, _HAS_OPEN_SSL +    global ssl, OpenSSL      try:          import ssl          _HAS_SSL = True @@ -77,6 +78,8 @@ def import_ssl():          except ImportError:              pass +    return _HAS_OPEN_SSL or _HAS_SSL +  class _StandaloneConnection(object):      """Mimic mod_python mp_conn.""" @@ -648,6 +651,8 @@ class DefaultOptions:      private_key = ''      certificate = ''      ca_certificate = '' +    tls_client_ca = '' +    tls_client_auth = False      dispatcher = None      request_queue_size = _DEFAULT_REQUEST_QUEUE_SIZE      use_basic_auth = False diff --git a/pyload/web/Gruntfile.js b/pyload/web/Gruntfile.js index 0a97e7360..4711ca66d 100644 --- a/pyload/web/Gruntfile.js +++ b/pyload/web/Gruntfile.js @@ -21,7 +21,8 @@ module.exports = function(grunt) {      var yeomanConfig = {          app: 'app',          dist: 'dist', -        banner: '/* Copyright(c) 2008-2013 pyLoad Team */\n' +        banner: '/* Copyright(c) 2008-2013 pyLoad Team */\n', +        protocol: 'http'      };      grunt.initConfig({ @@ -50,7 +51,8 @@ module.exports = function(grunt) {              options: {                  port: 9000,                  // change this to '0.0.0.0' to access the server from outside -                hostname: 'localhost' +                hostname: 'localhost', +                protocol: '<%= yeoman.protocol %>'              },              livereload: {                  options: { @@ -85,7 +87,7 @@ module.exports = function(grunt) {          },          open: { // Opens the webbrowser              server: { -                path: 'http://localhost:<%= connect.options.port %>' +                path: '<%= yeoman.protocol %>://localhost:<%= connect.options.port %>'              }          },          clean: { diff --git a/pyload/web/ServerThread.py b/pyload/web/ServerThread.py index 809c6c800..a2e375f1f 100644 --- a/pyload/web/ServerThread.py +++ b/pyload/web/ServerThread.py @@ -26,14 +26,14 @@ class WebServer(threading.Thread):          else:              raise Exception("No config context provided") -        self.server = config['webinterface']['server'] -        self.https = config['webinterface']['https'] +        self.server = config['webUI']['server'] +        self.https = config['webUI']['https']          self.cert = config["ssl"]["cert"]          self.key = config["ssl"]["key"] -        self.host = config['webinterface']['host'] -        self.port = config['webinterface']['port'] +        self.host = config['webUI']['host'] +        self.port = config['webUI']['port']          self.debug = config['general']['debug_mode'] -        self.force_server = config['webinterface']['force_server'] +        self.force_server = config['webUI']['force_server']          self.error = None          self.setDaemon(True) diff --git a/pyload/web/app/index.html b/pyload/web/app/index.html index 98e1bf233..08366f665 100644 --- a/pyload/web/app/index.html +++ b/pyload/web/app/index.html @@ -22,7 +22,7 @@          // Use value set by templateEngine or default val          function configValue(string, defaultValue) { -            if (string.indexOf('{{') > -1) +            if (string.indexOf('{{') > -1 && string !== 'None' && string !== '')                  return defaultValue;              return string;          } @@ -38,10 +38,10 @@          window.hostProtocol = window.location.protocol +  '//';          window.hostAddress = window.location.hostname;          window.hostPort = configValue('{{web}}', '8001'); -        // TODO -        window.pathPrefix = '/'; +        window.external = configValue('{{external}}', 'true').toLowerCase(); +        window.pathPrefix = configValue('{{prefix}}', '');          window.wsAddress = configValue('{{ws}}', 'ws://%s:7227'); -        window.setup = configValue('{{setup}}', 'false'); +        window.setup = configValue('{{setup}}', 'false').toLowerCase();          require(['config'], function(Config) {              require(['default'], function(App) { diff --git a/pyload/web/app/scripts/app.js b/pyload/web/app/scripts/app.js index af5c50b14..68a20666d 100644 --- a/pyload/web/app/scripts/app.js +++ b/pyload/web/app/scripts/app.js @@ -41,8 +41,11 @@ define([      };      App.apiUrl = function(path) { -        var url = window.hostProtocol + window.hostAddress + ':' + window.hostPort + window.pathPrefix + path; -        return url; +        var prefix = window.pathPrefix; +        if (window.external !== 'false') +            prefix = window.hostProtocol + window.hostAddress + ':' + window.hostPort + prefix; + +        return prefix + '/' + path;      };      // Add Global Helper functions diff --git a/pyload/web/app/scripts/views/headerView.js b/pyload/web/app/scripts/views/headerView.js index 49298d450..7d892bf01 100644 --- a/pyload/web/app/scripts/views/headerView.js +++ b/pyload/web/app/scripts/views/headerView.js @@ -62,21 +62,27 @@ define(['jquery', 'underscore', 'backbone', 'app', 'models/ServerStatus', 'colle                  });                  // TODO: button to start stop refresh -                var ws = App.openWebSocket('/async'); -                ws.onopen = function() { -                    ws.send(JSON.stringify('start')); -                }; -                // TODO compare with polling -                ws.onmessage = _.bind(this.onData, this); -                ws.onerror = function(error) { -                    console.log(error); -                    alert('WebSocket error' + error); -                }; -                ws.onclose = function() { -                    alert('WebSocket was closed'); -                }; - -                this.ws = ws; +                // TODO: catch ws errors / switch into ws less mode +                try { +                    var ws = App.openWebSocket('/async'); +                    ws.onopen = function() { +                        ws.send(JSON.stringify('start')); +                    }; +                    // TODO compare with polling +                    ws.onmessage = _.bind(this.onData, this); +                    ws.onerror = function(error) { +                        console.log(error); +                        alert('WebSocket error ' + error); +                    }; +                    ws.onclose = function() { +                        alert('WebSocket was closed'); +                    }; + +                    this.ws = ws; + +                } catch (e) { +                    alert('Could not open WebSocket: ' + e); +                }              },              gotoDashboard: function() { diff --git a/pyload/web/package.json b/pyload/web/package.json index 5de79a814..4ea7ce484 100644 --- a/pyload/web/package.json +++ b/pyload/web/package.json @@ -14,7 +14,7 @@          "grunt-contrib-jshint": "~0.4.1",          "grunt-contrib-less": "~0.5.2",          "grunt-contrib-cssmin": "~0.6.0", -        "grunt-contrib-connect": "~0.2.0", +        "grunt-contrib-connect": "~0.5.0",          "grunt-contrib-clean": "~0.4.0",          "grunt-contrib-htmlmin": "~0.1.3",          "grunt-contrib-requirejs": "~0.4.1", @@ -29,7 +29,7 @@          "grunt-concurrent": "~0.1.0",          "matchdep": "~0.1.1",          "rjs-build-analysis": "0.0.3", -        "connect-livereload": "~0.2.0" +        "connect-livereload": "~0.3.0"      },      "engines": {          "node": ">=0.8.0" diff --git a/pyload/web/pyload_app.py b/pyload/web/pyload_app.py index 50d9b9731..1a54c4a93 100644 --- a/pyload/web/pyload_app.py +++ b/pyload/web/pyload_app.py @@ -21,7 +21,7 @@ from os.path import join, exists  from bottle import route, static_file, response, request, redirect, template -from webinterface import PYLOAD, PROJECT_DIR, SETUP, APP_PATH, UNAVAILALBE +from webinterface import PYLOAD, PROJECT_DIR, SETUP, APP_PATH, UNAVAILALBE, PREFIX  from utils import login_required, add_json_header, select_language @@ -71,16 +71,17 @@ def index():      # set variable depending on setup mode      setup = 'false' if SETUP is None else 'true'      ws = PYLOAD.getWSAddress() if PYLOAD else False +    external = PYLOAD.getConfigValue('webUI', 'external') if PYLOAD else None      web = None      if PYLOAD: -        web = PYLOAD.getConfigValue('webinterface', 'port') +        web = PYLOAD.getConfigValue('webUI', 'port')      elif SETUP: -        web = SETUP.config['webinterface']['port'] +        web = SETUP.config['webUI']['port']      # Render variables into the html page      if resp.status_code == 200:          content = resp.body.read() -        resp.body = template(content, ws=ws, web=web, setup=setup) +        resp.body = template(content, ws=ws, web=web, setup=setup, external=external, prefix=PREFIX)          resp.content_length = len(resp.body)      return resp diff --git a/pyload/web/servers.py b/pyload/web/servers.py index a3c51e36b..2755cbaff 100644 --- a/pyload/web/servers.py +++ b/pyload/web/servers.py @@ -157,6 +157,6 @@ class FlupFCGIServer(ServerAdapter):          flup.server.fcgi.WSGIServer(handler, **self.options).run()  # Order is important and gives every server precedence over others! -all_server = [BjoernServer, TornadoServer, EventletServer, CherryPyWSGI] +all_server = [TornadoServer, EventletServer, CherryPyWSGI]  # Some are deactivated because they have some flaws  ##all_server = [FapwsServer, MeinheldServer, BjoernServer, TornadoServer, EventletServer, CherryPyWSGI]
\ No newline at end of file diff --git a/pyload/web/webinterface.py b/pyload/web/webinterface.py index 21c5f4a03..f732a933d 100644 --- a/pyload/web/webinterface.py +++ b/pyload/web/webinterface.py @@ -46,9 +46,9 @@ else:  from pyload.utils.JsEngine import JsEngine  JS = JsEngine() -TEMPLATE = config.get('webinterface', 'template') +TEMPLATE = config.get('webUI', 'template')  DL_ROOT = config.get('general', 'download_folder') -PREFIX = config.get('webinterface', 'prefix') +PREFIX = config.get('webUI', 'prefix')  if PREFIX:      PREFIX = PREFIX.rstrip("/") @@ -59,7 +59,7 @@ APP_PATH = "app"  UNAVAILALBE = True  # webUI build is available -if exists(join(PROJECT_DIR, "app", "components")) and exists(join(PROJECT_DIR, ".tmp")) and config.get('webinterface', 'develop'): +if exists(join(PROJECT_DIR, "app", "components")) and exists(join(PROJECT_DIR, ".tmp")) and config.get('webUI', 'develop'):      UNAVAILALBE = False  elif exists(join(PROJECT_DIR, "dist", "index.html")):      APP_PATH = "dist" | 
