summaryrefslogtreecommitdiffstats
path: root/pyload
diff options
context:
space:
mode:
Diffstat (limited to 'pyload')
-rw-r--r--pyload/Core.py75
-rw-r--r--pyload/api/CoreApi.py21
-rw-r--r--pyload/config/default.py12
-rw-r--r--pyload/plugins/addons/ClickAndLoad.py13
-rw-r--r--pyload/remote/RemoteManager.py4
-rw-r--r--pyload/remote/WebSocketBackend.py16
-rw-r--r--pyload/remote/wsbackend/AbstractHandler.py13
-rw-r--r--pyload/remote/wsbackend/Server.py5
-rw-r--r--pyload/web/Gruntfile.js8
-rw-r--r--pyload/web/ServerThread.py10
-rw-r--r--pyload/web/app/index.html8
-rw-r--r--pyload/web/app/scripts/app.js7
-rw-r--r--pyload/web/app/scripts/views/headerView.js36
-rw-r--r--pyload/web/package.json4
-rw-r--r--pyload/web/pyload_app.py9
-rw-r--r--pyload/web/servers.py2
-rw-r--r--pyload/web/webinterface.py6
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"