From 083f82fde197b2bf6ef118a6935c8001afcd48ec Mon Sep 17 00:00:00 2001 From: Mike Young Date: Sun, 22 Dec 2019 13:35:58 -0500 Subject: [PATCH 1/8] Linked Djangos settings to pyShelfs config --- config.json | 3 ++- install.py | 8 ++++++++ pyproject.toml | 2 +- src/backend/lib/config.py | 4 ++++ src/frontend/settings.py | 24 +++++++++++++++++++++--- 5 files changed, 36 insertions(+), 5 deletions(-) create mode 100644 install.py diff --git a/config.json b/config.json index 77f2b02..7940a68 100644 --- a/config.json +++ b/config.json @@ -7,5 +7,6 @@ "DATABASE": "pyshelf", "USER": "pyshelf", "PASSWORD": "pyshelf", - "BOOKSHELF": "data/shelf.json" + "BOOKSHELF": "data/shelf.json", + "ALLOWED_HOSTS": "*" } diff --git a/install.py b/install.py new file mode 100644 index 0000000..04bdeb3 --- /dev/null +++ b/install.py @@ -0,0 +1,8 @@ +#!/usr/bin/python + +import pathlib +import sys + +PRG_PATH = pathlib.Path.cwd() +LIB_PATH = pathlib.Path.joinpath(PRG_PATH, "src", "backend", "lib") +sys.path.insert(0, PRG_PATH) diff --git a/pyproject.toml b/pyproject.toml index 38d47ab..2db2a64 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,4 +7,4 @@ use_parentheses = true # NOTE: the known_third_party setting is managed by # seed-isort-config and should not be modified directly. # Any changes made to this setting will be overwritten. -known_third_party = ["bs4", "django", "interface", "psycopg2", "requests"] +known_third_party = ["backend", "bs4", "django", "interface", "psycopg2", "requests"] diff --git a/src/backend/lib/config.py b/src/backend/lib/config.py index 1591db6..a78054d 100755 --- a/src/backend/lib/config.py +++ b/src/backend/lib/config.py @@ -36,6 +36,10 @@ class Config: self.root = root self.auto_scan = True + self.allowed_hosts = _data["ALLOWED_HOSTS"] + self.db_user = _data["USER"] + self.db_pass = _data["PASSWORD"] + def open_file(self, _cp): """ Opens config.json and reads in configuration options diff --git a/src/frontend/settings.py b/src/frontend/settings.py index 397f10e..c669448 100755 --- a/src/frontend/settings.py +++ b/src/frontend/settings.py @@ -11,7 +11,17 @@ https://docs.djangoproject.com/en/2.2/ref/settings/ """ import os +import sys +from pathlib import Path +from backend.lib.config import Config + +CUR_DIR = Path.cwd() +PRG_DIR = CUR_DIR.parts[0:-1] +PRG_DIR = Path(*PRG_DIR) + + +CONFIG = Config(PRG_DIR) # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) @@ -23,9 +33,9 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) SECRET_KEY = "@(9b9jslgg41u1u=mr)-2*-n2x0vef0zsy39*z@sz18&tvow18" # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = False +DEBUG = True -ALLOWED_HOSTS = ["*"] +ALLOWED_HOSTS = CONFIG.allowed_hosts # Application definition @@ -82,7 +92,7 @@ WSGI_APPLICATION = "frontend.wsgi.application" # Database # https://docs.djangoproject.com/en/2.2/ref/settings/#databases - +""" DATABASES = { "default": { "ENGINE": "django.db.backends.postgresql", @@ -92,7 +102,15 @@ DATABASES = { # "NAME": os.path.join(BASE_DIR, "db.sqlite3"), } } +""" +DATABASES = { + "default": { + "ENGINE": "django.db.backends.postgresql", + "NAME": CONFIG.user, + "PASSWORD": CONFIG.password, + } +} # Password validation # https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators From 5b81252f4f22b8da667bfff683f699bc004febb7 Mon Sep 17 00:00:00 2001 From: Mike Young Date: Mon, 23 Dec 2019 17:06:51 -0500 Subject: [PATCH 2/8] start on installer script --- __init__.py | 4 +-- install | 28 ++++++++++++++++ install.py | 8 ----- pyproject.toml | 2 +- src/backend/lib/config.py | 3 -- src/backend/lib/display.py | 68 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 99 insertions(+), 14 deletions(-) create mode 100755 install delete mode 100644 install.py create mode 100644 src/backend/lib/display.py diff --git a/__init__.py b/__init__.py index 9171045..21575c2 100644 --- a/__init__.py +++ b/__init__.py @@ -1,4 +1,4 @@ import sys -sys.path.insert(1, "app/") -sys.path.insert(2, "frontend/") +# sys.path.insert(1, "app/") +# sys.path.insert(2, "frontend/") diff --git a/install b/install new file mode 100755 index 0000000..f469338 --- /dev/null +++ b/install @@ -0,0 +1,28 @@ +#!/usr/bin/python +import json +import pathlib + +from src.backend.lib.config import Config +from src.backend.lib.display import TerminalDisplay + +PRG_PATH = pathlib.Path.cwd() +LIB_PATH = pathlib.Path.joinpath(PRG_PATH, "src", "backend", "lib") +# sys.path.insert(0, PRG_PATH) + +TerminalDisplay().installer() + + +class Configuration: + def __init__(self): + _cp = pathlib.Path("config.json") + _data = self.open_file() + + def open_file(self): + with open(str(self._cp), "r") as read_file: + data = json.load(read_file) + return data + + def write_file(self, data): + with open(str(self._cp), "rw") as write_file: + json.dumps(write_file) + return True diff --git a/install.py b/install.py deleted file mode 100644 index 04bdeb3..0000000 --- a/install.py +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/python - -import pathlib -import sys - -PRG_PATH = pathlib.Path.cwd() -LIB_PATH = pathlib.Path.joinpath(PRG_PATH, "src", "backend", "lib") -sys.path.insert(0, PRG_PATH) diff --git a/pyproject.toml b/pyproject.toml index 2db2a64..ad507ce 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,4 +7,4 @@ use_parentheses = true # NOTE: the known_third_party setting is managed by # seed-isort-config and should not be modified directly. # Any changes made to this setting will be overwritten. -known_third_party = ["backend", "bs4", "django", "interface", "psycopg2", "requests"] +known_third_party = ["backend", "bs4", "django", "interface", "prompt_toolkit", "psycopg2", "requests"] diff --git a/src/backend/lib/config.py b/src/backend/lib/config.py index a78054d..c36c0c3 100755 --- a/src/backend/lib/config.py +++ b/src/backend/lib/config.py @@ -22,8 +22,6 @@ class Config: self.VERSION = _data["VERSION"] self.TITLE = self.TITLE + " ver " + self.VERSION self.book_shelf = _data["BOOKSHELF"] - # self.catalogue_db = "data/catalogue.db" - # self.catalogue_db = str(root) + "/" + _data["DATABASE"] self.catalogue_db = _data["DATABASE"] self.user = _data["USER"] self.password = _data["PASSWORD"] @@ -31,7 +29,6 @@ class Config: self.db_port = _data["DB_PORT"] self.file_array = [ self.book_shelf, - # self.catalogue_db, ] self.root = root self.auto_scan = True diff --git a/src/backend/lib/display.py b/src/backend/lib/display.py new file mode 100644 index 0000000..70d6381 --- /dev/null +++ b/src/backend/lib/display.py @@ -0,0 +1,68 @@ +from __future__ import unicode_literals + +import os +import sys +from pprint import pprint + +from prompt_toolkit import prompt as prm + + +class TerminalDisplay: + def __init__(self): + self.term = True + self.w, self.y = os.get_terminal_size()[0], os.get_terminal_size()[1] + + def screen(self): + return self.term + + def installer(self): + questions = [ + { + "message": "Input the absolute path to your ebooks\nEg. /home/{user}/Books > ", + "options": "", + "name": "BOOKPATH", + "answer": "", + }, + { + "message": "Input your PostgreSQL server ip\nEg. localhost > ", + "options": "localhost", + "name": "DB_HOST", + "answer": "", + }, + { + "message": "Input your PostgreSQL server port\nEg. 5432 > ", + "options": "5432", + "name": "DB_PORT", + "answer": "", + }, + { + "message": "Input your PostgreSQL user name\nEg. pyshelf > ", + "options": "pyshelf", + "name": "USER", + "answer": "", + }, + { + "message": "Input your PostgreSQL password\neg. pyshelf > ", + "options": "pyshelf", + "name": "PASSWORD", + "answer": "", + }, + ] + answers = self.prompt(questions) + pprint(answers) + + @staticmethod + def clear(): + os.system("cls" if os.name == "nt" else "clear") + + def prompt(self, questions): + self.clear() + answers = questions + for answer in answers: + self.h_rule() + answer["answer"] = prm(answer["message"]) + self.clear() + return answers + + def h_rule(self): + print("\u2501" * self.w) From ee96fbb85dbab0ff11e2c63d6f95cfac091b956b Mon Sep 17 00:00:00 2001 From: Raelon Masters Date: Wed, 25 Dec 2019 22:37:20 -0500 Subject: [PATCH 3/8] Added basics comments --- __init__.py | 4 ---- install | 23 +++++++++++++++-------- requirements.txt | 1 + 3 files changed, 16 insertions(+), 12 deletions(-) delete mode 100644 __init__.py diff --git a/__init__.py b/__init__.py deleted file mode 100644 index 21575c2..0000000 --- a/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -import sys - -# sys.path.insert(1, "app/") -# sys.path.insert(2, "frontend/") diff --git a/install b/install index f469338..0af5a89 100755 --- a/install +++ b/install @@ -1,21 +1,22 @@ -#!/usr/bin/python +#!/usr/bin/python3.8 import json import pathlib - -from src.backend.lib.config import Config +from pprint import pprint +# from src.backend.lib.config import Config # Use ptShelfs configuration class or stay independant for portability? from src.backend.lib.display import TerminalDisplay -PRG_PATH = pathlib.Path.cwd() -LIB_PATH = pathlib.Path.joinpath(PRG_PATH, "src", "backend", "lib") +# PRG_PATH = pathlib.Path.cwd() +# LIB_PATH = pathlib.Path.joinpath(PRG_PATH, "src", "backend", "lib") # sys.path.insert(0, PRG_PATH) -TerminalDisplay().installer() +# Call for the ui and installer questions. +install_answers = TerminalDisplay().installer() class Configuration: def __init__(self): - _cp = pathlib.Path("config.json") - _data = self.open_file() + self._cp = pathlib.Path("config.json") + self._data = self.open_file() def open_file(self): with open(str(self._cp), "r") as read_file: @@ -26,3 +27,9 @@ class Configuration: with open(str(self._cp), "rw") as write_file: json.dumps(write_file) return True + + +config = Configuration().open_file() +# Print a comparison between config.json and user inputs. +pprint(install_answers) +pprint(config) diff --git a/requirements.txt b/requirements.txt index b2f93d8..968cc3d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,3 +15,4 @@ toml uwsgi django-debug-toolbar psycopg2-binary +prompt_toolkit From 39f59aa68923d74308ffb3575ed0c87d3f1b01c2 Mon Sep 17 00:00:00 2001 From: Raelon Masters Date: Fri, 27 Dec 2019 10:28:49 -0500 Subject: [PATCH 4/8] Added config backup and write operations --- config.json | 13 +----------- install | 43 +++++++++++++++++++++++--------------- src/backend/lib/display.py | 36 +++++++++++++++++-------------- 3 files changed, 47 insertions(+), 45 deletions(-) diff --git a/config.json b/config.json index 7940a68..1c42e28 100644 --- a/config.json +++ b/config.json @@ -1,12 +1 @@ -{ - "TITLE": "pyShelf E-Book Server", - "VERSION": "0.3.0", - "BOOKPATH": "books/", - "DB_HOST": "localhost", - "DB_PORT": "5432", - "DATABASE": "pyshelf", - "USER": "pyshelf", - "PASSWORD": "pyshelf", - "BOOKSHELF": "data/shelf.json", - "ALLOWED_HOSTS": "*" -} +{"TITLE": "pyShelf E-Book Server", "VERSION": "0.3.0", "BOOKPATH": "~/Books", "DB_HOST": "localhost", "DB_PORT": "5432", "DATABASE": "pyshelf", "USER": "pyshelf", "PASSWORD": "pyshelf", "BOOKSHELF": "data/shelf.json", "ALLOWED_HOSTS": "*"} \ No newline at end of file diff --git a/install b/install index 0af5a89..12a9605 100755 --- a/install +++ b/install @@ -1,35 +1,44 @@ #!/usr/bin/python3.8 import json import pathlib -from pprint import pprint -# from src.backend.lib.config import Config # Use ptShelfs configuration class or stay independant for portability? +import os from src.backend.lib.display import TerminalDisplay -# PRG_PATH = pathlib.Path.cwd() -# LIB_PATH = pathlib.Path.joinpath(PRG_PATH, "src", "backend", "lib") -# sys.path.insert(0, PRG_PATH) - -# Call for the ui and installer questions. -install_answers = TerminalDisplay().installer() - class Configuration: def __init__(self): self._cp = pathlib.Path("config.json") self._data = self.open_file() + self.system = os.sys.environ def open_file(self): - with open(str(self._cp), "r") as read_file: - data = json.load(read_file) - return data + """ + Try to open and then backup the configuration file. + Fail and return false if initial configuration is not found. + # TODO: More specific error handling + """ + try: + with open(str(self._cp), "r") as read_file: + data = json.load(read_file) + with open('config.backup.json', 'w') as backup_file: + json.dump(data, backup_file) + return data + except Exception as e: + print(e) + return False def write_file(self, data): - with open(str(self._cp), "rw") as write_file: - json.dumps(write_file) + """ + Write the provided data to the new configuration file + """ + with open(str(self._cp), "w") as write_file: + json.dump(data, write_file) return True config = Configuration().open_file() -# Print a comparison between config.json and user inputs. -pprint(install_answers) -pprint(config) +install_answers = TerminalDisplay().installer() +for key in install_answers: + config[key["name"]] = key["answer"] +Configuration().write_file(config) + diff --git a/src/backend/lib/display.py b/src/backend/lib/display.py index 70d6381..966beea 100644 --- a/src/backend/lib/display.py +++ b/src/backend/lib/display.py @@ -1,9 +1,5 @@ from __future__ import unicode_literals - import os -import sys -from pprint import pprint - from prompt_toolkit import prompt as prm @@ -11,6 +7,8 @@ class TerminalDisplay: def __init__(self): self.term = True self.w, self.y = os.get_terminal_size()[0], os.get_terminal_size()[1] + self.home = os.environ["HOME"] + self.user = os.environ["USER"] def screen(self): return self.term @@ -18,38 +16,42 @@ class TerminalDisplay: def installer(self): questions = [ { - "message": "Input the absolute path to your ebooks\nEg. /home/{user}/Books > ", + "message": "Input the absolute path to your ebooks\nEnter for default \"~/Books\" > ", "options": "", "name": "BOOKPATH", - "answer": "", + "answer": None, + "default": self.home+"/Books" }, { - "message": "Input your PostgreSQL server ip\nEg. localhost > ", + "message": "Input your PostgreSQL server ip\nEnter for default \"localhost\" > ", "options": "localhost", "name": "DB_HOST", - "answer": "", + "answer": None, + "default": "localhost" }, { - "message": "Input your PostgreSQL server port\nEg. 5432 > ", + "message": "Input your PostgreSQL server port\nEnter for default \"5432\" > ", "options": "5432", "name": "DB_PORT", - "answer": "", + "answer": None, + "default": "5432" }, { - "message": "Input your PostgreSQL user name\nEg. pyshelf > ", + "message": "Input your PostgreSQL user name\nEnter for default \"pyshelf\" > ", "options": "pyshelf", "name": "USER", - "answer": "", + "answer": None, + "default": "pyshelf" }, { - "message": "Input your PostgreSQL password\neg. pyshelf > ", + "message": "Input your PostgreSQL password\nEnter for default \"pyshelf\" > ", "options": "pyshelf", "name": "PASSWORD", - "answer": "", + "answer": None, + "default": "pyshelf" }, ] - answers = self.prompt(questions) - pprint(answers) + return self.prompt(questions) @staticmethod def clear(): @@ -61,6 +63,8 @@ class TerminalDisplay: for answer in answers: self.h_rule() answer["answer"] = prm(answer["message"]) + if answer["answer"] == "": + answer["answer"] = answer["default"] self.clear() return answers From 1e33d5f5330714fece5c9adacbff4052788b1a20 Mon Sep 17 00:00:00 2001 From: Mike Young Date: Fri, 27 Dec 2019 12:33:15 -0500 Subject: [PATCH 5/8] working on process checking --- config.json | 2 +- install | 30 ++++++++++++++++++++++++++---- requirements.txt | 1 + 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/config.json b/config.json index 1c42e28..c26edc5 100644 --- a/config.json +++ b/config.json @@ -1 +1 @@ -{"TITLE": "pyShelf E-Book Server", "VERSION": "0.3.0", "BOOKPATH": "~/Books", "DB_HOST": "localhost", "DB_PORT": "5432", "DATABASE": "pyshelf", "USER": "pyshelf", "PASSWORD": "pyshelf", "BOOKSHELF": "data/shelf.json", "ALLOWED_HOSTS": "*"} \ No newline at end of file +{"TITLE": "pyShelf E-Book Server", "VERSION": "0.3.0", "BOOKPATH": "/home/raelon/Books", "DB_HOST": "localhost", "DB_PORT": "5432", "DATABASE": "pyshelf", "USER": "pyshelf", "PASSWORD": "pyshelf", "BOOKSHELF": "data/shelf.json", "ALLOWED_HOSTS": "*"} diff --git a/install b/install index 12a9605..7164867 100755 --- a/install +++ b/install @@ -1,7 +1,9 @@ #!/usr/bin/python3.8 import json import pathlib -import os +import platform + +import psutil from src.backend.lib.display import TerminalDisplay @@ -9,18 +11,18 @@ class Configuration: def __init__(self): self._cp = pathlib.Path("config.json") self._data = self.open_file() - self.system = os.sys.environ + self.system = platform.system() def open_file(self): """ Try to open and then backup the configuration file. Fail and return false if initial configuration is not found. - # TODO: More specific error handling + # TODO: More specific error handling """ try: with open(str(self._cp), "r") as read_file: data = json.load(read_file) - with open('config.backup.json', 'w') as backup_file: + with open("config.backup.json", "w") as backup_file: json.dump(data, backup_file) return data except Exception as e: @@ -36,9 +38,29 @@ class Configuration: return True +class RequiredServices: + @staticmethod + def check_ps(service_list): + breakpoint() + ps = psutil + passed = [] + for p in ps.process_iter(attrs=["name", "exe", "cmdline"]): + for s in service_list: + if s == p.name: + passed.append(s) + return + if len(passed) == len(service_list): + return True + else: + return failed + + config = Configuration().open_file() install_answers = TerminalDisplay().installer() for key in install_answers: config[key["name"]] = key["answer"] Configuration().write_file(config) +# Start checking for our list of required services +service_list = ["postgresql", "nginx", "httpd"] +req = RequiredServices.check_ps(service_list) diff --git a/requirements.txt b/requirements.txt index 968cc3d..87a1a7f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -16,3 +16,4 @@ uwsgi django-debug-toolbar psycopg2-binary prompt_toolkit +psutil From f23a05a9db57f41de7abd6891be712b1fcb79101 Mon Sep 17 00:00:00 2001 From: Mike Young Date: Sat, 28 Dec 2019 17:38:56 -0500 Subject: [PATCH 6/8] Started handling nginx config --- install | 205 ++++++++++++++++++++++++++++++++++--- pyshelf_nginx.conf | 18 ++-- src/backend/lib/display.py | 43 ++++++-- 3 files changed, 232 insertions(+), 34 deletions(-) diff --git a/install b/install index 7164867..1fdbf92 100755 --- a/install +++ b/install @@ -1,7 +1,11 @@ #!/usr/bin/python3.8 import json +import os import pathlib import platform +import subprocess as sp +import sys +from shutil import copyfile import psutil from src.backend.lib.display import TerminalDisplay @@ -39,28 +43,201 @@ class Configuration: class RequiredServices: + def check_ps(self, service_list): + """ + Check service_list against running processes + by calling self.process_list, remove found + services from the list and return + """ + # Get the matched processes + _matches = self.process_list().intersection(set(service_list)) + for r in _matches: + service_list.remove(r) + return service_list + @staticmethod - def check_ps(service_list): - breakpoint() - ps = psutil - passed = [] - for p in ps.process_iter(attrs=["name", "exe", "cmdline"]): - for s in service_list: - if s == p.name: - passed.append(s) - return - if len(passed) == len(service_list): - return True + def process_list(): + """ + Iterate running processes returning the name of each + make it a set and return + """ + _processes = [] + for p in psutil.process_iter(): + _processes.append(p.name()) + return set(_processes) + + @staticmethod + def web_server_found(service_list): + # Determine whether or not both possible webservers are missing + _c = 0 + for r in service_list: + if r == "nginx" or r == "httpd": + _c = _c + 1 + if _c > 1: + return False # Return false if neither are found else: - return failed + return True # Return true if one is found + + @staticmethod + def db_server_found(service_list): + _c = 0 + for r in service_list: + if r == "postgres": + _c = _c + 1 + if _c > 0: + return False + else: + return True + + +class SystemInstaller: + def __init__(self): + self.bin = self.get() + self.site_dirs = ["/etc/nginx/sites-available", "/etc/nginx/sites-enabled"] + self.nginx_conf = "pyshelf_nginx.conf" + + def get(self): + platfrm = platform.platform().split("-") + if platfrm[0].lower() == "linux": + installers = [ + {"bin": "apt", "options": [], "search": "search", "install": "install"}, + {"bin": "pacman", "options": [], "search": "-Ss", "install": "-S"}, + {"bin": "yum", "options": [], "search": "search", "install": "install"}, + {"bin": "docker", "options": []}, + ] + _paths = os.environ["PATH"].split(":") + for p in _paths: + for _installer in installers: + _fp = p + "/" + str(_installer["bin"]) + if os.path.isfile(_fp): + return _installer + + def copy_config(self, _file=None, _dirs=None): + if _file is None: + _file = self.nginx_conf + if _dirs is None: + _dirs = self.site_dirs + for r in _dirs: + copyfile(_file, r) + return True + + def make_nginx_config(self, answers): + breakpoint() + nginx_conf_str = """ + # pyshelf_nginx.conf + upstream django {server 127.0.0.1:8001;} + server { + listen %s; + server_name %s; + charset utf-8; + client_max_body_size 75M; + location /media {alias %s/frontend/interface/media;} + location /static {alias %s/frontend/interface/static;} + location /books {internal; alias %s;} + location / {uwsgi_pass django; include %s/uwsgi_params;} + } + """ % ( + answers + ) config = Configuration().open_file() +installer = None +messages = [] +# Get user configuration options install_answers = TerminalDisplay().installer() for key in install_answers: config[key["name"]] = key["answer"] + +# Write configuration Configuration().write_file(config) # Start checking for our list of required services -service_list = ["postgresql", "nginx", "httpd"] -req = RequiredServices.check_ps(service_list) +service_list = ["postgres", "nginx", "httpd", "test"] +req = RequiredServices().check_ps(service_list) + +# Does user have either nginx || apache? +if RequiredServices().web_server_found(req) is False: + web_prompt = [ + { + "message": "You must have either apache or nginx\nwould you like us to try and install nginx now? > ", + "options": "nginx", + "name": "NGINX", + "answer": None, + "default": "no", + } + ] + install_prompt = TerminalDisplay().prompt(web_prompt) + if install_prompt[0]["answer"] == "yes": + if installer is None: + installer = SystemInstaller().bin + if installer["bin"] == "pacman": + package = "nginx-mainline" + else: + package = "nginx" + options = "" + for o in installer["options"]: + options = options + " " + o + cmd = ( + "sudo " + + installer["bin"] + + " " + + installer["install"] + + " " + + options + + package + ) + install_status = os.system(cmd) + srvc_start = os.system("sudo systemctl start nginx") + messages = messages + [ + "Nginx installed and started", + "To enable autostart you must run", + " sudo systemctl enable nginx", + "\n", + ] +# Does user have postgreSQL? +if RequiredServices().db_server_found(req) is False: + db_prompt = [ + { + "message": "You must have PostgreSQL\nwould you like us to try and install it now? > ", + "options": "postgres", + "name": "postgresql", + "answer": None, + "default": "no", + } + ] + install_prompt = TerminalDisplay().prompt(db_prompt) + if install_prompt[0]["answer"] == "yes": + if installer is None: + installer = SystemInstaller().bin + options = "" + for o in installer["options"]: + options = options + " " + o + package = "postgresql" + cmd = ( + "sudo " + + installer["bin"] + + " " + + installer["install"] + + " " + + options + + package + ) + install_status = os.system(cmd) + copy_config = SystemInstaller().copy_config() + srvc_start = os.system("sudo systemctl start postgresql") + messages = messages + [ + "PostgreSQL installed and started", + "To enable autostart you must run", + " sudo systemctl enable nginx", + "\n", + ] + if copy_config: + messages = messages + ["pyShelf site config copied to sites_enabled"] + +# Display end screen +TerminalDisplay().clear() +TerminalDisplay().h_rule() +for message in messages: + print(message) +TerminalDisplay().h_rule() diff --git a/pyshelf_nginx.conf b/pyshelf_nginx.conf index 9820f27..f106c86 100644 --- a/pyshelf_nginx.conf +++ b/pyshelf_nginx.conf @@ -1,6 +1,5 @@ -# mysite_nginx.conf +# pyshelf_nginx.conf -# the upstream component nginx needs to connect to upstream django { # server unix:///path/to/your/mysite/mysite.sock; # for a file socket server 127.0.0.1:8001; # for a web port socket (we'll use this first) @@ -8,33 +7,32 @@ upstream django { # configuration of the server server { - # the port your site will be served on listen 8000; - # the domain name it will serve for server_name 127.0.0.1; # substitute your machine's IP address or FQDN charset utf-8; - - # max upload size client_max_body_size 75M; # adjust to taste # Django media location /media { - alias /home/raelon/Projects/pyShelf/frontend/interface/media; # your Django project's media files - amend as required + # your Django project's media files - amend as required + alias /home/raelon/Projects/pyShelf/frontend/interface/media; } location /static { - alias /home/raelon/Projects/pyShelf/frontend/interface/static; # your Django project's static files - amend as required + # your Django project's static files - amend as required + alias /home/raelon/Projects/pyShelf/frontend/interface/static; } location /books { + # Absolute location of your ebook files internal; alias /home/raelon/Projects/pyShelf/books; - # Absolute location of your ebook files } # Finally, send all non-media requests to the Django server. location / { + # the uwsgi_params file you installed uwsgi_pass django; - include /home/raelon/Projects/pyShelf/uwsgi_params; # the uwsgi_params file you installed + include /home/raelon/Projects/pyShelf/uwsgi_params; } } diff --git a/src/backend/lib/display.py b/src/backend/lib/display.py index 966beea..627f02f 100644 --- a/src/backend/lib/display.py +++ b/src/backend/lib/display.py @@ -1,5 +1,7 @@ from __future__ import unicode_literals + import os + from prompt_toolkit import prompt as prm @@ -16,39 +18,60 @@ class TerminalDisplay: def installer(self): questions = [ { - "message": "Input the absolute path to your ebooks\nEnter for default \"~/Books\" > ", + "message": 'Input the absolute path to your ebooks\nEnter for default "~/Books" > ', "options": "", "name": "BOOKPATH", "answer": None, - "default": self.home+"/Books" + "default": self.home + "/Books", }, { - "message": "Input your PostgreSQL server ip\nEnter for default \"localhost\" > ", + "message": 'Input your PostgreSQL server ip\nEnter for default "localhost" > ', "options": "localhost", "name": "DB_HOST", "answer": None, - "default": "localhost" + "default": "localhost", }, { - "message": "Input your PostgreSQL server port\nEnter for default \"5432\" > ", + "message": 'Input your PostgreSQL server port\nEnter for default "5432" > ', "options": "5432", "name": "DB_PORT", "answer": None, - "default": "5432" + "default": "5432", }, { - "message": "Input your PostgreSQL user name\nEnter for default \"pyshelf\" > ", + "message": 'Input your PostgreSQL user name\nEnter for default "pyshelf" > ', "options": "pyshelf", "name": "USER", "answer": None, - "default": "pyshelf" + "default": "pyshelf", }, { - "message": "Input your PostgreSQL password\nEnter for default \"pyshelf\" > ", + "message": 'Input your PostgreSQL password\nEnter for default "pyshelf" > ', "options": "pyshelf", "name": "PASSWORD", "answer": None, - "default": "pyshelf" + "default": "pyshelf", + }, + { + "message": 'Web ui hostname/ip\nEnter for default "localhost" > ', + "options": "localhost", + "name": "hostname", + "answer": None, + "default": "localhost", + }, + { + "message": 'Web ui port\nEnter for default "8000" > ', + "options": "8000", + "name": "webport", + "answer": None, + "default": "8000", + }, + { + "message": 'wsgi port\nEnter for default "8001"\n"You should probably leave this alone, unless you know what you\'re doing" > ', + "options": "8001", + "name": "wsgiport", + "answer": None, + "default": "8001", }, ] return self.prompt(questions) From 5456ea477dc129b897ac9aee109e5cc9b2f7c773 Mon Sep 17 00:00:00 2001 From: Mike Young Date: Sun, 29 Dec 2019 00:18:11 -0500 Subject: [PATCH 7/8] Finished ui design, system dependency installs, nginx config file generation --- config.json | 2 +- install | 82 +++++++++++++++++++++++++++++++++----- pyproject.toml | 2 +- pyshelf_nginx.conf | 38 ------------------ requirements.txt | 1 + src/backend/lib/display.py | 30 +++++++++++++- uwsgi.ini | 1 - 7 files changed, 103 insertions(+), 53 deletions(-) delete mode 100644 pyshelf_nginx.conf diff --git a/config.json b/config.json index c26edc5..18a02eb 100644 --- a/config.json +++ b/config.json @@ -1 +1 @@ -{"TITLE": "pyShelf E-Book Server", "VERSION": "0.3.0", "BOOKPATH": "/home/raelon/Books", "DB_HOST": "localhost", "DB_PORT": "5432", "DATABASE": "pyshelf", "USER": "pyshelf", "PASSWORD": "pyshelf", "BOOKSHELF": "data/shelf.json", "ALLOWED_HOSTS": "*"} +{"TITLE": "pyShelf E-Book Server", "VERSION": "0.3.0", "BOOKPATH": "/home/raelon/Books", "DB_HOST": "localhost", "DB_PORT": "5432", "DATABASE": "pyshelf", "USER": "pyshelf", "PASSWORD": "pyshelf", "BOOKSHELF": "data/shelf.json", "ALLOWED_HOSTS": "*", "hostname": "localhost", "webport": "8000", "wsgiport": "8001"} diff --git a/install b/install index 1fdbf92..7129ca0 100755 --- a/install +++ b/install @@ -1,8 +1,9 @@ -#!/usr/bin/python3.8 +#!/usr/bin/python3 import json import os import pathlib import platform +import pprint import subprocess as sp import sys from shutil import copyfile @@ -10,6 +11,9 @@ from shutil import copyfile import psutil from src.backend.lib.display import TerminalDisplay +log_file = "installer.log" +messages = [] + class Configuration: def __init__(self): @@ -110,6 +114,10 @@ class SystemInstaller: for _installer in installers: _fp = p + "/" + str(_installer["bin"]) if os.path.isfile(_fp): + global messages + messages = messages + [ + "Found system installer binary " + str(_installer["bin"]) + ] return _installer def copy_config(self, _file=None, _dirs=None): @@ -117,15 +125,42 @@ class SystemInstaller: _file = self.nginx_conf if _dirs is None: _dirs = self.site_dirs + + outfile = "/%s" % _file.__str__() + if os.path.isdir(_dirs[0]): + os.system("sudo cp %s %s" % (_file, _dirs[0] + outfile)) + else: + os.system("sudo mkdir %s" % _dirs[0]) + os.system("sudo cp %s %s" % (_file, _dirs[0] + outfile)) + try: + if os.path.isdir(_dirs[1]): + ln_string = str(_dirs[0] + outfile + " " + _dirs[1] + outfile) + os.system("sudo ln -s %s" % ln_string) + except Exception as e: + pass + """ for r in _dirs: - copyfile(_file, r) + if os.path.isdir(r): + os.system("sudo cp %s %s" % (_file, r+"/"+_file.__str__())) + else: + os.system("sudo mkdir %s" % r) + os.system("sudo cp %s %s" % (_file, r+"/"+_file.__str__())) + """ return True def make_nginx_config(self, answers): - breakpoint() + root = os.path.abspath(".") + _fp = "pyshelf_nginx.conf" + for r in answers: + if r["name"] == "hostname": + hostname = r["answer"] + elif r["name"] == "webport": + port = r["answer"] + elif r["name"] == "wsgiport": + wsgiport = r["answer"] nginx_conf_str = """ # pyshelf_nginx.conf - upstream django {server 127.0.0.1:8001;} + upstream django {server 127.0.0.1:%s;} server { listen %s; server_name %s; @@ -137,13 +172,32 @@ class SystemInstaller: location / {uwsgi_pass django; include %s/uwsgi_params;} } """ % ( - answers + wsgiport, + port, + hostname, + root, + root, + root, + root, ) + with open(_fp, "w") as write_file: + write_file.write(nginx_conf_str) + global messages + messages = messages + ["Generated new pyshelf_nginx.conf", nginx_conf_str] + + def log(self): + global log_file + global messages + with open(log_file, "w") as write_file: + write_file.write(TerminalDisplay().banner_render()) + for message in messages: + write_file.write(message + "\n") + messages = messages + ["Log file written to " + log_file.__str__()] config = Configuration().open_file() -installer = None -messages = [] +sysinstall = SystemInstaller() +installer = sysinstall.bin # Get user configuration options install_answers = TerminalDisplay().installer() for key in install_answers: @@ -224,7 +278,6 @@ if RequiredServices().db_server_found(req) is False: + package ) install_status = os.system(cmd) - copy_config = SystemInstaller().copy_config() srvc_start = os.system("sudo systemctl start postgresql") messages = messages + [ "PostgreSQL installed and started", @@ -232,12 +285,19 @@ if RequiredServices().db_server_found(req) is False: " sudo systemctl enable nginx", "\n", ] - if copy_config: - messages = messages + ["pyShelf site config copied to sites_enabled"] + +# Post install configurations +sysinstall.make_nginx_config(install_answers) +copy_config = sysinstall.copy_config() +if copy_config: + messages = messages + ["pyShelf site config copied to sites_enabled"] # Display end screen +sysinstall.log() TerminalDisplay().clear() -TerminalDisplay().h_rule() +TerminalDisplay().banner() for message in messages: print(message) +print() + TerminalDisplay().h_rule() diff --git a/pyproject.toml b/pyproject.toml index ad507ce..eaa5fde 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,4 +7,4 @@ use_parentheses = true # NOTE: the known_third_party setting is managed by # seed-isort-config and should not be modified directly. # Any changes made to this setting will be overwritten. -known_third_party = ["backend", "bs4", "django", "interface", "prompt_toolkit", "psycopg2", "requests"] +known_third_party = ["backend", "bs4", "django", "interface", "prompt_toolkit", "psycopg2", "pyfiglet", "requests"] diff --git a/pyshelf_nginx.conf b/pyshelf_nginx.conf deleted file mode 100644 index f106c86..0000000 --- a/pyshelf_nginx.conf +++ /dev/null @@ -1,38 +0,0 @@ -# pyshelf_nginx.conf - -upstream django { - # server unix:///path/to/your/mysite/mysite.sock; # for a file socket - server 127.0.0.1:8001; # for a web port socket (we'll use this first) -} - -# configuration of the server -server { - listen 8000; - server_name 127.0.0.1; # substitute your machine's IP address or FQDN - charset utf-8; - client_max_body_size 75M; # adjust to taste - - # Django media - location /media { - # your Django project's media files - amend as required - alias /home/raelon/Projects/pyShelf/frontend/interface/media; - } - - location /static { - # your Django project's static files - amend as required - alias /home/raelon/Projects/pyShelf/frontend/interface/static; - } - - location /books { - # Absolute location of your ebook files - internal; - alias /home/raelon/Projects/pyShelf/books; - } - - # Finally, send all non-media requests to the Django server. - location / { - # the uwsgi_params file you installed - uwsgi_pass django; - include /home/raelon/Projects/pyShelf/uwsgi_params; - } -} diff --git a/requirements.txt b/requirements.txt index 87a1a7f..dfb1cd5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,3 +17,4 @@ django-debug-toolbar psycopg2-binary prompt_toolkit psutil +pyfiglet diff --git a/src/backend/lib/display.py b/src/backend/lib/display.py index 627f02f..c487d7d 100644 --- a/src/backend/lib/display.py +++ b/src/backend/lib/display.py @@ -2,6 +2,7 @@ from __future__ import unicode_literals import os +import pyfiglet from prompt_toolkit import prompt as prm @@ -11,6 +12,11 @@ class TerminalDisplay: self.w, self.y = os.get_terminal_size()[0], os.get_terminal_size()[1] self.home = os.environ["HOME"] self.user = os.environ["USER"] + self.version = "0.4.0" + self.slogan = "Installer Initiative" + self.green = "\033[1;32m" + self.blue = "\033[94m" + self.clr_term = "\033[m" def screen(self): return self.term @@ -84,7 +90,7 @@ class TerminalDisplay: self.clear() answers = questions for answer in answers: - self.h_rule() + self.banner() answer["answer"] = prm(answer["message"]) if answer["answer"] == "": answer["answer"] = answer["default"] @@ -93,3 +99,25 @@ class TerminalDisplay: def h_rule(self): print("\u2501" * self.w) + + def banner(self): + self.h_rule() + title = pyfiglet.Figlet(font="cyberlarge") + print(self.green + title.renderText("pyShelf") + self.clr_term) + print( + self.blue + " version " + self.version + self.clr_term + " " + self.slogan + ) + self.h_rule() + print() + + def banner_render(self): + title = pyfiglet.Figlet(font="cyberlarge") + _banner = ( + title.renderText("pyShelf") + + "\nversion " + + self.version + + " " + + self.slogan + + "\n" + ) + return _banner diff --git a/uwsgi.ini b/uwsgi.ini index 9d68e9f..18bea03 100644 --- a/uwsgi.ini +++ b/uwsgi.ini @@ -1,5 +1,4 @@ [uwsgi] -# chdir = {Full path to pyShelf/frontend} chdir=/home/raelon/Projects/pyShelf/src module=frontend.wsgi master=True From 579c05c4ab0c76761ae58413aa0bba6b97525df9 Mon Sep 17 00:00:00 2001 From: Mike Young Date: Sun, 29 Dec 2019 22:14:28 -0500 Subject: [PATCH 8/8] Finished installer --- install.sh | 4 + install => installer | 84 ++- src/backend/lib/display.py | 18 +- src/frontend/settings.py | 2 +- .../static/admin/css/changelists.css | 3 +- src/interface/static/admin/css/responsive.css | 4 + .../css/vendor/select2/LICENSE-SELECT2.md | 2 +- .../admin/css/vendor/select2/select2.css | 10 +- .../admin/css/vendor/select2/select2.min.css | 2 +- src/interface/static/admin/css/widgets.css | 1 - .../static/admin/js/SelectFilter2.js | 2 +- .../admin/js/admin/DateTimeShortcuts.js | 6 +- src/interface/static/admin/js/core.js | 41 +- src/interface/static/admin/js/inlines.min.js | 23 +- .../static/admin/js/vendor/jquery/LICENSE.txt | 8 +- .../static/admin/js/vendor/jquery/jquery.js | 597 ++++++++++++------ .../admin/js/vendor/jquery/jquery.min.js | 4 +- .../static/admin/js/vendor/select2/LICENSE.md | 2 +- .../static/admin/js/vendor/select2/i18n/af.js | 3 + .../static/admin/js/vendor/select2/i18n/ar.js | 4 +- .../static/admin/js/vendor/select2/i18n/az.js | 4 +- .../static/admin/js/vendor/select2/i18n/bg.js | 4 +- .../static/admin/js/vendor/select2/i18n/bn.js | 3 + .../static/admin/js/vendor/select2/i18n/bs.js | 3 + .../static/admin/js/vendor/select2/i18n/ca.js | 4 +- .../static/admin/js/vendor/select2/i18n/cs.js | 4 +- .../static/admin/js/vendor/select2/i18n/da.js | 4 +- .../static/admin/js/vendor/select2/i18n/de.js | 4 +- .../admin/js/vendor/select2/i18n/dsb.js | 3 + .../static/admin/js/vendor/select2/i18n/el.js | 4 +- .../static/admin/js/vendor/select2/i18n/en.js | 4 +- .../static/admin/js/vendor/select2/i18n/es.js | 4 +- .../static/admin/js/vendor/select2/i18n/et.js | 4 +- .../static/admin/js/vendor/select2/i18n/eu.js | 4 +- .../static/admin/js/vendor/select2/i18n/fa.js | 4 +- .../static/admin/js/vendor/select2/i18n/fi.js | 4 +- .../static/admin/js/vendor/select2/i18n/fr.js | 4 +- .../static/admin/js/vendor/select2/i18n/gl.js | 4 +- .../static/admin/js/vendor/select2/i18n/he.js | 4 +- .../static/admin/js/vendor/select2/i18n/hi.js | 4 +- .../static/admin/js/vendor/select2/i18n/hr.js | 4 +- .../admin/js/vendor/select2/i18n/hsb.js | 3 + .../static/admin/js/vendor/select2/i18n/hu.js | 4 +- .../static/admin/js/vendor/select2/i18n/hy.js | 3 + .../static/admin/js/vendor/select2/i18n/id.js | 4 +- .../static/admin/js/vendor/select2/i18n/is.js | 4 +- .../static/admin/js/vendor/select2/i18n/it.js | 4 +- .../static/admin/js/vendor/select2/i18n/ja.js | 4 +- .../static/admin/js/vendor/select2/i18n/ka.js | 3 + .../static/admin/js/vendor/select2/i18n/km.js | 4 +- .../static/admin/js/vendor/select2/i18n/ko.js | 4 +- .../static/admin/js/vendor/select2/i18n/lt.js | 4 +- .../static/admin/js/vendor/select2/i18n/lv.js | 4 +- .../static/admin/js/vendor/select2/i18n/mk.js | 4 +- .../static/admin/js/vendor/select2/i18n/ms.js | 4 +- .../static/admin/js/vendor/select2/i18n/nb.js | 4 +- .../static/admin/js/vendor/select2/i18n/ne.js | 3 + .../static/admin/js/vendor/select2/i18n/nl.js | 4 +- .../static/admin/js/vendor/select2/i18n/pl.js | 4 +- .../static/admin/js/vendor/select2/i18n/ps.js | 3 + .../admin/js/vendor/select2/i18n/pt-BR.js | 4 +- .../static/admin/js/vendor/select2/i18n/pt.js | 4 +- .../static/admin/js/vendor/select2/i18n/ro.js | 4 +- .../static/admin/js/vendor/select2/i18n/ru.js | 4 +- .../static/admin/js/vendor/select2/i18n/sk.js | 4 +- .../static/admin/js/vendor/select2/i18n/sl.js | 3 + .../static/admin/js/vendor/select2/i18n/sq.js | 3 + .../admin/js/vendor/select2/i18n/sr-Cyrl.js | 4 +- .../static/admin/js/vendor/select2/i18n/sr.js | 4 +- .../static/admin/js/vendor/select2/i18n/sv.js | 4 +- .../static/admin/js/vendor/select2/i18n/th.js | 4 +- .../static/admin/js/vendor/select2/i18n/tk.js | 3 + .../static/admin/js/vendor/select2/i18n/tr.js | 4 +- .../static/admin/js/vendor/select2/i18n/uk.js | 4 +- .../static/admin/js/vendor/select2/i18n/vi.js | 4 +- .../admin/js/vendor/select2/i18n/zh-CN.js | 4 +- .../admin/js/vendor/select2/i18n/zh-TW.js | 4 +- .../admin/js/vendor/select2/select2.full.js | 453 ++++++++----- .../js/vendor/select2/select2.full.min.js | 4 +- 79 files changed, 963 insertions(+), 531 deletions(-) create mode 100755 install.sh rename install => installer (75%) mode change 100755 => 100644 create mode 100644 src/interface/static/admin/js/vendor/select2/i18n/af.js create mode 100644 src/interface/static/admin/js/vendor/select2/i18n/bn.js create mode 100644 src/interface/static/admin/js/vendor/select2/i18n/bs.js create mode 100644 src/interface/static/admin/js/vendor/select2/i18n/dsb.js create mode 100644 src/interface/static/admin/js/vendor/select2/i18n/hsb.js create mode 100644 src/interface/static/admin/js/vendor/select2/i18n/hy.js create mode 100644 src/interface/static/admin/js/vendor/select2/i18n/ka.js create mode 100644 src/interface/static/admin/js/vendor/select2/i18n/ne.js create mode 100644 src/interface/static/admin/js/vendor/select2/i18n/ps.js create mode 100644 src/interface/static/admin/js/vendor/select2/i18n/sl.js create mode 100644 src/interface/static/admin/js/vendor/select2/i18n/sq.js create mode 100644 src/interface/static/admin/js/vendor/select2/i18n/tk.js diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..cda8ef9 --- /dev/null +++ b/install.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +eval "pip install -r requirements.txt" +eval python3 installer diff --git a/install b/installer old mode 100755 new mode 100644 similarity index 75% rename from install rename to installer index 7129ca0..9e4546c --- a/install +++ b/installer @@ -138,14 +138,6 @@ class SystemInstaller: os.system("sudo ln -s %s" % ln_string) except Exception as e: pass - """ - for r in _dirs: - if os.path.isdir(r): - os.system("sudo cp %s %s" % (_file, r+"/"+_file.__str__())) - else: - os.system("sudo mkdir %s" % r) - os.system("sudo cp %s %s" % (_file, r+"/"+_file.__str__())) - """ return True def make_nginx_config(self, answers): @@ -160,14 +152,16 @@ class SystemInstaller: wsgiport = r["answer"] nginx_conf_str = """ # pyshelf_nginx.conf - upstream django {server 127.0.0.1:%s;} + upstream django {server localhost:%s;} server { listen %s; server_name %s; + access_log /var/log/nginx/pyshelf.access.log; + error_log /var/log/nginx/pyshelf.error.log; charset utf-8; client_max_body_size 75M; - location /media {alias %s/frontend/interface/media;} - location /static {alias %s/frontend/interface/static;} + location /media {root %s/src/interface;} + location /static {root %s/src/interface;} location /books {internal; alias %s;} location / {uwsgi_pass django; include %s/uwsgi_params;} } @@ -185,6 +179,32 @@ class SystemInstaller: global messages messages = messages + ["Generated new pyshelf_nginx.conf", nginx_conf_str] + def make_wsgi_config(self, answers): + root = os.path.abspath(".") + _fp = "uwsgi.ini" + for r in answers: + if r["name"] == "hostname": + hostname = r["answer"] + elif r["name"] == "wsgiport": + wsgiport = r["answer"] + wsgi_conf_str = """ + [uwsgi] + chdir=%s/src + module=frontend.wsgi + master=True + pidfile=/tmp/pyShelf.pid + vacuum=True + socket=%s:%s + """ % ( + root, + hostname, + wsgiport + ) + with open(_fp, "w") as write_file: + write_file.write(wsgi_conf_str) + global messages + messages = messages + ["Generated uwsgi.ini", wsgi_conf_str] + def log(self): global log_file global messages @@ -214,7 +234,7 @@ req = RequiredServices().check_ps(service_list) if RequiredServices().web_server_found(req) is False: web_prompt = [ { - "message": "You must have either apache or nginx\nwould you like us to try and install nginx now? > ", + "message": " You must have either apache or nginx\n would you like us to try and install nginx now? > ", "options": "nginx", "name": "NGINX", "answer": None, @@ -242,7 +262,7 @@ if RequiredServices().web_server_found(req) is False: + package ) install_status = os.system(cmd) - srvc_start = os.system("sudo systemctl start nginx") + os.system("sudo systemctl start nginx") messages = messages + [ "Nginx installed and started", "To enable autostart you must run", @@ -253,7 +273,7 @@ if RequiredServices().web_server_found(req) is False: if RequiredServices().db_server_found(req) is False: db_prompt = [ { - "message": "You must have PostgreSQL\nwould you like us to try and install it now? > ", + "message": " You must have PostgreSQL\n would you like us to try and install it now? > ", "options": "postgres", "name": "postgresql", "answer": None, @@ -267,7 +287,7 @@ if RequiredServices().db_server_found(req) is False: options = "" for o in installer["options"]: options = options + " " + o - package = "postgresql" + package = "postgresql postgresql-contrib" cmd = ( "sudo " + installer["bin"] @@ -278,26 +298,50 @@ if RequiredServices().db_server_found(req) is False: + package ) install_status = os.system(cmd) - srvc_start = os.system("sudo systemctl start postgresql") + for r in install_answers: + if r["name"] == "USER": sql_user = r["answer"] + elif r["name"] == "PASSWORD": sql_pass = r["answer"] + db_name = "pyshelf" + psql_cmd = """ + CREATE DATABASE %s; + CREATE USER %s WITH ENCRYPTED PASSWORD \'%s\'; + GRANT ALL PRIVILEGES ON DATABASE %s TO %s; + """ % (db_name, sql_user, sql_pass, db_name, sql_user) + _sql_file = "create_db.sql" + with open(_sql_file, "w") as sql_file_open: + sql_file_open.write(psql_cmd) + sql_file_open.close() + os.system("sudo systemctl start postgresql") + os.system("sudo -u postgres initdb --locale=en_US.UTF-8 -E UTF8 -D /var/lib/postgres/data") + os.system("sudo -u postgres psql -f %s"%_sql_file) messages = messages + [ "PostgreSQL installed and started", "To enable autostart you must run", " sudo systemctl enable nginx", "\n", + "Database cluster initialized at /var/lib/postgres", + "pyShelf database and user created", + psql_cmd ] # Post install configurations sysinstall.make_nginx_config(install_answers) -copy_config = sysinstall.copy_config() -if copy_config: - messages = messages + ["pyShelf site config copied to sites_enabled"] +try: + copy_config = sysinstall.copy_config() + if copy_config: + messages = messages + ["pyShelf site config copied to sites-available, and symlinked to sites-enabled"] +except Exception as e: + messages = messages + ["nginx site config not copied", + "you are responsible for setting up your web server"] +sysinstall.make_wsgi_config(install_answers) +message = message + ["You should now import your books by running importBooks", "You can then start the interface with uwsgi --ini uwsgi.ini"] # Display end screen sysinstall.log() TerminalDisplay().clear() TerminalDisplay().banner() for message in messages: - print(message) + print(" "+message) print() TerminalDisplay().h_rule() diff --git a/src/backend/lib/display.py b/src/backend/lib/display.py index c487d7d..eb8d624 100644 --- a/src/backend/lib/display.py +++ b/src/backend/lib/display.py @@ -13,7 +13,7 @@ class TerminalDisplay: self.home = os.environ["HOME"] self.user = os.environ["USER"] self.version = "0.4.0" - self.slogan = "Installer Initiative" + self.slogan = "The Installer Initiative" self.green = "\033[1;32m" self.blue = "\033[94m" self.clr_term = "\033[m" @@ -24,56 +24,56 @@ class TerminalDisplay: def installer(self): questions = [ { - "message": 'Input the absolute path to your ebooks\nEnter for default "~/Books" > ', + "message": ' Input the absolute path to your ebooks\n Enter for default "~/Books" > ', "options": "", "name": "BOOKPATH", "answer": None, "default": self.home + "/Books", }, { - "message": 'Input your PostgreSQL server ip\nEnter for default "localhost" > ', + "message": ' Input your PostgreSQL server ip\n Enter for default "localhost" > ', "options": "localhost", "name": "DB_HOST", "answer": None, "default": "localhost", }, { - "message": 'Input your PostgreSQL server port\nEnter for default "5432" > ', + "message": ' Input your PostgreSQL server port\n Enter for default "5432" > ', "options": "5432", "name": "DB_PORT", "answer": None, "default": "5432", }, { - "message": 'Input your PostgreSQL user name\nEnter for default "pyshelf" > ', + "message": ' Input your PostgreSQL user name\n Enter for default "pyshelf" > ', "options": "pyshelf", "name": "USER", "answer": None, "default": "pyshelf", }, { - "message": 'Input your PostgreSQL password\nEnter for default "pyshelf" > ', + "message": ' Input your PostgreSQL password\n Enter for default "pyshelf" > ', "options": "pyshelf", "name": "PASSWORD", "answer": None, "default": "pyshelf", }, { - "message": 'Web ui hostname/ip\nEnter for default "localhost" > ', + "message": ' Web ui hostname/ip\n Enter for default "localhost" > ', "options": "localhost", "name": "hostname", "answer": None, "default": "localhost", }, { - "message": 'Web ui port\nEnter for default "8000" > ', + "message": ' Web ui port\n Enter for default "8000" > ', "options": "8000", "name": "webport", "answer": None, "default": "8000", }, { - "message": 'wsgi port\nEnter for default "8001"\n"You should probably leave this alone, unless you know what you\'re doing" > ', + "message": ' wsgi port\n Enter for default "8001 > ', "options": "8001", "name": "wsgiport", "answer": None, diff --git a/src/frontend/settings.py b/src/frontend/settings.py index c669448..ced1c33 100755 --- a/src/frontend/settings.py +++ b/src/frontend/settings.py @@ -33,7 +33,7 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) SECRET_KEY = "@(9b9jslgg41u1u=mr)-2*-n2x0vef0zsy39*z@sz18&tvow18" # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = True +DEBUG = False ALLOWED_HOSTS = CONFIG.allowed_hosts diff --git a/src/interface/static/admin/css/changelists.css b/src/interface/static/admin/css/changelists.css index 17690a3..30a6386 100644 --- a/src/interface/static/admin/css/changelists.css +++ b/src/interface/static/admin/css/changelists.css @@ -98,7 +98,8 @@ #changelist #toolbar form input[type="submit"] { border: 1px solid #ccc; - padding: 2px 10px; + font-size: 13px; + padding: 4px 8px; margin: 0; vertical-align: middle; background: #fff; diff --git a/src/interface/static/admin/css/responsive.css b/src/interface/static/admin/css/responsive.css index 5b0d1ec..b3db28f 100644 --- a/src/interface/static/admin/css/responsive.css +++ b/src/interface/static/admin/css/responsive.css @@ -392,6 +392,10 @@ input[type="submit"], button { color: #ccc; } + .form-row .datetime input.vDateField, .form-row .datetime input.vTimeField { + width: 75%; + } + .inline-group { overflow: auto; } diff --git a/src/interface/static/admin/css/vendor/select2/LICENSE-SELECT2.md b/src/interface/static/admin/css/vendor/select2/LICENSE-SELECT2.md index 86c7c29..8cb8a2b 100644 --- a/src/interface/static/admin/css/vendor/select2/LICENSE-SELECT2.md +++ b/src/interface/static/admin/css/vendor/select2/LICENSE-SELECT2.md @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2012-2015 Kevin Brown, Igor Vaynberg, and Select2 contributors +Copyright (c) 2012-2017 Kevin Brown, Igor Vaynberg, and Select2 contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/interface/static/admin/css/vendor/select2/select2.css b/src/interface/static/admin/css/vendor/select2/select2.css index 447b2b8..ce3afd1 100644 --- a/src/interface/static/admin/css/vendor/select2/select2.css +++ b/src/interface/static/admin/css/vendor/select2/select2.css @@ -118,12 +118,14 @@ .select2-hidden-accessible { border: 0 !important; clip: rect(0 0 0 0) !important; + -webkit-clip-path: inset(50%) !important; + clip-path: inset(50%) !important; height: 1px !important; - margin: -1px !important; overflow: hidden !important; padding: 0 !important; position: absolute !important; - width: 1px !important; } + width: 1px !important; + white-space: nowrap !important; } .select2-container--default .select2-selection--single { background-color: #fff; @@ -420,9 +422,7 @@ color: #555; } .select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice { - float: right; } - -.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice { + float: right; margin-left: 5px; margin-right: auto; } diff --git a/src/interface/static/admin/css/vendor/select2/select2.min.css b/src/interface/static/admin/css/vendor/select2/select2.min.css index 76de04d..60d5990 100644 --- a/src/interface/static/admin/css/vendor/select2/select2.min.css +++ b/src/interface/static/admin/css/vendor/select2/select2.min.css @@ -1 +1 @@ -.select2-container{box-sizing:border-box;display:inline-block;margin:0;position:relative;vertical-align:middle}.select2-container .select2-selection--single{box-sizing:border-box;cursor:pointer;display:block;height:28px;user-select:none;-webkit-user-select:none}.select2-container .select2-selection--single .select2-selection__rendered{display:block;padding-left:8px;padding-right:20px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.select2-container .select2-selection--single .select2-selection__clear{position:relative}.select2-container[dir="rtl"] .select2-selection--single .select2-selection__rendered{padding-right:8px;padding-left:20px}.select2-container .select2-selection--multiple{box-sizing:border-box;cursor:pointer;display:block;min-height:32px;user-select:none;-webkit-user-select:none}.select2-container .select2-selection--multiple .select2-selection__rendered{display:inline-block;overflow:hidden;padding-left:8px;text-overflow:ellipsis;white-space:nowrap}.select2-container .select2-search--inline{float:left}.select2-container .select2-search--inline .select2-search__field{box-sizing:border-box;border:none;font-size:100%;margin-top:5px;padding:0}.select2-container .select2-search--inline .select2-search__field::-webkit-search-cancel-button{-webkit-appearance:none}.select2-dropdown{background-color:white;border:1px solid #aaa;border-radius:4px;box-sizing:border-box;display:block;position:absolute;left:-100000px;width:100%;z-index:1051}.select2-results{display:block}.select2-results__options{list-style:none;margin:0;padding:0}.select2-results__option{padding:6px;user-select:none;-webkit-user-select:none}.select2-results__option[aria-selected]{cursor:pointer}.select2-container--open .select2-dropdown{left:0}.select2-container--open .select2-dropdown--above{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--open .select2-dropdown--below{border-top:none;border-top-left-radius:0;border-top-right-radius:0}.select2-search--dropdown{display:block;padding:4px}.select2-search--dropdown .select2-search__field{padding:4px;width:100%;box-sizing:border-box}.select2-search--dropdown .select2-search__field::-webkit-search-cancel-button{-webkit-appearance:none}.select2-search--dropdown.select2-search--hide{display:none}.select2-close-mask{border:0;margin:0;padding:0;display:block;position:fixed;left:0;top:0;min-height:100%;min-width:100%;height:auto;width:auto;opacity:0;z-index:99;background-color:#fff;filter:alpha(opacity=0)}.select2-hidden-accessible{border:0 !important;clip:rect(0 0 0 0) !important;height:1px !important;margin:-1px !important;overflow:hidden !important;padding:0 !important;position:absolute !important;width:1px !important}.select2-container--default .select2-selection--single{background-color:#fff;border:1px solid #aaa;border-radius:4px}.select2-container--default .select2-selection--single .select2-selection__rendered{color:#444;line-height:28px}.select2-container--default .select2-selection--single .select2-selection__clear{cursor:pointer;float:right;font-weight:bold}.select2-container--default .select2-selection--single .select2-selection__placeholder{color:#999}.select2-container--default .select2-selection--single .select2-selection__arrow{height:26px;position:absolute;top:1px;right:1px;width:20px}.select2-container--default .select2-selection--single .select2-selection__arrow b{border-color:#888 transparent transparent transparent;border-style:solid;border-width:5px 4px 0 4px;height:0;left:50%;margin-left:-4px;margin-top:-2px;position:absolute;top:50%;width:0}.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__clear{float:left}.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__arrow{left:1px;right:auto}.select2-container--default.select2-container--disabled .select2-selection--single{background-color:#eee;cursor:default}.select2-container--default.select2-container--disabled .select2-selection--single .select2-selection__clear{display:none}.select2-container--default.select2-container--open .select2-selection--single .select2-selection__arrow b{border-color:transparent transparent #888 transparent;border-width:0 4px 5px 4px}.select2-container--default .select2-selection--multiple{background-color:white;border:1px solid #aaa;border-radius:4px;cursor:text}.select2-container--default .select2-selection--multiple .select2-selection__rendered{box-sizing:border-box;list-style:none;margin:0;padding:0 5px;width:100%}.select2-container--default .select2-selection--multiple .select2-selection__rendered li{list-style:none}.select2-container--default .select2-selection--multiple .select2-selection__placeholder{color:#999;margin-top:5px;float:left}.select2-container--default .select2-selection--multiple .select2-selection__clear{cursor:pointer;float:right;font-weight:bold;margin-top:5px;margin-right:10px}.select2-container--default .select2-selection--multiple .select2-selection__choice{background-color:#e4e4e4;border:1px solid #aaa;border-radius:4px;cursor:default;float:left;margin-right:5px;margin-top:5px;padding:0 5px}.select2-container--default .select2-selection--multiple .select2-selection__choice__remove{color:#999;cursor:pointer;display:inline-block;font-weight:bold;margin-right:2px}.select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover{color:#333}.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice,.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__placeholder,.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-search--inline{float:right}.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice{margin-left:5px;margin-right:auto}.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove{margin-left:2px;margin-right:auto}.select2-container--default.select2-container--focus .select2-selection--multiple{border:solid black 1px;outline:0}.select2-container--default.select2-container--disabled .select2-selection--multiple{background-color:#eee;cursor:default}.select2-container--default.select2-container--disabled .select2-selection__choice__remove{display:none}.select2-container--default.select2-container--open.select2-container--above .select2-selection--single,.select2-container--default.select2-container--open.select2-container--above .select2-selection--multiple{border-top-left-radius:0;border-top-right-radius:0}.select2-container--default.select2-container--open.select2-container--below .select2-selection--single,.select2-container--default.select2-container--open.select2-container--below .select2-selection--multiple{border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--default .select2-search--dropdown .select2-search__field{border:1px solid #aaa}.select2-container--default .select2-search--inline .select2-search__field{background:transparent;border:none;outline:0;box-shadow:none;-webkit-appearance:textfield}.select2-container--default .select2-results>.select2-results__options{max-height:200px;overflow-y:auto}.select2-container--default .select2-results__option[role=group]{padding:0}.select2-container--default .select2-results__option[aria-disabled=true]{color:#999}.select2-container--default .select2-results__option[aria-selected=true]{background-color:#ddd}.select2-container--default .select2-results__option .select2-results__option{padding-left:1em}.select2-container--default .select2-results__option .select2-results__option .select2-results__group{padding-left:0}.select2-container--default .select2-results__option .select2-results__option .select2-results__option{margin-left:-1em;padding-left:2em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-2em;padding-left:3em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-3em;padding-left:4em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-4em;padding-left:5em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-5em;padding-left:6em}.select2-container--default .select2-results__option--highlighted[aria-selected]{background-color:#5897fb;color:white}.select2-container--default .select2-results__group{cursor:default;display:block;padding:6px}.select2-container--classic .select2-selection--single{background-color:#f7f7f7;border:1px solid #aaa;border-radius:4px;outline:0;background-image:-webkit-linear-gradient(top, #fff 50%, #eee 100%);background-image:-o-linear-gradient(top, #fff 50%, #eee 100%);background-image:linear-gradient(to bottom, #fff 50%, #eee 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0)}.select2-container--classic .select2-selection--single:focus{border:1px solid #5897fb}.select2-container--classic .select2-selection--single .select2-selection__rendered{color:#444;line-height:28px}.select2-container--classic .select2-selection--single .select2-selection__clear{cursor:pointer;float:right;font-weight:bold;margin-right:10px}.select2-container--classic .select2-selection--single .select2-selection__placeholder{color:#999}.select2-container--classic .select2-selection--single .select2-selection__arrow{background-color:#ddd;border:none;border-left:1px solid #aaa;border-top-right-radius:4px;border-bottom-right-radius:4px;height:26px;position:absolute;top:1px;right:1px;width:20px;background-image:-webkit-linear-gradient(top, #eee 50%, #ccc 100%);background-image:-o-linear-gradient(top, #eee 50%, #ccc 100%);background-image:linear-gradient(to bottom, #eee 50%, #ccc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFCCCCCC', GradientType=0)}.select2-container--classic .select2-selection--single .select2-selection__arrow b{border-color:#888 transparent transparent transparent;border-style:solid;border-width:5px 4px 0 4px;height:0;left:50%;margin-left:-4px;margin-top:-2px;position:absolute;top:50%;width:0}.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__clear{float:left}.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__arrow{border:none;border-right:1px solid #aaa;border-radius:0;border-top-left-radius:4px;border-bottom-left-radius:4px;left:1px;right:auto}.select2-container--classic.select2-container--open .select2-selection--single{border:1px solid #5897fb}.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow{background:transparent;border:none}.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow b{border-color:transparent transparent #888 transparent;border-width:0 4px 5px 4px}.select2-container--classic.select2-container--open.select2-container--above .select2-selection--single{border-top:none;border-top-left-radius:0;border-top-right-radius:0;background-image:-webkit-linear-gradient(top, #fff 0%, #eee 50%);background-image:-o-linear-gradient(top, #fff 0%, #eee 50%);background-image:linear-gradient(to bottom, #fff 0%, #eee 50%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0)}.select2-container--classic.select2-container--open.select2-container--below .select2-selection--single{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0;background-image:-webkit-linear-gradient(top, #eee 50%, #fff 100%);background-image:-o-linear-gradient(top, #eee 50%, #fff 100%);background-image:linear-gradient(to bottom, #eee 50%, #fff 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFFFFFFF', GradientType=0)}.select2-container--classic .select2-selection--multiple{background-color:white;border:1px solid #aaa;border-radius:4px;cursor:text;outline:0}.select2-container--classic .select2-selection--multiple:focus{border:1px solid #5897fb}.select2-container--classic .select2-selection--multiple .select2-selection__rendered{list-style:none;margin:0;padding:0 5px}.select2-container--classic .select2-selection--multiple .select2-selection__clear{display:none}.select2-container--classic .select2-selection--multiple .select2-selection__choice{background-color:#e4e4e4;border:1px solid #aaa;border-radius:4px;cursor:default;float:left;margin-right:5px;margin-top:5px;padding:0 5px}.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove{color:#888;cursor:pointer;display:inline-block;font-weight:bold;margin-right:2px}.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove:hover{color:#555}.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice{float:right}.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice{margin-left:5px;margin-right:auto}.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove{margin-left:2px;margin-right:auto}.select2-container--classic.select2-container--open .select2-selection--multiple{border:1px solid #5897fb}.select2-container--classic.select2-container--open.select2-container--above .select2-selection--multiple{border-top:none;border-top-left-radius:0;border-top-right-radius:0}.select2-container--classic.select2-container--open.select2-container--below .select2-selection--multiple{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--classic .select2-search--dropdown .select2-search__field{border:1px solid #aaa;outline:0}.select2-container--classic .select2-search--inline .select2-search__field{outline:0;box-shadow:none}.select2-container--classic .select2-dropdown{background-color:#fff;border:1px solid transparent}.select2-container--classic .select2-dropdown--above{border-bottom:none}.select2-container--classic .select2-dropdown--below{border-top:none}.select2-container--classic .select2-results>.select2-results__options{max-height:200px;overflow-y:auto}.select2-container--classic .select2-results__option[role=group]{padding:0}.select2-container--classic .select2-results__option[aria-disabled=true]{color:grey}.select2-container--classic .select2-results__option--highlighted[aria-selected]{background-color:#3875d7;color:#fff}.select2-container--classic .select2-results__group{cursor:default;display:block;padding:6px}.select2-container--classic.select2-container--open .select2-dropdown{border-color:#5897fb} +.select2-container{box-sizing:border-box;display:inline-block;margin:0;position:relative;vertical-align:middle}.select2-container .select2-selection--single{box-sizing:border-box;cursor:pointer;display:block;height:28px;user-select:none;-webkit-user-select:none}.select2-container .select2-selection--single .select2-selection__rendered{display:block;padding-left:8px;padding-right:20px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.select2-container .select2-selection--single .select2-selection__clear{position:relative}.select2-container[dir="rtl"] .select2-selection--single .select2-selection__rendered{padding-right:8px;padding-left:20px}.select2-container .select2-selection--multiple{box-sizing:border-box;cursor:pointer;display:block;min-height:32px;user-select:none;-webkit-user-select:none}.select2-container .select2-selection--multiple .select2-selection__rendered{display:inline-block;overflow:hidden;padding-left:8px;text-overflow:ellipsis;white-space:nowrap}.select2-container .select2-search--inline{float:left}.select2-container .select2-search--inline .select2-search__field{box-sizing:border-box;border:none;font-size:100%;margin-top:5px;padding:0}.select2-container .select2-search--inline .select2-search__field::-webkit-search-cancel-button{-webkit-appearance:none}.select2-dropdown{background-color:white;border:1px solid #aaa;border-radius:4px;box-sizing:border-box;display:block;position:absolute;left:-100000px;width:100%;z-index:1051}.select2-results{display:block}.select2-results__options{list-style:none;margin:0;padding:0}.select2-results__option{padding:6px;user-select:none;-webkit-user-select:none}.select2-results__option[aria-selected]{cursor:pointer}.select2-container--open .select2-dropdown{left:0}.select2-container--open .select2-dropdown--above{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--open .select2-dropdown--below{border-top:none;border-top-left-radius:0;border-top-right-radius:0}.select2-search--dropdown{display:block;padding:4px}.select2-search--dropdown .select2-search__field{padding:4px;width:100%;box-sizing:border-box}.select2-search--dropdown .select2-search__field::-webkit-search-cancel-button{-webkit-appearance:none}.select2-search--dropdown.select2-search--hide{display:none}.select2-close-mask{border:0;margin:0;padding:0;display:block;position:fixed;left:0;top:0;min-height:100%;min-width:100%;height:auto;width:auto;opacity:0;z-index:99;background-color:#fff;filter:alpha(opacity=0)}.select2-hidden-accessible{border:0 !important;clip:rect(0 0 0 0) !important;-webkit-clip-path:inset(50%) !important;clip-path:inset(50%) !important;height:1px !important;overflow:hidden !important;padding:0 !important;position:absolute !important;width:1px !important;white-space:nowrap !important}.select2-container--default .select2-selection--single{background-color:#fff;border:1px solid #aaa;border-radius:4px}.select2-container--default .select2-selection--single .select2-selection__rendered{color:#444;line-height:28px}.select2-container--default .select2-selection--single .select2-selection__clear{cursor:pointer;float:right;font-weight:bold}.select2-container--default .select2-selection--single .select2-selection__placeholder{color:#999}.select2-container--default .select2-selection--single .select2-selection__arrow{height:26px;position:absolute;top:1px;right:1px;width:20px}.select2-container--default .select2-selection--single .select2-selection__arrow b{border-color:#888 transparent transparent transparent;border-style:solid;border-width:5px 4px 0 4px;height:0;left:50%;margin-left:-4px;margin-top:-2px;position:absolute;top:50%;width:0}.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__clear{float:left}.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__arrow{left:1px;right:auto}.select2-container--default.select2-container--disabled .select2-selection--single{background-color:#eee;cursor:default}.select2-container--default.select2-container--disabled .select2-selection--single .select2-selection__clear{display:none}.select2-container--default.select2-container--open .select2-selection--single .select2-selection__arrow b{border-color:transparent transparent #888 transparent;border-width:0 4px 5px 4px}.select2-container--default .select2-selection--multiple{background-color:white;border:1px solid #aaa;border-radius:4px;cursor:text}.select2-container--default .select2-selection--multiple .select2-selection__rendered{box-sizing:border-box;list-style:none;margin:0;padding:0 5px;width:100%}.select2-container--default .select2-selection--multiple .select2-selection__rendered li{list-style:none}.select2-container--default .select2-selection--multiple .select2-selection__placeholder{color:#999;margin-top:5px;float:left}.select2-container--default .select2-selection--multiple .select2-selection__clear{cursor:pointer;float:right;font-weight:bold;margin-top:5px;margin-right:10px}.select2-container--default .select2-selection--multiple .select2-selection__choice{background-color:#e4e4e4;border:1px solid #aaa;border-radius:4px;cursor:default;float:left;margin-right:5px;margin-top:5px;padding:0 5px}.select2-container--default .select2-selection--multiple .select2-selection__choice__remove{color:#999;cursor:pointer;display:inline-block;font-weight:bold;margin-right:2px}.select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover{color:#333}.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice,.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__placeholder,.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-search--inline{float:right}.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice{margin-left:5px;margin-right:auto}.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove{margin-left:2px;margin-right:auto}.select2-container--default.select2-container--focus .select2-selection--multiple{border:solid black 1px;outline:0}.select2-container--default.select2-container--disabled .select2-selection--multiple{background-color:#eee;cursor:default}.select2-container--default.select2-container--disabled .select2-selection__choice__remove{display:none}.select2-container--default.select2-container--open.select2-container--above .select2-selection--single,.select2-container--default.select2-container--open.select2-container--above .select2-selection--multiple{border-top-left-radius:0;border-top-right-radius:0}.select2-container--default.select2-container--open.select2-container--below .select2-selection--single,.select2-container--default.select2-container--open.select2-container--below .select2-selection--multiple{border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--default .select2-search--dropdown .select2-search__field{border:1px solid #aaa}.select2-container--default .select2-search--inline .select2-search__field{background:transparent;border:none;outline:0;box-shadow:none;-webkit-appearance:textfield}.select2-container--default .select2-results>.select2-results__options{max-height:200px;overflow-y:auto}.select2-container--default .select2-results__option[role=group]{padding:0}.select2-container--default .select2-results__option[aria-disabled=true]{color:#999}.select2-container--default .select2-results__option[aria-selected=true]{background-color:#ddd}.select2-container--default .select2-results__option .select2-results__option{padding-left:1em}.select2-container--default .select2-results__option .select2-results__option .select2-results__group{padding-left:0}.select2-container--default .select2-results__option .select2-results__option .select2-results__option{margin-left:-1em;padding-left:2em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-2em;padding-left:3em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-3em;padding-left:4em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-4em;padding-left:5em}.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option{margin-left:-5em;padding-left:6em}.select2-container--default .select2-results__option--highlighted[aria-selected]{background-color:#5897fb;color:white}.select2-container--default .select2-results__group{cursor:default;display:block;padding:6px}.select2-container--classic .select2-selection--single{background-color:#f7f7f7;border:1px solid #aaa;border-radius:4px;outline:0;background-image:-webkit-linear-gradient(top, #fff 50%, #eee 100%);background-image:-o-linear-gradient(top, #fff 50%, #eee 100%);background-image:linear-gradient(to bottom, #fff 50%, #eee 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0)}.select2-container--classic .select2-selection--single:focus{border:1px solid #5897fb}.select2-container--classic .select2-selection--single .select2-selection__rendered{color:#444;line-height:28px}.select2-container--classic .select2-selection--single .select2-selection__clear{cursor:pointer;float:right;font-weight:bold;margin-right:10px}.select2-container--classic .select2-selection--single .select2-selection__placeholder{color:#999}.select2-container--classic .select2-selection--single .select2-selection__arrow{background-color:#ddd;border:none;border-left:1px solid #aaa;border-top-right-radius:4px;border-bottom-right-radius:4px;height:26px;position:absolute;top:1px;right:1px;width:20px;background-image:-webkit-linear-gradient(top, #eee 50%, #ccc 100%);background-image:-o-linear-gradient(top, #eee 50%, #ccc 100%);background-image:linear-gradient(to bottom, #eee 50%, #ccc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFCCCCCC', GradientType=0)}.select2-container--classic .select2-selection--single .select2-selection__arrow b{border-color:#888 transparent transparent transparent;border-style:solid;border-width:5px 4px 0 4px;height:0;left:50%;margin-left:-4px;margin-top:-2px;position:absolute;top:50%;width:0}.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__clear{float:left}.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__arrow{border:none;border-right:1px solid #aaa;border-radius:0;border-top-left-radius:4px;border-bottom-left-radius:4px;left:1px;right:auto}.select2-container--classic.select2-container--open .select2-selection--single{border:1px solid #5897fb}.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow{background:transparent;border:none}.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow b{border-color:transparent transparent #888 transparent;border-width:0 4px 5px 4px}.select2-container--classic.select2-container--open.select2-container--above .select2-selection--single{border-top:none;border-top-left-radius:0;border-top-right-radius:0;background-image:-webkit-linear-gradient(top, #fff 0%, #eee 50%);background-image:-o-linear-gradient(top, #fff 0%, #eee 50%);background-image:linear-gradient(to bottom, #fff 0%, #eee 50%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0)}.select2-container--classic.select2-container--open.select2-container--below .select2-selection--single{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0;background-image:-webkit-linear-gradient(top, #eee 50%, #fff 100%);background-image:-o-linear-gradient(top, #eee 50%, #fff 100%);background-image:linear-gradient(to bottom, #eee 50%, #fff 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFFFFFFF', GradientType=0)}.select2-container--classic .select2-selection--multiple{background-color:white;border:1px solid #aaa;border-radius:4px;cursor:text;outline:0}.select2-container--classic .select2-selection--multiple:focus{border:1px solid #5897fb}.select2-container--classic .select2-selection--multiple .select2-selection__rendered{list-style:none;margin:0;padding:0 5px}.select2-container--classic .select2-selection--multiple .select2-selection__clear{display:none}.select2-container--classic .select2-selection--multiple .select2-selection__choice{background-color:#e4e4e4;border:1px solid #aaa;border-radius:4px;cursor:default;float:left;margin-right:5px;margin-top:5px;padding:0 5px}.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove{color:#888;cursor:pointer;display:inline-block;font-weight:bold;margin-right:2px}.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove:hover{color:#555}.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice{float:right;margin-left:5px;margin-right:auto}.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove{margin-left:2px;margin-right:auto}.select2-container--classic.select2-container--open .select2-selection--multiple{border:1px solid #5897fb}.select2-container--classic.select2-container--open.select2-container--above .select2-selection--multiple{border-top:none;border-top-left-radius:0;border-top-right-radius:0}.select2-container--classic.select2-container--open.select2-container--below .select2-selection--multiple{border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0}.select2-container--classic .select2-search--dropdown .select2-search__field{border:1px solid #aaa;outline:0}.select2-container--classic .select2-search--inline .select2-search__field{outline:0;box-shadow:none}.select2-container--classic .select2-dropdown{background-color:#fff;border:1px solid transparent}.select2-container--classic .select2-dropdown--above{border-bottom:none}.select2-container--classic .select2-dropdown--below{border-top:none}.select2-container--classic .select2-results>.select2-results__options{max-height:200px;overflow-y:auto}.select2-container--classic .select2-results__option[role=group]{padding:0}.select2-container--classic .select2-results__option[aria-disabled=true]{color:grey}.select2-container--classic .select2-results__option--highlighted[aria-selected]{background-color:#3875d7;color:#fff}.select2-container--classic .select2-results__group{cursor:default;display:block;padding:6px}.select2-container--classic.select2-container--open .select2-dropdown{border-color:#5897fb} diff --git a/src/interface/static/admin/css/widgets.css b/src/interface/static/admin/css/widgets.css index d3bd67a..6dbc58e 100644 --- a/src/interface/static/admin/css/widgets.css +++ b/src/interface/static/admin/css/widgets.css @@ -263,7 +263,6 @@ p.datetime { } .datetime input, .form-row .datetime input.vDateField, .form-row .datetime input.vTimeField { - min-width: 0; margin-left: 5px; margin-bottom: 4px; } diff --git a/src/interface/static/admin/js/SelectFilter2.js b/src/interface/static/admin/js/SelectFilter2.js index 4221778..46056b8 100644 --- a/src/interface/static/admin/js/SelectFilter2.js +++ b/src/interface/static/admin/js/SelectFilter2.js @@ -179,11 +179,11 @@ Requires jQuery, core.js, and SelectBox.js. // This is much faster in WebKit browsers than the fallback. field.attr('required', 'required'); any_selected = field.is(':valid'); - field.removeAttr('required'); } catch (e) { // Browsers that don't support :valid (IE < 10) any_selected = field.find('option:selected').length > 0; } + field.removeAttr('required'); return any_selected; }, refresh_icons: function(field_id) { diff --git a/src/interface/static/admin/js/admin/DateTimeShortcuts.js b/src/interface/static/admin/js/admin/DateTimeShortcuts.js index 1ee7c2a..f803523 100644 --- a/src/interface/static/admin/js/admin/DateTimeShortcuts.js +++ b/src/interface/static/admin/js/admin/DateTimeShortcuts.js @@ -1,4 +1,4 @@ -/*global Calendar, findPosX, findPosY, getStyle, get_format, gettext, gettext_noop, interpolate, ngettext, quickElement*/ +/*global Calendar, findPosX, findPosY, get_format, gettext, gettext_noop, interpolate, ngettext, quickElement*/ // Inserts shortcut buttons after all of the following: // // @@ -197,7 +197,7 @@ // Recalculate the clockbox position // is it left-to-right or right-to-left layout ? - if (getStyle(document.body, 'direction') !== 'rtl') { + if (window.getComputedStyle(document.body).direction !== 'rtl') { clock_box.style.left = findPosX(clock_link) + 17 + 'px'; } else { @@ -370,7 +370,7 @@ // Recalculate the clockbox position // is it left-to-right or right-to-left layout ? - if (getStyle(document.body, 'direction') !== 'rtl') { + if (window.getComputedStyle(document.body).direction !== 'rtl') { cal_box.style.left = findPosX(cal_link) + 17 + 'px'; } else { diff --git a/src/interface/static/admin/js/core.js b/src/interface/static/admin/js/core.js index 4f2fe13..e3ca0b6 100644 --- a/src/interface/static/admin/js/core.js +++ b/src/interface/static/admin/js/core.js @@ -74,13 +74,7 @@ function findPosY(obj) { (function() { 'use strict'; Date.prototype.getTwelveHours = function() { - var hours = this.getHours(); - if (hours === 0) { - return 12; - } - else { - return hours <= 12 ? hours : hours - 12; - } + return this.getHours() % 12 || 12; }; Date.prototype.getTwoDigitMonth = function() { @@ -107,14 +101,6 @@ function findPosY(obj) { return (this.getSeconds() < 10) ? '0' + this.getSeconds() : this.getSeconds(); }; - Date.prototype.getHourMinute = function() { - return this.getTwoDigitHour() + ':' + this.getTwoDigitMinute(); - }; - - Date.prototype.getHourMinuteSecond = function() { - return this.getTwoDigitHour() + ':' + this.getTwoDigitMinute() + ':' + this.getTwoDigitSecond(); - }; - Date.prototype.getFullMonthName = function() { return typeof window.CalendarNamespace === "undefined" ? this.getTwoDigitMonth() @@ -156,14 +142,6 @@ function findPosY(obj) { // ---------------------------------------------------------------------------- // String object extensions // ---------------------------------------------------------------------------- - String.prototype.pad_left = function(pad_length, pad_string) { - var new_string = this; - for (var i = 0; new_string.length < pad_length; i++) { - new_string = pad_string + new_string; - } - return new_string; - }; - String.prototype.strptime = function(format) { var split_format = format.split(/[.\-/]/); var date = this.split(/[.\-/]/); @@ -193,20 +171,3 @@ function findPosY(obj) { }; })(); -// ---------------------------------------------------------------------------- -// Get the computed style for and element -// ---------------------------------------------------------------------------- -function getStyle(oElm, strCssRule) { - 'use strict'; - var strValue = ""; - if(document.defaultView && document.defaultView.getComputedStyle) { - strValue = document.defaultView.getComputedStyle(oElm, "").getPropertyValue(strCssRule); - } - else if(oElm.currentStyle) { - strCssRule = strCssRule.replace(/\-(\w)/g, function(strMatch, p1) { - return p1.toUpperCase(); - }); - strValue = oElm.currentStyle[strCssRule]; - } - return strValue; -} diff --git a/src/interface/static/admin/js/inlines.min.js b/src/interface/static/admin/js/inlines.min.js index 65af8eb..0b818ca 100644 --- a/src/interface/static/admin/js/inlines.min.js +++ b/src/interface/static/admin/js/inlines.min.js @@ -1,13 +1,10 @@ -var $jscomp=$jscomp||{};$jscomp.scope={};$jscomp.findInternal=function(b,d,a){b instanceof String&&(b=String(b));for(var c=b.length,f=0;f'+a.addText+""),l=d.find("tr:last a")):(c.filter(":last").after('"),l=c.filter(":last").next().find("a")));l.on("click",function(d){d.preventDefault();d=b("#"+a.prefix+"-empty"); -var c=d.clone(!0);c.removeClass(a.emptyCssClass).addClass(a.formCssClass).attr("id",a.prefix+"-"+h);c.is("tr")?c.children(":last").append('"):c.is("ul")||c.is("ol")?c.append('
  • '+a.deleteText+"
  • "):c.children(":first").append(''+a.deleteText+"");c.find("*").each(function(){f(this,a.prefix,g.val())});c.insertBefore(b(d)); -b(g).val(parseInt(g.val(),10)+1);h+=1;""!==e.val()&&0>=e.val()-g.val()&&l.parent().hide();c.find("a."+a.deleteCssClass).on("click",function(d){d.preventDefault();c.remove();--h;a.removed&&a.removed(c);b(document).trigger("formset:removed",[c,a.prefix]);d=b("."+a.formCssClass);b("#id_"+a.prefix+"-TOTAL_FORMS").val(d.length);(""===e.val()||0 tr",b(d).tabularFormset(d,a.options)}})})})(django.jQuery); +(function(b){b.fn.formset=function(c){var a=b.extend({},b.fn.formset.defaults,c),d=b(this);c=d.parent();var h=function(a,e,f){var c=new RegExp("("+e+"-(\\d+|__prefix__))");e=e+"-"+f;b(a).prop("for")&&b(a).prop("for",b(a).prop("for").replace(c,e));a.id&&(a.id=a.id.replace(c,e));a.name&&(a.name=a.name.replace(c,e))},g=b("#id_"+a.prefix+"-TOTAL_FORMS").prop("autocomplete","off"),k=parseInt(g.val(),10),e=b("#id_"+a.prefix+"-MAX_NUM_FORMS").prop("autocomplete","off"),f=""===e.val()||0'+a.addText+""),m=c.find("tr:last a")):(d.filter(":last").after('"),m=d.filter(":last").next().find("a")));m.on("click",function(f){f.preventDefault();f=b("#"+a.prefix+"-empty"); +var c=f.clone(!0);c.removeClass(a.emptyCssClass).addClass(a.formCssClass).attr("id",a.prefix+"-"+k);c.is("tr")?c.children(":last").append('"):c.is("ul")||c.is("ol")?c.append('
  • '+a.deleteText+"
  • "):c.children(":first").append(''+a.deleteText+"");c.find("*").each(function(){h(this,a.prefix,g.val())});c.insertBefore(b(f)); +b(g).val(parseInt(g.val(),10)+1);k+=1;""!==e.val()&&0>=e.val()-g.val()&&m.parent().hide();c.find("a."+a.deleteCssClass).on("click",function(f){f.preventDefault();c.remove();--k;a.removed&&a.removed(c);b(document).trigger("formset:removed",[c,a.prefix]);f=b("."+a.formCssClass);b("#id_"+a.prefix+"-TOTAL_FORMS").val(f.length);(""===e.val()||0 tr",b(c).tabularFormset(c,a.options)}})})})(django.jQuery); diff --git a/src/interface/static/admin/js/vendor/jquery/LICENSE.txt b/src/interface/static/admin/js/vendor/jquery/LICENSE.txt index d930e62..e3dbacb 100644 --- a/src/interface/static/admin/js/vendor/jquery/LICENSE.txt +++ b/src/interface/static/admin/js/vendor/jquery/LICENSE.txt @@ -1,10 +1,4 @@ -Copyright jQuery Foundation and other contributors, https://jquery.org/ - -This software consists of voluntary contributions made by many -individuals. For exact contribution history, see the revision history -available at https://github.com/jquery/jquery - -==== +Copyright JS Foundation and other contributors, https://js.foundation/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/src/interface/static/admin/js/vendor/jquery/jquery.js b/src/interface/static/admin/js/vendor/jquery/jquery.js index 34a5703..773ad95 100644 --- a/src/interface/static/admin/js/vendor/jquery/jquery.js +++ b/src/interface/static/admin/js/vendor/jquery/jquery.js @@ -1,5 +1,5 @@ /*! - * jQuery JavaScript Library v3.3.1 + * jQuery JavaScript Library v3.4.1 * https://jquery.com/ * * Includes Sizzle.js @@ -9,7 +9,7 @@ * Released under the MIT license * https://jquery.org/license * - * Date: 2018-01-20T17:24Z + * Date: 2019-05-01T21:04Z */ ( function( global, factory ) { @@ -91,20 +91,33 @@ var isWindow = function isWindow( obj ) { var preservedScriptAttributes = { type: true, src: true, + nonce: true, noModule: true }; - function DOMEval( code, doc, node ) { + function DOMEval( code, node, doc ) { doc = doc || document; - var i, + var i, val, script = doc.createElement( "script" ); script.text = code; if ( node ) { for ( i in preservedScriptAttributes ) { - if ( node[ i ] ) { - script[ i ] = node[ i ]; + + // Support: Firefox 64+, Edge 18+ + // Some browsers don't support the "nonce" property on scripts. + // On the other hand, just using `getAttribute` is not enough as + // the `nonce` attribute is reset to an empty string whenever it + // becomes browsing-context connected. + // See https://github.com/whatwg/html/issues/2369 + // See https://html.spec.whatwg.org/#nonce-attributes + // The `node.getAttribute` check was added for the sake of + // `jQuery.globalEval` so that it can fake a nonce-containing node + // via an object. + val = node[ i ] || node.getAttribute && node.getAttribute( i ); + if ( val ) { + script.setAttribute( i, val ); } } } @@ -129,7 +142,7 @@ function toType( obj ) { var - version = "3.3.1", + version = "3.4.1", // Define a local copy of jQuery jQuery = function( selector, context ) { @@ -258,7 +271,6 @@ jQuery.extend = jQuery.fn.extend = function() { // Extend the base object for ( name in options ) { - src = target[ name ]; copy = options[ name ]; // Prevent Object.prototype pollution @@ -270,14 +282,17 @@ jQuery.extend = jQuery.fn.extend = function() { // Recurse if we're merging plain objects or arrays if ( deep && copy && ( jQuery.isPlainObject( copy ) || ( copyIsArray = Array.isArray( copy ) ) ) ) { + src = target[ name ]; - if ( copyIsArray ) { - copyIsArray = false; - clone = src && Array.isArray( src ) ? src : []; - + // Ensure proper type for the source value + if ( copyIsArray && !Array.isArray( src ) ) { + clone = []; + } else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) { + clone = {}; } else { - clone = src && jQuery.isPlainObject( src ) ? src : {}; + clone = src; } + copyIsArray = false; // Never move original objects, clone them target[ name ] = jQuery.extend( deep, clone, copy ); @@ -330,9 +345,6 @@ jQuery.extend( { }, isEmptyObject: function( obj ) { - - /* eslint-disable no-unused-vars */ - // See https://github.com/eslint/eslint/issues/6125 var name; for ( name in obj ) { @@ -342,8 +354,8 @@ jQuery.extend( { }, // Evaluates a script in a global context - globalEval: function( code ) { - DOMEval( code ); + globalEval: function( code, options ) { + DOMEval( code, { nonce: options && options.nonce } ); }, each: function( obj, callback ) { @@ -499,14 +511,14 @@ function isArrayLike( obj ) { } var Sizzle = /*! - * Sizzle CSS Selector Engine v2.3.3 + * Sizzle CSS Selector Engine v2.3.4 * https://sizzlejs.com/ * - * Copyright jQuery Foundation and other contributors + * Copyright JS Foundation and other contributors * Released under the MIT license - * http://jquery.org/license + * https://js.foundation/ * - * Date: 2016-08-08 + * Date: 2019-04-08 */ (function( window ) { @@ -540,6 +552,7 @@ var i, classCache = createCache(), tokenCache = createCache(), compilerCache = createCache(), + nonnativeSelectorCache = createCache(), sortOrder = function( a, b ) { if ( a === b ) { hasDuplicate = true; @@ -601,8 +614,7 @@ var i, rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ), - - rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g" ), + rdescend = new RegExp( whitespace + "|>" ), rpseudo = new RegExp( pseudos ), ridentifier = new RegExp( "^" + identifier + "$" ), @@ -623,6 +635,7 @@ var i, whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) }, + rhtml = /HTML$/i, rinputs = /^(?:input|select|textarea|button)$/i, rheader = /^h\d$/i, @@ -677,9 +690,9 @@ var i, setDocument(); }, - disabledAncestor = addCombinator( + inDisabledFieldset = addCombinator( function( elem ) { - return elem.disabled === true && ("form" in elem || "label" in elem); + return elem.disabled === true && elem.nodeName.toLowerCase() === "fieldset"; }, { dir: "parentNode", next: "legend" } ); @@ -792,18 +805,22 @@ function Sizzle( selector, context, results, seed ) { // Take advantage of querySelectorAll if ( support.qsa && - !compilerCache[ selector + " " ] && - (!rbuggyQSA || !rbuggyQSA.test( selector )) ) { + !nonnativeSelectorCache[ selector + " " ] && + (!rbuggyQSA || !rbuggyQSA.test( selector )) && - if ( nodeType !== 1 ) { - newContext = context; - newSelector = selector; - - // qSA looks outside Element context, which is not what we want - // Thanks to Andrew Dupont for this workaround technique - // Support: IE <=8 + // Support: IE 8 only // Exclude object elements - } else if ( context.nodeName.toLowerCase() !== "object" ) { + (nodeType !== 1 || context.nodeName.toLowerCase() !== "object") ) { + + newSelector = selector; + newContext = context; + + // qSA considers elements outside a scoping root when evaluating child or + // descendant combinators, which is not what we want. + // In such cases, we work around the behavior by prefixing every selector in the + // list with an ID selector referencing the scope context. + // Thanks to Andrew Dupont for this technique. + if ( nodeType === 1 && rdescend.test( selector ) ) { // Capture the context ID, setting it first if necessary if ( (nid = context.getAttribute( "id" )) ) { @@ -825,17 +842,16 @@ function Sizzle( selector, context, results, seed ) { context; } - if ( newSelector ) { - try { - push.apply( results, - newContext.querySelectorAll( newSelector ) - ); - return results; - } catch ( qsaError ) { - } finally { - if ( nid === expando ) { - context.removeAttribute( "id" ); - } + try { + push.apply( results, + newContext.querySelectorAll( newSelector ) + ); + return results; + } catch ( qsaError ) { + nonnativeSelectorCache( selector, true ); + } finally { + if ( nid === expando ) { + context.removeAttribute( "id" ); } } } @@ -999,7 +1015,7 @@ function createDisabledPseudo( disabled ) { // Where there is no isDisabled, check manually /* jshint -W018 */ elem.isDisabled !== !disabled && - disabledAncestor( elem ) === disabled; + inDisabledFieldset( elem ) === disabled; } return elem.disabled === disabled; @@ -1056,10 +1072,13 @@ support = Sizzle.support = {}; * @returns {Boolean} True iff elem is a non-HTML XML node */ isXML = Sizzle.isXML = function( elem ) { - // documentElement is verified for cases where it doesn't yet exist - // (such as loading iframes in IE - #4833) - var documentElement = elem && (elem.ownerDocument || elem).documentElement; - return documentElement ? documentElement.nodeName !== "HTML" : false; + var namespace = elem.namespaceURI, + docElem = (elem.ownerDocument || elem).documentElement; + + // Support: IE <=8 + // Assume HTML when documentElement doesn't yet exist, such as inside loading iframes + // https://bugs.jquery.com/ticket/4833 + return !rhtml.test( namespace || docElem && docElem.nodeName || "HTML" ); }; /** @@ -1481,11 +1500,8 @@ Sizzle.matchesSelector = function( elem, expr ) { setDocument( elem ); } - // Make sure that attribute selectors are quoted - expr = expr.replace( rattributeQuotes, "='$1']" ); - if ( support.matchesSelector && documentIsHTML && - !compilerCache[ expr + " " ] && + !nonnativeSelectorCache[ expr + " " ] && ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { @@ -1499,7 +1515,9 @@ Sizzle.matchesSelector = function( elem, expr ) { elem.document && elem.document.nodeType !== 11 ) { return ret; } - } catch (e) {} + } catch (e) { + nonnativeSelectorCache( expr, true ); + } } return Sizzle( expr, document, null, [ elem ] ).length > 0; @@ -1958,7 +1976,7 @@ Expr = Sizzle.selectors = { "contains": markFunction(function( text ) { text = text.replace( runescape, funescape ); return function( elem ) { - return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1; + return ( elem.textContent || getText( elem ) ).indexOf( text ) > -1; }; }), @@ -2097,7 +2115,11 @@ Expr = Sizzle.selectors = { }), "lt": createPositionalPseudo(function( matchIndexes, length, argument ) { - var i = argument < 0 ? argument + length : argument; + var i = argument < 0 ? + argument + length : + argument > length ? + length : + argument; for ( ; --i >= 0; ) { matchIndexes.push( i ); } @@ -3147,18 +3169,18 @@ jQuery.each( { return siblings( elem.firstChild ); }, contents: function( elem ) { - if ( nodeName( elem, "iframe" ) ) { - return elem.contentDocument; - } + if ( typeof elem.contentDocument !== "undefined" ) { + return elem.contentDocument; + } - // Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only - // Treat the template element as a regular one in browsers that - // don't support it. - if ( nodeName( elem, "template" ) ) { - elem = elem.content || elem; - } + // Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only + // Treat the template element as a regular one in browsers that + // don't support it. + if ( nodeName( elem, "template" ) ) { + elem = elem.content || elem; + } - return jQuery.merge( [], elem.childNodes ); + return jQuery.merge( [], elem.childNodes ); } }, function( name, fn ) { jQuery.fn[ name ] = function( until, selector ) { @@ -4467,6 +4489,26 @@ var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ); var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; +var documentElement = document.documentElement; + + + + var isAttached = function( elem ) { + return jQuery.contains( elem.ownerDocument, elem ); + }, + composed = { composed: true }; + + // Support: IE 9 - 11+, Edge 12 - 18+, iOS 10.0 - 10.2 only + // Check attachment across shadow DOM boundaries when possible (gh-3504) + // Support: iOS 10.0-10.2 only + // Early iOS 10 versions support `attachShadow` but not `getRootNode`, + // leading to errors. We need to check for `getRootNode`. + if ( documentElement.getRootNode ) { + isAttached = function( elem ) { + return jQuery.contains( elem.ownerDocument, elem ) || + elem.getRootNode( composed ) === elem.ownerDocument; + }; + } var isHiddenWithinTree = function( elem, el ) { // isHiddenWithinTree might be called from jQuery#filter function; @@ -4481,7 +4523,7 @@ var isHiddenWithinTree = function( elem, el ) { // Support: Firefox <=43 - 45 // Disconnected elements can have computed display: none, so first confirm that elem is // in the document. - jQuery.contains( elem.ownerDocument, elem ) && + isAttached( elem ) && jQuery.css( elem, "display" ) === "none"; }; @@ -4523,7 +4565,8 @@ function adjustCSS( elem, prop, valueParts, tween ) { unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ), // Starting value computation is required for potential unit mismatches - initialInUnit = ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) && + initialInUnit = elem.nodeType && + ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) && rcssNum.exec( jQuery.css( elem, prop ) ); if ( initialInUnit && initialInUnit[ 3 ] !== unit ) { @@ -4670,7 +4713,7 @@ jQuery.fn.extend( { } ); var rcheckableType = ( /^(?:checkbox|radio)$/i ); -var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]+)/i ); +var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]*)/i ); var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i ); @@ -4742,7 +4785,7 @@ function setGlobalEval( elems, refElements ) { var rhtml = /<|&#?\w+;/; function buildFragment( elems, context, scripts, selection, ignored ) { - var elem, tmp, tag, wrap, contains, j, + var elem, tmp, tag, wrap, attached, j, fragment = context.createDocumentFragment(), nodes = [], i = 0, @@ -4806,13 +4849,13 @@ function buildFragment( elems, context, scripts, selection, ignored ) { continue; } - contains = jQuery.contains( elem.ownerDocument, elem ); + attached = isAttached( elem ); // Append to fragment tmp = getAll( fragment.appendChild( elem ), "script" ); // Preserve script evaluation history - if ( contains ) { + if ( attached ) { setGlobalEval( tmp ); } @@ -4855,8 +4898,6 @@ function buildFragment( elems, context, scripts, selection, ignored ) { div.innerHTML = ""; support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; } )(); -var documentElement = document.documentElement; - var @@ -4872,8 +4913,19 @@ function returnFalse() { return false; } +// Support: IE <=9 - 11+ +// focus() and blur() are asynchronous, except when they are no-op. +// So expect focus to be synchronous when the element is already active, +// and blur to be synchronous when the element is not already active. +// (focus and blur are always synchronous in other supported browsers, +// this just defines when we can count on it). +function expectSync( elem, type ) { + return ( elem === safeActiveElement() ) === ( type === "focus" ); +} + // Support: IE <=9 only -// See #13393 for more info +// Accessing document.activeElement can throw unexpectedly +// https://bugs.jquery.com/ticket/13393 function safeActiveElement() { try { return document.activeElement; @@ -5173,9 +5225,10 @@ jQuery.event = { while ( ( handleObj = matched.handlers[ j++ ] ) && !event.isImmediatePropagationStopped() ) { - // Triggered event must either 1) have no namespace, or 2) have namespace(s) - // a subset or equal to those in the bound event (both can have no namespace). - if ( !event.rnamespace || event.rnamespace.test( handleObj.namespace ) ) { + // If the event is namespaced, then each handler is only invoked if it is + // specially universal or its namespaces are a superset of the event's. + if ( !event.rnamespace || handleObj.namespace === false || + event.rnamespace.test( handleObj.namespace ) ) { event.handleObj = handleObj; event.data = handleObj.data; @@ -5299,39 +5352,51 @@ jQuery.event = { // Prevent triggered image.load events from bubbling to window.load noBubble: true }, - focus: { - - // Fire native event if possible so blur/focus sequence is correct - trigger: function() { - if ( this !== safeActiveElement() && this.focus ) { - this.focus(); - return false; - } - }, - delegateType: "focusin" - }, - blur: { - trigger: function() { - if ( this === safeActiveElement() && this.blur ) { - this.blur(); - return false; - } - }, - delegateType: "focusout" - }, click: { - // For checkbox, fire native event so checked state will be right - trigger: function() { - if ( this.type === "checkbox" && this.click && nodeName( this, "input" ) ) { - this.click(); - return false; + // Utilize native event to ensure correct state for checkable inputs + setup: function( data ) { + + // For mutual compressibility with _default, replace `this` access with a local var. + // `|| data` is dead code meant only to preserve the variable through minification. + var el = this || data; + + // Claim the first handler + if ( rcheckableType.test( el.type ) && + el.click && nodeName( el, "input" ) ) { + + // dataPriv.set( el, "click", ... ) + leverageNative( el, "click", returnTrue ); } + + // Return false to allow normal processing in the caller + return false; + }, + trigger: function( data ) { + + // For mutual compressibility with _default, replace `this` access with a local var. + // `|| data` is dead code meant only to preserve the variable through minification. + var el = this || data; + + // Force setup before triggering a click + if ( rcheckableType.test( el.type ) && + el.click && nodeName( el, "input" ) ) { + + leverageNative( el, "click" ); + } + + // Return non-false to allow normal event-path propagation + return true; }, - // For cross-browser consistency, don't fire native .click() on links + // For cross-browser consistency, suppress native .click() on links + // Also prevent it if we're currently inside a leveraged native-event stack _default: function( event ) { - return nodeName( event.target, "a" ); + var target = event.target; + return rcheckableType.test( target.type ) && + target.click && nodeName( target, "input" ) && + dataPriv.get( target, "click" ) || + nodeName( target, "a" ); } }, @@ -5348,6 +5413,93 @@ jQuery.event = { } }; +// Ensure the presence of an event listener that handles manually-triggered +// synthetic events by interrupting progress until reinvoked in response to +// *native* events that it fires directly, ensuring that state changes have +// already occurred before other listeners are invoked. +function leverageNative( el, type, expectSync ) { + + // Missing expectSync indicates a trigger call, which must force setup through jQuery.event.add + if ( !expectSync ) { + if ( dataPriv.get( el, type ) === undefined ) { + jQuery.event.add( el, type, returnTrue ); + } + return; + } + + // Register the controller as a special universal handler for all event namespaces + dataPriv.set( el, type, false ); + jQuery.event.add( el, type, { + namespace: false, + handler: function( event ) { + var notAsync, result, + saved = dataPriv.get( this, type ); + + if ( ( event.isTrigger & 1 ) && this[ type ] ) { + + // Interrupt processing of the outer synthetic .trigger()ed event + // Saved data should be false in such cases, but might be a leftover capture object + // from an async native handler (gh-4350) + if ( !saved.length ) { + + // Store arguments for use when handling the inner native event + // There will always be at least one argument (an event object), so this array + // will not be confused with a leftover capture object. + saved = slice.call( arguments ); + dataPriv.set( this, type, saved ); + + // Trigger the native event and capture its result + // Support: IE <=9 - 11+ + // focus() and blur() are asynchronous + notAsync = expectSync( this, type ); + this[ type ](); + result = dataPriv.get( this, type ); + if ( saved !== result || notAsync ) { + dataPriv.set( this, type, false ); + } else { + result = {}; + } + if ( saved !== result ) { + + // Cancel the outer synthetic event + event.stopImmediatePropagation(); + event.preventDefault(); + return result.value; + } + + // If this is an inner synthetic event for an event with a bubbling surrogate + // (focus or blur), assume that the surrogate already propagated from triggering the + // native event and prevent that from happening again here. + // This technically gets the ordering wrong w.r.t. to `.trigger()` (in which the + // bubbling surrogate propagates *after* the non-bubbling base), but that seems + // less bad than duplication. + } else if ( ( jQuery.event.special[ type ] || {} ).delegateType ) { + event.stopPropagation(); + } + + // If this is a native event triggered above, everything is now in order + // Fire an inner synthetic event with the original arguments + } else if ( saved.length ) { + + // ...and capture the result + dataPriv.set( this, type, { + value: jQuery.event.trigger( + + // Support: IE <=9 - 11+ + // Extend with the prototype to reset the above stopImmediatePropagation() + jQuery.extend( saved[ 0 ], jQuery.Event.prototype ), + saved.slice( 1 ), + this + ) + } ); + + // Abort handling of the native event + event.stopImmediatePropagation(); + } + } + } ); +} + jQuery.removeEvent = function( elem, type, handle ) { // This "if" is needed for plain objects @@ -5460,6 +5612,7 @@ jQuery.each( { shiftKey: true, view: true, "char": true, + code: true, charCode: true, key: true, keyCode: true, @@ -5506,6 +5659,33 @@ jQuery.each( { } }, jQuery.event.addProp ); +jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateType ) { + jQuery.event.special[ type ] = { + + // Utilize native event if possible so blur/focus sequence is correct + setup: function() { + + // Claim the first handler + // dataPriv.set( this, "focus", ... ) + // dataPriv.set( this, "blur", ... ) + leverageNative( this, type, expectSync ); + + // Return false to allow normal processing in the caller + return false; + }, + trigger: function() { + + // Force setup before trigger + leverageNative( this, type ); + + // Return non-false to allow normal event-path propagation + return true; + }, + + delegateType: delegateType + }; +} ); + // Create mouseenter/leave events using mouseover/out and event-time checks // so that event delegation works in jQuery. // Do the same for pointerenter/pointerleave and pointerover/pointerout @@ -5756,11 +5936,13 @@ function domManip( collection, args, callback, ignored ) { if ( node.src && ( node.type || "" ).toLowerCase() !== "module" ) { // Optional AJAX dependency, but won't run scripts if not present - if ( jQuery._evalUrl ) { - jQuery._evalUrl( node.src ); + if ( jQuery._evalUrl && !node.noModule ) { + jQuery._evalUrl( node.src, { + nonce: node.nonce || node.getAttribute( "nonce" ) + } ); } } else { - DOMEval( node.textContent.replace( rcleanScript, "" ), doc, node ); + DOMEval( node.textContent.replace( rcleanScript, "" ), node, doc ); } } } @@ -5782,7 +5964,7 @@ function remove( elem, selector, keepData ) { } if ( node.parentNode ) { - if ( keepData && jQuery.contains( node.ownerDocument, node ) ) { + if ( keepData && isAttached( node ) ) { setGlobalEval( getAll( node, "script" ) ); } node.parentNode.removeChild( node ); @@ -5800,7 +5982,7 @@ jQuery.extend( { clone: function( elem, dataAndEvents, deepDataAndEvents ) { var i, l, srcElements, destElements, clone = elem.cloneNode( true ), - inPage = jQuery.contains( elem.ownerDocument, elem ); + inPage = isAttached( elem ); // Fix IE cloning issues if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && @@ -6096,8 +6278,10 @@ var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" ); // Support: IE 9 only // Detect overflow:scroll screwiness (gh-3699) + // Support: Chrome <=64 + // Don't get tricked when zoom affects offsetWidth (gh-4029) div.style.position = "absolute"; - scrollboxSizeVal = div.offsetWidth === 36 || "absolute"; + scrollboxSizeVal = roundPixelMeasures( div.offsetWidth / 3 ) === 12; documentElement.removeChild( container ); @@ -6168,7 +6352,7 @@ function curCSS( elem, name, computed ) { if ( computed ) { ret = computed.getPropertyValue( name ) || computed[ name ]; - if ( ret === "" && !jQuery.contains( elem.ownerDocument, elem ) ) { + if ( ret === "" && !isAttached( elem ) ) { ret = jQuery.style( elem, name ); } @@ -6224,30 +6408,13 @@ function addGetHookIf( conditionFn, hookFn ) { } -var +var cssPrefixes = [ "Webkit", "Moz", "ms" ], + emptyStyle = document.createElement( "div" ).style, + vendorProps = {}; - // Swappable if display is none or starts with table - // except "table", "table-cell", or "table-caption" - // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display - rdisplayswap = /^(none|table(?!-c[ea]).+)/, - rcustomProp = /^--/, - cssShow = { position: "absolute", visibility: "hidden", display: "block" }, - cssNormalTransform = { - letterSpacing: "0", - fontWeight: "400" - }, - - cssPrefixes = [ "Webkit", "Moz", "ms" ], - emptyStyle = document.createElement( "div" ).style; - -// Return a css property mapped to a potentially vendor prefixed property +// Return a vendor-prefixed property or undefined function vendorPropName( name ) { - // Shortcut for names that are not vendor prefixed - if ( name in emptyStyle ) { - return name; - } - // Check for vendor prefixed names var capName = name[ 0 ].toUpperCase() + name.slice( 1 ), i = cssPrefixes.length; @@ -6260,16 +6427,33 @@ function vendorPropName( name ) { } } -// Return a property mapped along what jQuery.cssProps suggests or to -// a vendor prefixed property. +// Return a potentially-mapped jQuery.cssProps or vendor prefixed property function finalPropName( name ) { - var ret = jQuery.cssProps[ name ]; - if ( !ret ) { - ret = jQuery.cssProps[ name ] = vendorPropName( name ) || name; + var final = jQuery.cssProps[ name ] || vendorProps[ name ]; + + if ( final ) { + return final; } - return ret; + if ( name in emptyStyle ) { + return name; + } + return vendorProps[ name ] = vendorPropName( name ) || name; } + +var + + // Swappable if display is none or starts with table + // except "table", "table-cell", or "table-caption" + // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display + rdisplayswap = /^(none|table(?!-c[ea]).+)/, + rcustomProp = /^--/, + cssShow = { position: "absolute", visibility: "hidden", display: "block" }, + cssNormalTransform = { + letterSpacing: "0", + fontWeight: "400" + }; + function setPositiveNumber( elem, value, subtract ) { // Any relative (+/-) values have already been @@ -6341,7 +6525,10 @@ function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computed delta - extra - 0.5 - ) ); + + // If offsetWidth/offsetHeight is unknown, then we can't determine content-box scroll gutter + // Use an explicit zero to avoid NaN (gh-3964) + ) ) || 0; } return delta; @@ -6351,9 +6538,16 @@ function getWidthOrHeight( elem, dimension, extra ) { // Start with computed style var styles = getStyles( elem ), + + // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-4322). + // Fake content-box until we know it's needed to know the true value. + boxSizingNeeded = !support.boxSizingReliable() || extra, + isBorderBox = boxSizingNeeded && + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + valueIsBorderBox = isBorderBox, + val = curCSS( elem, dimension, styles ), - isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box", - valueIsBorderBox = isBorderBox; + offsetProp = "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ); // Support: Firefox <=54 // Return a confounding non-pixel value or feign ignorance, as appropriate. @@ -6364,22 +6558,29 @@ function getWidthOrHeight( elem, dimension, extra ) { val = "auto"; } - // Check for style in case a browser which returns unreliable values - // for getComputedStyle silently falls back to the reliable elem.style - valueIsBorderBox = valueIsBorderBox && - ( support.boxSizingReliable() || val === elem.style[ dimension ] ); // Fall back to offsetWidth/offsetHeight when value is "auto" // This happens for inline elements with no explicit setting (gh-3571) // Support: Android <=4.1 - 4.3 only // Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602) - if ( val === "auto" || - !parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) { + // Support: IE 9-11 only + // Also use offsetWidth/offsetHeight for when box sizing is unreliable + // We use getClientRects() to check for hidden/disconnected. + // In those cases, the computed value can be trusted to be border-box + if ( ( !support.boxSizingReliable() && isBorderBox || + val === "auto" || + !parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) && + elem.getClientRects().length ) { - val = elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ]; + isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; - // offsetWidth/offsetHeight provide border-box values - valueIsBorderBox = true; + // Where available, offsetWidth/offsetHeight approximate border box dimensions. + // Where not available (e.g., SVG), assume unreliable box-sizing and interpret the + // retrieved value as a content box dimension. + valueIsBorderBox = offsetProp in elem; + if ( valueIsBorderBox ) { + val = elem[ offsetProp ]; + } } // Normalize "" and auto @@ -6425,6 +6626,13 @@ jQuery.extend( { "flexGrow": true, "flexShrink": true, "fontWeight": true, + "gridArea": true, + "gridColumn": true, + "gridColumnEnd": true, + "gridColumnStart": true, + "gridRow": true, + "gridRowEnd": true, + "gridRowStart": true, "lineHeight": true, "opacity": true, "order": true, @@ -6480,7 +6688,9 @@ jQuery.extend( { } // If a number was passed in, add the unit (except for certain CSS properties) - if ( type === "number" ) { + // The isCustomProp check can be removed in jQuery 4.0 when we only auto-append + // "px" to a few hardcoded values. + if ( type === "number" && !isCustomProp ) { value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" ); } @@ -6580,18 +6790,29 @@ jQuery.each( [ "height", "width" ], function( i, dimension ) { set: function( elem, value, extra ) { var matches, styles = getStyles( elem ), - isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box", - subtract = extra && boxModelAdjustment( - elem, - dimension, - extra, - isBorderBox, - styles - ); + + // Only read styles.position if the test has a chance to fail + // to avoid forcing a reflow. + scrollboxSizeBuggy = !support.scrollboxSize() && + styles.position === "absolute", + + // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-3991) + boxSizingNeeded = scrollboxSizeBuggy || extra, + isBorderBox = boxSizingNeeded && + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + subtract = extra ? + boxModelAdjustment( + elem, + dimension, + extra, + isBorderBox, + styles + ) : + 0; // Account for unreliable border-box dimensions by comparing offset* to computed and // faking a content-box to get border and padding (gh-3699) - if ( isBorderBox && support.scrollboxSize() === styles.position ) { + if ( isBorderBox && scrollboxSizeBuggy ) { subtract -= Math.ceil( elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - parseFloat( styles[ dimension ] ) - @@ -6759,9 +6980,9 @@ Tween.propHooks = { // Use .style if available and use plain properties where available. if ( jQuery.fx.step[ tween.prop ] ) { jQuery.fx.step[ tween.prop ]( tween ); - } else if ( tween.elem.nodeType === 1 && - ( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null || - jQuery.cssHooks[ tween.prop ] ) ) { + } else if ( tween.elem.nodeType === 1 && ( + jQuery.cssHooks[ tween.prop ] || + tween.elem.style[ finalPropName( tween.prop ) ] != null ) ) { jQuery.style( tween.elem, tween.prop, tween.now + tween.unit ); } else { tween.elem[ tween.prop ] = tween.now; @@ -8468,6 +8689,10 @@ jQuery.param = function( a, traditional ) { encodeURIComponent( value == null ? "" : value ); }; + if ( a == null ) { + return ""; + } + // If an array was passed in, assume that it is an array of form elements. if ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { @@ -8970,12 +9195,14 @@ jQuery.extend( { if ( !responseHeaders ) { responseHeaders = {}; while ( ( match = rheaders.exec( responseHeadersString ) ) ) { - responseHeaders[ match[ 1 ].toLowerCase() ] = match[ 2 ]; + responseHeaders[ match[ 1 ].toLowerCase() + " " ] = + ( responseHeaders[ match[ 1 ].toLowerCase() + " " ] || [] ) + .concat( match[ 2 ] ); } } - match = responseHeaders[ key.toLowerCase() ]; + match = responseHeaders[ key.toLowerCase() + " " ]; } - return match == null ? null : match; + return match == null ? null : match.join( ", " ); }, // Raw string @@ -9364,7 +9591,7 @@ jQuery.each( [ "get", "post" ], function( i, method ) { } ); -jQuery._evalUrl = function( url ) { +jQuery._evalUrl = function( url, options ) { return jQuery.ajax( { url: url, @@ -9374,7 +9601,16 @@ jQuery._evalUrl = function( url ) { cache: true, async: false, global: false, - "throws": true + + // Only evaluate the response if it is successful (gh-4126) + // dataFilter is not invoked for failure responses, so using it instead + // of the default converter is kludgy but it works. + converters: { + "text script": function() {} + }, + dataFilter: function( response ) { + jQuery.globalEval( response, options ); + } } ); }; @@ -9657,24 +9893,21 @@ jQuery.ajaxPrefilter( "script", function( s ) { // Bind script tag hack transport jQuery.ajaxTransport( "script", function( s ) { - // This transport only deals with cross domain requests - if ( s.crossDomain ) { + // This transport only deals with cross domain or forced-by-attrs requests + if ( s.crossDomain || s.scriptAttrs ) { var script, callback; return { send: function( _, complete ) { - script = jQuery( "