├── .gitignore ├── Dockerfile ├── GAPS 2.py ├── LICENSE ├── PlexAccountData.py ├── README.md ├── app.ini ├── config.py ├── requirements.txt ├── static ├── css │ ├── input.css │ └── libraries │ │ ├── bootstrap-grid.css │ │ ├── bootstrap-grid.min.css │ │ ├── bootstrap-reboot.css │ │ ├── bootstrap-reboot.min.css │ │ ├── bootstrap.css │ │ ├── bootstrap.min.css │ │ ├── dataTables.bootstrap4.min.css │ │ ├── datatables.css │ │ └── datatables.min.css ├── images │ ├── Final.png │ ├── FinalGAPS.jpg │ ├── FinalGAPS.png │ ├── arrow-clockwise.svg │ ├── broken-bridge-2.jpg │ ├── collection-fill.svg │ ├── dead_end.png │ ├── exclamation-triangle.svg │ ├── final-1.svg │ ├── final-2.svg │ ├── final-gaps.svg │ ├── gaps.ico │ ├── gear.svg │ ├── info-circle.svg │ ├── list-ul.svg │ ├── mind_the_gap.png │ ├── rss.svg │ ├── sort_asc.png │ ├── sort_asc_disabled.png │ ├── sort_both.png │ ├── sort_desc.png │ ├── sort_desc_disabled.png │ └── wrench.svg └── js │ ├── libraries │ ├── bootstrap.bundle.js │ ├── bootstrap.bundle.min.js │ ├── datatables.js │ ├── datatables.min.js │ ├── handlebars-v4.7.6.js │ ├── handlebars-v4.7.6.min.js │ ├── jquery-3.4.1.js │ ├── jquery-3.4.1.min.js │ ├── sockjs-1.4.0.js │ ├── sockjs-1.4.0.min.js │ ├── stomp-2.3.3.js │ └── stomp-2.3.3.min.js │ └── modules │ └── plex-configuration.js ├── templates ├── about.html ├── configuration.html ├── emptyState.html ├── error.html ├── error │ └── 404.html ├── fragments │ ├── common.html │ └── header.html ├── index.html ├── libraries.html ├── recommended.html └── updates.html └── wsgi.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore compiled Python files 2 | *.pyc 3 | *.pyo 4 | *.pyd 5 | 6 | # Ignore the __pycache__ folder 7 | __pycache__/ -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Use an official Python runtime as a parent image 2 | FROM python:3.7-slim-buster 3 | 4 | # Set the working directory in the container to /app 5 | WORKDIR /app 6 | 7 | # Add the current directory contents into the container at /app 8 | ADD . /app 9 | 10 | # Update and install build dependencies 11 | RUN apt-get update && apt-get install -y \ 12 | build-essential \ 13 | libssl-dev \ 14 | libffi-dev \ 15 | python3-dev \ 16 | python3-pip 17 | 18 | # Install any needed packages specified in requirements.txt 19 | RUN pip install --no-cache-dir -r requirements.txt 20 | 21 | # Make port 80 available to the world outside this container 22 | EXPOSE 5000 23 | 24 | # Define environment variable 25 | ENV NAME World 26 | 27 | # Run app.py when the container launches 28 | CMD ["uwsgi", "--ini", "app.ini"] 29 | 30 | -------------------------------------------------------------------------------- /GAPS 2.py: -------------------------------------------------------------------------------- 1 | import json, webbrowser, requests 2 | from flask import Flask, render_template, request, jsonify 3 | from plexapi.myplex import MyPlexPinLogin, MyPlexAccount, PlexServer 4 | from PlexAccountData import PlexAccountData 5 | 6 | app = Flask(__name__) 7 | 8 | # Configuration 9 | app.config.from_pyfile('config.py') # Assuming you have a config.py file 10 | 11 | @app.context_processor 12 | def inject_page_display_names(): 13 | return dict(page_display_names=app.config['PAGE_DISPLAY_NAMES']) 14 | 15 | @app.route('/') 16 | def main_index(): 17 | return render_template('index.html') 18 | 19 | @app.route('/about') 20 | def about(): 21 | return render_template('about.html') 22 | 23 | @app.route('/configuration') 24 | def configuration(): 25 | return render_template('configuration.html') 26 | 27 | @app.route('/emptyState') 28 | def empty_state(): 29 | return render_template('emptyState.html') 30 | 31 | @app.route('/error') 32 | def error(): 33 | return render_template('error.html') 34 | 35 | @app.route('/libraries') 36 | def libraries(): 37 | if currentActiveServer.selected_server in stored_plexAccounts: 38 | libraries = currentActiveServer.libraries 39 | return render_template('libraries.html', plexServer=currentActiveServer.selected_server, libraries=libraries, currentActiveServer=currentActiveServer) 40 | 41 | # Handle case when PlexAccountData is not found 42 | return render_template('error.html', error=app.config['RESPONSE_MESSAGES']['data_not_found']) 43 | 44 | @app.route('/recommended') 45 | def recommended(): 46 | return render_template('recommended.html') 47 | 48 | @app.route('/updates') 49 | def updates(): 50 | return render_template('updates.html') 51 | 52 | @app.route('/testTmdbKey', methods=['POST']) 53 | def test_tmdb_key(): 54 | api_key = request.json.get('api_key') # Get the API key from the request body 55 | url = f"https://api.themoviedb.org/3/configuration?api_key={api_key}" # Include the API key in the URL 56 | 57 | response = requests.get(url) 58 | 59 | if response.status_code == 200: 60 | return {'message': app.config['RESPONSE_MESSAGES']['api_key_success']} 61 | else: 62 | return {'message': app.config['RESPONSE_MESSAGES']['api_key_failure'] + str(response.status_code)}, response.status_code 63 | 64 | @app.route('/saveTmdbKey', methods=['POST']) 65 | def save_tmdb_key(): 66 | # Extract data from request 67 | data = request.get_json() 68 | 69 | # Test the API key 70 | api_key = data.get('key') 71 | url = f"https://api.themoviedb.org/3/configuration?api_key={api_key}" 72 | response = requests.get(url) 73 | 74 | if response.status_code == 200: 75 | # If the API key is valid, save it and return a success message 76 | return {'message': app.config['RESPONSE_MESSAGES']['api_key_saved']} 77 | else: 78 | # If the API key is not valid, return an error message 79 | return {'message': app.config['RESPONSE_MESSAGES']['api_key_failure'] + str(response.status_code)}, response.status_code 80 | 81 | @app.route('/fetch_servers', methods=['POST']) 82 | def fetch_servers(): 83 | if globalPin.checkLogin(): # Assumes that checkLogin() returns True if the user is authenticated 84 | plex_data = PlexAccountData() # Create a new PlexAccountData object 85 | plex_account = MyPlexAccount(token=globalPin.token) 86 | username = plex_account.username # Get the username 87 | resources = [resource for resource in plex_account.resources() if resource.owned and resource.connections] 88 | servers = [f"{resource.name} ({resource.connections[0].address})" for resource in resources if resource.connections] 89 | 90 | # Store tokens in the dictionary 91 | for resource in resources: 92 | if resource.connections: 93 | server_name = f"{resource.name} ({resource.connections[0].address})" 94 | tokens[server_name] = globalPin.token 95 | plex_data.add_token(server_name, globalPin.token) 96 | 97 | plex_data.set_servers(servers) 98 | 99 | # Store the PlexAccountData object in the array 100 | plex_data_array.append(plex_data) 101 | 102 | # Return the JSON response with servers and token 103 | return jsonify(servers=servers, token=globalPin.token) 104 | else: 105 | return jsonify({'message': 'User is not authenticated', 'servers': [], 'token': None}) 106 | 107 | globalPin = None 108 | @app.route('/authenticate_plex_acc', methods=['POST']) 109 | def authenticate_plex_acc(): 110 | global globalPin 111 | globalPin = MyPlexPinLogin(oauth=True) 112 | oauth_url = globalPin.oauthUrl() 113 | return jsonify({'oauth_url': oauth_url}) 114 | 115 | @app.route('/fetch_libraries/') 116 | def fetch_libraries(serverName): 117 | # Find the PlexAccountData object with the matching serverName 118 | plex_data = next((data for data in plex_data_array if serverName in data.tokens), None) 119 | 120 | if plex_data is None: 121 | #print("PlexAccountData not found") 122 | return {'message': app.config['RESPONSE_MESSAGES']['plex_data_not_found']} 123 | 124 | token = plex_data.tokens.get(serverName) 125 | 126 | #print("Token: " + token) 127 | plex_account = MyPlexAccount(token=token) 128 | 129 | server = None 130 | for resource in plex_account.resources(): 131 | if f"{resource.name} ({resource.connections[0].address})" == serverName: 132 | #print(f"Attempting to connect to server {serverName}") 133 | server = resource.connect() 134 | break 135 | 136 | if server is None: 137 | #print("Server not found") 138 | return jsonify(error=app.config['RESPONSE_MESSAGES']['server_not_found']), 404 139 | 140 | libraries = [section.title for section in server.library.sections()] 141 | 142 | #print(f"Libraries: {libraries}") 143 | 144 | plex_data.set_libraries(libraries) 145 | 146 | # Store the libraries in a global variable 147 | global stored_libraries 148 | stored_libraries[serverName] = libraries 149 | 150 | # Return the JSON response 151 | return jsonify(libraries=libraries, token=token) 152 | 153 | 154 | @app.route('/save_plex_data', methods=['POST']) 155 | def save_plex_data(): 156 | try: 157 | # Extract data from request 158 | data = request.get_json() 159 | selectedServer = data.get('server') 160 | token = data.get('token') 161 | libraries = data.get('libraries') 162 | 163 | # Create a new PlexAccountData object 164 | plex_data = PlexAccountData() 165 | 166 | # Update the PlexAccountData object with the selected server information 167 | plex_data.set_selected_server(selectedServer) 168 | plex_data.set_token(token) 169 | 170 | # Fetch the libraries for the selected server 171 | fetch_libraries(selectedServer) 172 | 173 | # Get the libraries from the response 174 | libraries = stored_libraries #this makes the key for libraries be the server name: stored_libraries[serverName] = libraries 175 | 176 | # Update the PlexAccountData object with the libraries 177 | plex_data.set_libraries(libraries) 178 | 179 | # Store the PlexAccountData object in the dictionary with the server name as the key 180 | stored_plexAccounts[selectedServer] = plex_data 181 | 182 | # Set the selected_server and token when saving 183 | currentActiveServer.selected_server = selectedServer 184 | currentActiveServer.token = token 185 | currentActiveServer.libraries = libraries 186 | 187 | #print('Calling get_movies_from_plex_library') 188 | #get_movies_from_plex_library() 189 | 190 | return jsonify(result='Success') 191 | except Exception as e: 192 | return jsonify(result='Error', error=str(e)) 193 | 194 | @app.route('/get_active_server', methods=['GET']) 195 | def get_active_server(): 196 | try: 197 | global currentActiveServer 198 | #print(f"get_active_server libraries: {currentActiveServer.libraries}") 199 | if currentActiveServer: 200 | return jsonify(server=currentActiveServer.selected_server, token=currentActiveServer.token, libraries=currentActiveServer.libraries) 201 | else: 202 | return jsonify(error='No active server found') 203 | except Exception as e: 204 | return jsonify(error=str(e)) 205 | 206 | @app.route('/get_movies', methods=['GET']) 207 | def get_movies_from_plex_library(): 208 | global moviesFromSelectedLibrary # Declare this variable as global 209 | 210 | try: 211 | # Retrieve the library name from the query parameter 212 | library_name = request.args.get('library_name') 213 | 214 | # Check if data for the library already exists in global variable 215 | if library_name in moviesFromSelectedLibrary: 216 | return jsonify(movies=moviesFromSelectedLibrary[library_name]) 217 | 218 | # Connect to the Plex account using the token 219 | plex_account = MyPlexAccount(token=currentActiveServer.token) 220 | 221 | # Find the server resource associated with the selected server 222 | server_resource = None 223 | resources = [resource for resource in plex_account.resources() if resource.owned] 224 | for resource in resources: 225 | if f"{resource.name} ({resource.connections[0].address})" == currentActiveServer.selected_server: 226 | #print(f"resource: {resource.name} ({resource.connections[0].address}) == {currentActiveServer.selected_server}") 227 | server_resource = resource 228 | break 229 | 230 | if server_resource is None: 231 | return jsonify(error=app.config['RESPONSE_MESSAGES']['server_resource_not_found']) 232 | 233 | # Connect to the server using the server resource 234 | server = server_resource.connect() 235 | 236 | # Get the library by name 237 | library = server.library.section(library_name) 238 | 239 | # Retrieve all movies from the library 240 | movies = library.search(libtype='movie') 241 | 242 | # Extract movie data 243 | movie_data = [] 244 | for movie in movies: 245 | imdb_id = None 246 | tmdb_id = None 247 | tvdb_id = None 248 | 249 | for guid in movie.guids: 250 | if 'imdb' in guid.id: 251 | imdb_id = guid.id.replace('imdb://', '') 252 | elif 'tmdb' in guid.id: 253 | tmdb_id = guid.id.replace('tmdb://', '') 254 | elif 'tvdb' in guid.id: 255 | tvdb_id = guid.id.replace('tvdb://', '') 256 | 257 | movie_info = { 258 | 'name': movie.title, 259 | 'year': movie.year, 260 | 'overview': movie.summary, 261 | 'posterUrl': movie.posterUrl, 262 | 'imdbId': imdb_id, 263 | 'tmdbId': tmdb_id, 264 | 'tvdbId': tvdb_id 265 | } 266 | movie_data.append(movie_info) 267 | 268 | # store the data globally so if the html page is refreshed, 269 | # it doesnt have to make another request to get the data 270 | # (do this instead of storing locally as thats limited to 5 MB) 271 | moviesFromSelectedLibrary[library_name] = movie_data 272 | 273 | return jsonify(movies=movie_data) 274 | 275 | except Exception as e: 276 | return jsonify(error=str(e)) 277 | 278 | # Uses themoviedb api to get recommended movies, removes movies from the recommended list already in the library 279 | @app.route('/recommendations', methods=['GET']) 280 | def get_recommendations(): 281 | global global_recommendations 282 | movie_id = request.args.get('movieId', default = 11, type = int) 283 | api_key = request.args.get('apiKey', default = "", type = str) 284 | 285 | url = f"{app.config['TMDB_BASE_URL']}/movie/{movie_id}/recommendations" 286 | params = {"api_key": api_key} 287 | 288 | response = requests.get(url, params=params) 289 | 290 | if response.status_code != 200: 291 | return {'message': 'API request failed with status code ' + str(response.status_code)}, response.status_code 292 | 293 | data = response.json() 294 | 295 | base_image_url = "https://image.tmdb.org/t/p/w500" 296 | """ recommendations = [{'id': i['id'], 297 | 'title': i['title'], 298 | 'release_date': i['release_date'], 299 | 'overview': i['overview'], 300 | 'poster_path': base_image_url + i['poster_path']} for i in data['results']] """ 301 | recommendations = [{'tmdbId': i['id'], 302 | 'name': i['title'], 303 | 'year': i['release_date'][:4], 304 | 'posterUrl': base_image_url + i['poster_path'], 305 | 'overview': i['overview']} for i in data['results']] 306 | 307 | global_recommendations = recommendations 308 | 309 | return jsonify(recommendations) 310 | 311 | # Retrieve the recommended movies from python storage to use on the recommended page 312 | @app.route("/get_recommendated_movies", methods=["GET"]) 313 | def get_recommendated_movies(): 314 | return jsonify(global_recommendations) 315 | 316 | stored_libraries = {} #dictionary to get the libraries later. Key is the Plex serverName 317 | stored_plexAccounts = {} 318 | tokens = {} 319 | # Create an array to store PlexAccountData objects 320 | plex_data_array = [] 321 | # Create an instance of PlexAccountData as a global variable 322 | currentActiveServer = PlexAccountData() 323 | moviesFromSelectedLibrary = {} 324 | 325 | if __name__ == '__main__': 326 | app.run(host='0.0.0.0', port=5000, debug=False) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 primetime43 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /PlexAccountData.py: -------------------------------------------------------------------------------- 1 | class PlexAccountData: 2 | def __init__(self): 3 | self.tokens = {} 4 | self.servers = [] 5 | self.libraries = [] 6 | self.token = None 7 | self.selected_server = None 8 | 9 | def add_token(self, server_name, token): 10 | self.tokens[server_name] = token 11 | 12 | def set_servers(self, servers): 13 | self.servers = servers 14 | 15 | def set_libraries(self, libraries): 16 | self.libraries = libraries 17 | 18 | def set_token(self, token): 19 | self.token = token 20 | 21 | def set_selected_server(self, server): 22 | self.selected_server = server 23 | 24 | def set_selected_library(self, library): 25 | self.selected_library = library -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GAPS 2 2 | 3 | Table of Contents 4 | 5 | - [GAPS 2](#gaps-2) 6 | - [Features](#features) 7 | - [TODO](#todo) 8 | - [Installation](#installation) 9 | - [Docker](#docker) 10 | - [Images of v1.0.0](#images-of-v100) 11 | - [Development](#development) 12 | 13 | # GAPS 2 14 | 15 | GAPS 2 is a rewrite of the original [GAPS](https://github.com/JasonHHouse/gaps) project, now written in Python instead of Java. GAPS (Gaps A Plex Server) finds movies you're missing in your Plex Server. It's a great way to find additional movies that you might be interested in based on collections from movies in your Plex Server. 16 | 17 | The GAPS 2 project aims to bring the same functionality with the simplicity and versatility of Python. 18 | 19 | ## Features 20 | 21 | - Finds missing movies in Plex libraries based on collections 22 | - Lists missing and existing movies within collections 23 | - Easy to use interface 24 | - Now written in Python for easy deployment and updates 25 | 26 | ## TODO 27 | 28 | - [x] Add the back end functionality 29 | - [x] Fix/finish overall functionality that's missing from the original code 30 | - [ ] Need to add entire library recommendations 31 | - [ ] Need to remove existing movies in plex library from recommendations 32 | - [ ] Fix bugs & add updates to refactor code for simplicity 33 | 34 | ## Installation 35 | 36 | Run the python file (if running from source code) or run the exe from [releases](https://github.com/primetime43/GAPS-2/releases) and it will be locally hosted at http://127.0.0.1:5000/ or a LAN IP Address 37 | 38 | **Command on Windows for creating an exe out of the entire project from the main python file** 39 | ``` 40 | pyinstaller --onefile --add-data "config.py;." --add-data "templates;templates" --add-data "static;static" "GAPS 2.py" 41 | ``` 42 | 43 | **Install the required packages** 44 | ``` 45 | pip install -r requirements.txt 46 | ``` 47 | Requires Python 3.7 or newer 48 | 49 | # Docker 50 | You can pull the docker image from [here](https://hub.docker.com/repository/docker/primetime43/gaps-2/general) 51 | 52 | or do it manually with the steps below. 53 | 54 | To build the docker image, run this command in the latest downloaded tag's source code directory 55 | ``` 56 | docker build -t gaps-2 . 57 | ``` 58 | 59 | Once the image is created, run the image in a container using this command. If you want to modify which port to run on, you'll need to modify the wsgi.py file 60 | ``` 61 | docker run -p 5000:5000 gaps-2 62 | ``` 63 | 64 | ## Images of v1.0.0 65 | ![image](https://github.com/primetime43/GAPS-2/assets/12754111/a9ae50f3-5a9a-4f93-bfdb-a90b6783a47f) 66 | ![image](https://github.com/primetime43/GAPS-2/assets/12754111/4466e0bf-70be-4ab7-b5c5-02140c31cae9) 67 | ![image](https://github.com/primetime43/GAPS-2/assets/12754111/be56426e-7c5f-492a-a852-04e4fc076bd9) 68 | 69 | ## Development 70 | 71 | GAPS 2 is developed by [primetime43](https://github.com/primetime43). Contributions are welcome! Feel free to report bugs, suggest features, or contribute to the code. 72 | 73 | Please report any bugs encountered. You can see a log output in the python console window that is opened when running the exe. 74 | -------------------------------------------------------------------------------- /app.ini: -------------------------------------------------------------------------------- 1 | [uwsgi] 2 | module = wsgi:app 3 | http = :5000 4 | master = true 5 | processes = 5 6 | 7 | socket = app.sock 8 | chmod-socket = 660 9 | vacuum = true 10 | 11 | die-on-term = true 12 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | # config.py 2 | 3 | # Define the page display names 4 | PAGE_DISPLAY_NAMES = { 5 | 'libraries': 'Libraries', 6 | 'recommended': 'Recommended', 7 | 'configuration': 'Configuration', 8 | 'updates': 'Updates', 9 | 'about': 'About' 10 | } 11 | 12 | # Define the base URLs 13 | TMDB_BASE_URL = "https://api.themoviedb.org/3" 14 | TMDB_IMAGE_BASE_URL = "https://image.tmdb.org/t/p/w500" 15 | 16 | # Define the response messages 17 | RESPONSE_MESSAGES = { 18 | "api_key_success": "API key is working!", 19 | "api_key_failure": "Failed to connect to API, status code: ", 20 | "data_not_found": "Data not found", 21 | "plex_account_error": "Could not log in to Plex account", 22 | "plex_data_not_found": "PlexAccountData not found", 23 | "server_not_found": "Server not found", 24 | "server_resource_not_found": "Server resource not found", 25 | "invalid_token": "Invalid token", 26 | "api_key_saved":"Successfully saved API key" 27 | } -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Flask 2 | requests 3 | plexapi 4 | uwsgi -------------------------------------------------------------------------------- /static/css/input.css: -------------------------------------------------------------------------------- 1 | /* Change the white to any color ;) */ 2 | .top-margin { 3 | margin-top: 16px; 4 | } 5 | 6 | .all-padding { 7 | padding: 16px; 8 | } 9 | 10 | .gaps-hide { 11 | display: none; 12 | } 13 | 14 | .thumbnail { 15 | max-width: 96px; 16 | height: auto; 17 | } 18 | 19 | .bottom-margin { 20 | margin-top: 16px; 21 | } 22 | 23 | .word-break { 24 | word-break: break-all; 25 | } 26 | 27 | .center { 28 | display: block; 29 | margin-left: auto; 30 | margin-right: auto; 31 | width: 50%; 32 | } 33 | 34 | .center-text { 35 | text-align: center; 36 | } 37 | 38 | .center-parent { 39 | display: flex; 40 | flex-direction: column; 41 | justify-content: center; 42 | } 43 | 44 | .nav-button-padding { 45 | padding-top: 8px; 46 | display: block; 47 | width: 96px; 48 | } 49 | 50 | .nav-anchor-spacing { 51 | display: block; 52 | width: 96px; 53 | } 54 | 55 | .icon { 56 | width: 48px; 57 | height: 48px; 58 | margin-bottom: 4px; 59 | display: block; 60 | margin-left: auto; 61 | margin-right: auto; 62 | } 63 | 64 | .default { 65 | background-color: #fafafa; /* defines the background color of the image */ 66 | } 67 | 68 | .activePage { 69 | background-color: #00bc8c; /* defines the background color of the image */ 70 | } 71 | 72 | .long-text { 73 | overflow: hidden; 74 | text-overflow: ellipsis; 75 | display: -webkit-box; 76 | -webkit-line-clamp: 3; /* number of lines to show */ 77 | -webkit-box-orient: vertical; 78 | } 79 | 80 | hr.thin { 81 | height: 1px; 82 | border: 0; 83 | color: #333; 84 | background-color: #333; 85 | width: 95%; 86 | } 87 | 88 | .white-line { 89 | width: 75%; 90 | height: 4px; 91 | background-color: white; 92 | margin-top: 10px; 93 | margin-bottom: 10px; 94 | } -------------------------------------------------------------------------------- /static/css/libraries/bootstrap-reboot.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Reboot v4.4.1 (https://getbootstrap.com/) 3 | * Copyright 2011-2019 The Bootstrap Authors 4 | * Copyright 2011-2019 Twitter, Inc. 5 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 6 | * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md) 7 | */ 8 | *, 9 | *::before, 10 | *::after { 11 | box-sizing: border-box; 12 | } 13 | 14 | html { 15 | font-family: sans-serif; 16 | line-height: 1.15; 17 | -webkit-text-size-adjust: 100%; 18 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 19 | } 20 | 21 | article, aside, figcaption, figure, footer, header, hgroup, main, nav, section { 22 | display: block; 23 | } 24 | 25 | body { 26 | margin: 0; 27 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; 28 | font-size: 1rem; 29 | font-weight: 400; 30 | line-height: 1.5; 31 | color: #212529; 32 | text-align: left; 33 | background-color: #fff; 34 | } 35 | 36 | [tabindex="-1"]:focus:not(:focus-visible) { 37 | outline: 0 !important; 38 | } 39 | 40 | hr { 41 | box-sizing: content-box; 42 | height: 0; 43 | overflow: visible; 44 | } 45 | 46 | h1, h2, h3, h4, h5, h6 { 47 | margin-top: 0; 48 | margin-bottom: 0.5rem; 49 | } 50 | 51 | p { 52 | margin-top: 0; 53 | margin-bottom: 1rem; 54 | } 55 | 56 | abbr[title], 57 | abbr[data-original-title] { 58 | text-decoration: underline; 59 | -webkit-text-decoration: underline dotted; 60 | text-decoration: underline dotted; 61 | cursor: help; 62 | border-bottom: 0; 63 | -webkit-text-decoration-skip-ink: none; 64 | text-decoration-skip-ink: none; 65 | } 66 | 67 | address { 68 | margin-bottom: 1rem; 69 | font-style: normal; 70 | line-height: inherit; 71 | } 72 | 73 | ol, 74 | ul, 75 | dl { 76 | margin-top: 0; 77 | margin-bottom: 1rem; 78 | } 79 | 80 | ol ol, 81 | ul ul, 82 | ol ul, 83 | ul ol { 84 | margin-bottom: 0; 85 | } 86 | 87 | dt { 88 | font-weight: 700; 89 | } 90 | 91 | dd { 92 | margin-bottom: .5rem; 93 | margin-left: 0; 94 | } 95 | 96 | blockquote { 97 | margin: 0 0 1rem; 98 | } 99 | 100 | b, 101 | strong { 102 | font-weight: bolder; 103 | } 104 | 105 | small { 106 | font-size: 80%; 107 | } 108 | 109 | sub, 110 | sup { 111 | position: relative; 112 | font-size: 75%; 113 | line-height: 0; 114 | vertical-align: baseline; 115 | } 116 | 117 | sub { 118 | bottom: -.25em; 119 | } 120 | 121 | sup { 122 | top: -.5em; 123 | } 124 | 125 | a { 126 | color: #007bff; 127 | text-decoration: none; 128 | background-color: transparent; 129 | } 130 | 131 | a:hover { 132 | color: #0056b3; 133 | text-decoration: underline; 134 | } 135 | 136 | a:not([href]) { 137 | color: inherit; 138 | text-decoration: none; 139 | } 140 | 141 | a:not([href]):hover { 142 | color: inherit; 143 | text-decoration: none; 144 | } 145 | 146 | pre, 147 | code, 148 | kbd, 149 | samp { 150 | font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; 151 | font-size: 1em; 152 | } 153 | 154 | pre { 155 | margin-top: 0; 156 | margin-bottom: 1rem; 157 | overflow: auto; 158 | } 159 | 160 | figure { 161 | margin: 0 0 1rem; 162 | } 163 | 164 | img { 165 | vertical-align: middle; 166 | border-style: none; 167 | } 168 | 169 | svg { 170 | overflow: hidden; 171 | vertical-align: middle; 172 | } 173 | 174 | table { 175 | border-collapse: collapse; 176 | } 177 | 178 | caption { 179 | padding-top: 0.75rem; 180 | padding-bottom: 0.75rem; 181 | color: #6c757d; 182 | text-align: left; 183 | caption-side: bottom; 184 | } 185 | 186 | th { 187 | text-align: inherit; 188 | } 189 | 190 | label { 191 | display: inline-block; 192 | margin-bottom: 0.5rem; 193 | } 194 | 195 | button { 196 | border-radius: 0; 197 | } 198 | 199 | button:focus { 200 | outline: 1px dotted; 201 | outline: 5px auto -webkit-focus-ring-color; 202 | } 203 | 204 | input, 205 | button, 206 | select, 207 | optgroup, 208 | textarea { 209 | margin: 0; 210 | font-family: inherit; 211 | font-size: inherit; 212 | line-height: inherit; 213 | } 214 | 215 | button, 216 | input { 217 | overflow: visible; 218 | } 219 | 220 | button, 221 | select { 222 | text-transform: none; 223 | } 224 | 225 | select { 226 | word-wrap: normal; 227 | } 228 | 229 | button, 230 | [type="button"], 231 | [type="reset"], 232 | [type="submit"] { 233 | -webkit-appearance: button; 234 | } 235 | 236 | button:not(:disabled), 237 | [type="button"]:not(:disabled), 238 | [type="reset"]:not(:disabled), 239 | [type="submit"]:not(:disabled) { 240 | cursor: pointer; 241 | } 242 | 243 | button::-moz-focus-inner, 244 | [type="button"]::-moz-focus-inner, 245 | [type="reset"]::-moz-focus-inner, 246 | [type="submit"]::-moz-focus-inner { 247 | padding: 0; 248 | border-style: none; 249 | } 250 | 251 | input[type="radio"], 252 | input[type="checkbox"] { 253 | box-sizing: border-box; 254 | padding: 0; 255 | } 256 | 257 | input[type="date"], 258 | input[type="time"], 259 | input[type="datetime-local"], 260 | input[type="month"] { 261 | -webkit-appearance: listbox; 262 | } 263 | 264 | textarea { 265 | overflow: auto; 266 | resize: vertical; 267 | } 268 | 269 | fieldset { 270 | min-width: 0; 271 | padding: 0; 272 | margin: 0; 273 | border: 0; 274 | } 275 | 276 | legend { 277 | display: block; 278 | width: 100%; 279 | max-width: 100%; 280 | padding: 0; 281 | margin-bottom: .5rem; 282 | font-size: 1.5rem; 283 | line-height: inherit; 284 | color: inherit; 285 | white-space: normal; 286 | } 287 | 288 | progress { 289 | vertical-align: baseline; 290 | } 291 | 292 | [type="number"]::-webkit-inner-spin-button, 293 | [type="number"]::-webkit-outer-spin-button { 294 | height: auto; 295 | } 296 | 297 | [type="search"] { 298 | outline-offset: -2px; 299 | -webkit-appearance: none; 300 | } 301 | 302 | [type="search"]::-webkit-search-decoration { 303 | -webkit-appearance: none; 304 | } 305 | 306 | ::-webkit-file-upload-button { 307 | font: inherit; 308 | -webkit-appearance: button; 309 | } 310 | 311 | output { 312 | display: inline-block; 313 | } 314 | 315 | summary { 316 | display: list-item; 317 | cursor: pointer; 318 | } 319 | 320 | template { 321 | display: none; 322 | } 323 | 324 | [hidden] { 325 | display: none !important; 326 | } 327 | /*# sourceMappingURL=bootstrap-reboot.css.map */ -------------------------------------------------------------------------------- /static/css/libraries/bootstrap-reboot.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Reboot v4.4.1 (https://getbootstrap.com/) 3 | * Copyright 2011-2019 The Bootstrap Authors 4 | * Copyright 2011-2019 Twitter, Inc. 5 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 6 | * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md) 7 | */*,*::before,*::after{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:rgba(0,0,0,0)}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus:not(:focus-visible){outline:0 !important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[title],abbr[data-original-title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul,dl{margin-top:0;margin-bottom:1rem}ol ol,ul ul,ol ul,ul ol{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]){color:inherit;text-decoration:none}a:not([href]):hover{color:inherit;text-decoration:none}pre,code,kbd,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}input,button,select,optgroup,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}select{word-wrap:normal}button,[type="button"],[type="reset"],[type="submit"]{-webkit-appearance:button}button:not(:disabled),[type="button"]:not(:disabled),[type="reset"]:not(:disabled),[type="submit"]:not(:disabled){cursor:pointer}button::-moz-focus-inner,[type="button"]::-moz-focus-inner,[type="reset"]::-moz-focus-inner,[type="submit"]::-moz-focus-inner{padding:0;border-style:none}input[type="radio"],input[type="checkbox"]{box-sizing:border-box;padding:0}input[type="date"],input[type="time"],input[type="datetime-local"],input[type="month"]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type="number"]::-webkit-inner-spin-button,[type="number"]::-webkit-outer-spin-button{height:auto}[type="search"]{outline-offset:-2px;-webkit-appearance:none}[type="search"]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none !important} -------------------------------------------------------------------------------- /static/css/libraries/dataTables.bootstrap4.min.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Table styles 3 | */ 4 | table.dataTable { 5 | width: 100%; 6 | margin: 0 auto; 7 | clear: both; 8 | border-collapse: separate; 9 | border-spacing: 0; 10 | /* 11 | * Header and footer styles 12 | */ 13 | /* 14 | * Body styles 15 | */ 16 | } 17 | 18 | table.dataTable thead th, 19 | table.dataTable tfoot th { 20 | font-weight: bold; 21 | } 22 | 23 | table.dataTable thead th, 24 | table.dataTable thead td { 25 | padding: 10px 18px; 26 | border-bottom: 1px solid #111111; 27 | } 28 | 29 | table.dataTable thead th:active, 30 | table.dataTable thead td:active { 31 | outline: none; 32 | } 33 | 34 | table.dataTable tfoot th, 35 | table.dataTable tfoot td { 36 | padding: 10px 18px 6px 18px; 37 | border-top: 1px solid #111111; 38 | } 39 | 40 | table.dataTable thead .sorting, 41 | table.dataTable thead .sorting_asc, 42 | table.dataTable thead .sorting_desc, 43 | table.dataTable thead .sorting_asc_disabled, 44 | table.dataTable thead .sorting_desc_disabled { 45 | cursor: pointer; 46 | *cursor: hand; 47 | background-repeat: no-repeat; 48 | background-position: center right; 49 | } 50 | 51 | table.dataTable thead .sorting { 52 | background-image: url("../../images/sort_both.png"); 53 | } 54 | 55 | table.dataTable thead .sorting_asc { 56 | background-image: url("../../images/sort_asc.png"); 57 | } 58 | 59 | table.dataTable thead .sorting_desc { 60 | background-image: url("../../images/sort_desc.png"); 61 | } 62 | 63 | table.dataTable thead .sorting_asc_disabled { 64 | background-image: url("../../images/sort_asc_disabled.png"); 65 | } 66 | 67 | table.dataTable thead .sorting_desc_disabled { 68 | background-image: url("../../images/sort_desc_disabled.png"); 69 | } 70 | 71 | table.dataTable tbody tr { 72 | background-color: #383838; 73 | } 74 | 75 | table.dataTable tbody tr.selected { 76 | background-color: #b0bed9; 77 | } 78 | 79 | table.dataTable tbody th, 80 | table.dataTable tbody td { 81 | padding: 8px 10px; 82 | } 83 | 84 | table.dataTable.row-border tbody th, table.dataTable.row-border tbody td, table.dataTable.display tbody th, table.dataTable.display tbody td { 85 | border-top: 1px solid #dddddd; 86 | } 87 | 88 | table.dataTable.row-border tbody tr:first-child th, 89 | table.dataTable.row-border tbody tr:first-child td, table.dataTable.display tbody tr:first-child th, 90 | table.dataTable.display tbody tr:first-child td { 91 | border-top: none; 92 | } 93 | 94 | table.dataTable.cell-border tbody th, table.dataTable.cell-border tbody td { 95 | border-top: 1px solid #dddddd; 96 | border-right: 1px solid #dddddd; 97 | } 98 | 99 | table.dataTable.cell-border tbody tr th:first-child, 100 | table.dataTable.cell-border tbody tr td:first-child { 101 | border-left: 1px solid #dddddd; 102 | } 103 | 104 | table.dataTable.cell-border tbody tr:first-child th, 105 | table.dataTable.cell-border tbody tr:first-child td { 106 | border-top: none; 107 | } 108 | 109 | table.dataTable.stripe tbody tr.odd, table.dataTable.display tbody tr.odd { 110 | background-color: #363636; 111 | } 112 | 113 | table.dataTable.stripe tbody tr.odd.selected, table.dataTable.display tbody tr.odd.selected { 114 | background-color: #abb9d3; 115 | } 116 | 117 | table.dataTable.hover tbody tr:hover, table.dataTable.display tbody tr:hover { 118 | background-color: #353535; 119 | } 120 | 121 | table.dataTable.hover tbody tr:hover.selected, table.dataTable.display tbody tr:hover.selected { 122 | background-color: #a9b7d1; 123 | } 124 | 125 | table.dataTable.order-column tbody tr > .sorting_1, 126 | table.dataTable.order-column tbody tr > .sorting_2, 127 | table.dataTable.order-column tbody tr > .sorting_3, table.dataTable.display tbody tr > .sorting_1, 128 | table.dataTable.display tbody tr > .sorting_2, 129 | table.dataTable.display tbody tr > .sorting_3 { 130 | background-color: #363636; 131 | } 132 | 133 | table.dataTable.order-column tbody tr.selected > .sorting_1, 134 | table.dataTable.order-column tbody tr.selected > .sorting_2, 135 | table.dataTable.order-column tbody tr.selected > .sorting_3, table.dataTable.display tbody tr.selected > .sorting_1, 136 | table.dataTable.display tbody tr.selected > .sorting_2, 137 | table.dataTable.display tbody tr.selected > .sorting_3 { 138 | background-color: #acbad4; 139 | } 140 | 141 | table.dataTable.display tbody tr.odd > .sorting_1, table.dataTable.order-column.stripe tbody tr.odd > .sorting_1 { 142 | background-color: #343434; 143 | } 144 | 145 | table.dataTable.display tbody tr.odd > .sorting_2, table.dataTable.order-column.stripe tbody tr.odd > .sorting_2 { 146 | background-color: #353535; 147 | } 148 | 149 | table.dataTable.display tbody tr.odd > .sorting_3, table.dataTable.order-column.stripe tbody tr.odd > .sorting_3 { 150 | background-color: #353535; 151 | } 152 | 153 | table.dataTable.display tbody tr.odd.selected > .sorting_1, table.dataTable.order-column.stripe tbody tr.odd.selected > .sorting_1 { 154 | background-color: #a6b3cd; 155 | } 156 | 157 | table.dataTable.display tbody tr.odd.selected > .sorting_2, table.dataTable.order-column.stripe tbody tr.odd.selected > .sorting_2 { 158 | background-color: #a7b5ce; 159 | } 160 | 161 | table.dataTable.display tbody tr.odd.selected > .sorting_3, table.dataTable.order-column.stripe tbody tr.odd.selected > .sorting_3 { 162 | background-color: #a9b6d0; 163 | } 164 | 165 | table.dataTable.display tbody tr.even > .sorting_1, table.dataTable.order-column.stripe tbody tr.even > .sorting_1 { 166 | background-color: #363636; 167 | } 168 | 169 | table.dataTable.display tbody tr.even > .sorting_2, table.dataTable.order-column.stripe tbody tr.even > .sorting_2 { 170 | background-color: #373737; 171 | } 172 | 173 | table.dataTable.display tbody tr.even > .sorting_3, table.dataTable.order-column.stripe tbody tr.even > .sorting_3 { 174 | background-color: #373737; 175 | } 176 | 177 | table.dataTable.display tbody tr.even.selected > .sorting_1, table.dataTable.order-column.stripe tbody tr.even.selected > .sorting_1 { 178 | background-color: #acbad4; 179 | } 180 | 181 | table.dataTable.display tbody tr.even.selected > .sorting_2, table.dataTable.order-column.stripe tbody tr.even.selected > .sorting_2 { 182 | background-color: #adbbd6; 183 | } 184 | 185 | table.dataTable.display tbody tr.even.selected > .sorting_3, table.dataTable.order-column.stripe tbody tr.even.selected > .sorting_3 { 186 | background-color: #afbdd8; 187 | } 188 | 189 | table.dataTable.display tbody tr:hover > .sorting_1, table.dataTable.order-column.hover tbody tr:hover > .sorting_1 { 190 | background-color: #333333; 191 | } 192 | 193 | table.dataTable.display tbody tr:hover > .sorting_2, table.dataTable.order-column.hover tbody tr:hover > .sorting_2 { 194 | background-color: #333333; 195 | } 196 | 197 | table.dataTable.display tbody tr:hover > .sorting_3, table.dataTable.order-column.hover tbody tr:hover > .sorting_3 { 198 | background-color: #343434; 199 | } 200 | 201 | table.dataTable.display tbody tr:hover.selected > .sorting_1, table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_1 { 202 | background-color: #a1aec7; 203 | } 204 | 205 | table.dataTable.display tbody tr:hover.selected > .sorting_2, table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_2 { 206 | background-color: #a2afc8; 207 | } 208 | 209 | table.dataTable.display tbody tr:hover.selected > .sorting_3, table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_3 { 210 | background-color: #a4b2cb; 211 | } 212 | 213 | table.dataTable.no-footer { 214 | border-bottom: 1px solid #111111; 215 | } 216 | 217 | table.dataTable.nowrap th, table.dataTable.nowrap td { 218 | white-space: nowrap; 219 | } 220 | 221 | table.dataTable.compact thead th, 222 | table.dataTable.compact thead td { 223 | padding: 4px 17px 4px 4px; 224 | } 225 | 226 | table.dataTable.compact tfoot th, 227 | table.dataTable.compact tfoot td { 228 | padding: 4px; 229 | } 230 | 231 | table.dataTable.compact tbody th, 232 | table.dataTable.compact tbody td { 233 | padding: 4px; 234 | } 235 | 236 | table.dataTable th.dt-left, 237 | table.dataTable td.dt-left { 238 | text-align: left; 239 | } 240 | 241 | table.dataTable th.dt-center, 242 | table.dataTable td.dt-center, 243 | table.dataTable td.dataTables_empty { 244 | text-align: center; 245 | } 246 | 247 | table.dataTable th.dt-right, 248 | table.dataTable td.dt-right { 249 | text-align: right; 250 | } 251 | 252 | table.dataTable th.dt-justify, 253 | table.dataTable td.dt-justify { 254 | text-align: justify; 255 | } 256 | 257 | table.dataTable th.dt-nowrap, 258 | table.dataTable td.dt-nowrap { 259 | white-space: nowrap; 260 | } 261 | 262 | table.dataTable thead th.dt-head-left, 263 | table.dataTable thead td.dt-head-left, 264 | table.dataTable tfoot th.dt-head-left, 265 | table.dataTable tfoot td.dt-head-left { 266 | text-align: left; 267 | } 268 | 269 | table.dataTable thead th.dt-head-center, 270 | table.dataTable thead td.dt-head-center, 271 | table.dataTable tfoot th.dt-head-center, 272 | table.dataTable tfoot td.dt-head-center { 273 | text-align: center; 274 | } 275 | 276 | table.dataTable thead th.dt-head-right, 277 | table.dataTable thead td.dt-head-right, 278 | table.dataTable tfoot th.dt-head-right, 279 | table.dataTable tfoot td.dt-head-right { 280 | text-align: right; 281 | } 282 | 283 | table.dataTable thead th.dt-head-justify, 284 | table.dataTable thead td.dt-head-justify, 285 | table.dataTable tfoot th.dt-head-justify, 286 | table.dataTable tfoot td.dt-head-justify { 287 | text-align: justify; 288 | } 289 | 290 | table.dataTable thead th.dt-head-nowrap, 291 | table.dataTable thead td.dt-head-nowrap, 292 | table.dataTable tfoot th.dt-head-nowrap, 293 | table.dataTable tfoot td.dt-head-nowrap { 294 | white-space: nowrap; 295 | } 296 | 297 | table.dataTable tbody th.dt-body-left, 298 | table.dataTable tbody td.dt-body-left { 299 | text-align: left; 300 | } 301 | 302 | table.dataTable tbody th.dt-body-center, 303 | table.dataTable tbody td.dt-body-center { 304 | text-align: center; 305 | } 306 | 307 | table.dataTable tbody th.dt-body-right, 308 | table.dataTable tbody td.dt-body-right { 309 | text-align: right; 310 | } 311 | 312 | table.dataTable tbody th.dt-body-justify, 313 | table.dataTable tbody td.dt-body-justify { 314 | text-align: justify; 315 | } 316 | 317 | table.dataTable tbody th.dt-body-nowrap, 318 | table.dataTable tbody td.dt-body-nowrap { 319 | white-space: nowrap; 320 | } 321 | 322 | table.dataTable, 323 | table.dataTable th, 324 | table.dataTable td { 325 | box-sizing: content-box; 326 | } 327 | 328 | /* 329 | * Control feature layout 330 | */ 331 | .dataTables_wrapper { 332 | position: relative; 333 | clear: both; 334 | *zoom: 1; 335 | zoom: 1; 336 | } 337 | 338 | .dataTables_wrapper .dataTables_length { 339 | float: left; 340 | } 341 | 342 | .dataTables_wrapper .dataTables_filter { 343 | float: right; 344 | text-align: right; 345 | } 346 | 347 | .dataTables_wrapper .dataTables_filter input { 348 | margin-left: 0.5em; 349 | } 350 | 351 | .dataTables_wrapper .dataTables_info { 352 | clear: both; 353 | float: left; 354 | padding-top: 0.755em; 355 | } 356 | 357 | .dataTables_wrapper .dataTables_paginate { 358 | float: right; 359 | text-align: right; 360 | padding-top: 0.25em; 361 | } 362 | 363 | .dataTables_wrapper .dataTables_paginate .paginate_button { 364 | box-sizing: border-box; 365 | display: inline-block; 366 | min-width: 1.5em; 367 | padding: 0.5em 1em; 368 | margin-left: 2px; 369 | text-align: center; 370 | text-decoration: none !important; 371 | cursor: pointer; 372 | *cursor: hand; 373 | color: #ededed !important; 374 | border: 1px solid transparent; 375 | border-radius: 2px; 376 | } 377 | 378 | .dataTables_wrapper .dataTables_paginate .paginate_button.current, .dataTables_wrapper .dataTables_paginate .paginate_button.current:hover { 379 | color: #ededed !important; 380 | border: 1px solid #212121; 381 | background-color: #adadad; 382 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #adadad), color-stop(100%, #666666)); 383 | /* Chrome,Safari4+ */ 384 | background: -webkit-linear-gradient(top, #adadad 0%, #666666 100%); 385 | /* Chrome10+,Safari5.1+ */ 386 | background: -moz-linear-gradient(top, #adadad 0%, #666666 100%); 387 | /* FF3.6+ */ 388 | background: -ms-linear-gradient(top, #adadad 0%, #666666 100%); 389 | /* IE10+ */ 390 | background: -o-linear-gradient(top, #adadad 0%, #666666 100%); 391 | /* Opera 11.10+ */ 392 | background: linear-gradient(to bottom, #adadad 0%, #666666 100%); 393 | /* W3C */ 394 | } 395 | 396 | .dataTables_wrapper .dataTables_paginate .paginate_button.disabled, .dataTables_wrapper .dataTables_paginate .paginate_button.disabled:hover, .dataTables_wrapper .dataTables_paginate .paginate_button.disabled:active { 397 | cursor: default; 398 | color: #666 !important; 399 | border: 1px solid transparent; 400 | background: transparent; 401 | box-shadow: none; 402 | } 403 | 404 | .dataTables_wrapper .dataTables_paginate .paginate_button:hover { 405 | color: white !important; 406 | border: 1px solid #111111; 407 | background-color: #585858; 408 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #585858), color-stop(100%, #111111)); 409 | /* Chrome,Safari4+ */ 410 | background: -webkit-linear-gradient(top, #585858 0%, #111111 100%); 411 | /* Chrome10+,Safari5.1+ */ 412 | background: -moz-linear-gradient(top, #585858 0%, #111111 100%); 413 | /* FF3.6+ */ 414 | background: -ms-linear-gradient(top, #585858 0%, #111111 100%); 415 | /* IE10+ */ 416 | background: -o-linear-gradient(top, #585858 0%, #111111 100%); 417 | /* Opera 11.10+ */ 418 | background: linear-gradient(to bottom, #585858 0%, #111111 100%); 419 | /* W3C */ 420 | } 421 | 422 | .dataTables_wrapper .dataTables_paginate .paginate_button:active { 423 | outline: none; 424 | background-color: #2b2b2b; 425 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #2b2b2b), color-stop(100%, #0c0c0c)); 426 | /* Chrome,Safari4+ */ 427 | background: -webkit-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%); 428 | /* Chrome10+,Safari5.1+ */ 429 | background: -moz-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%); 430 | /* FF3.6+ */ 431 | background: -ms-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%); 432 | /* IE10+ */ 433 | background: -o-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%); 434 | /* Opera 11.10+ */ 435 | background: linear-gradient(to bottom, #2b2b2b 0%, #0c0c0c 100%); 436 | /* W3C */ 437 | box-shadow: inset 0 0 3px #111; 438 | } 439 | 440 | .dataTables_wrapper .dataTables_paginate .ellipsis { 441 | padding: 0 1em; 442 | } 443 | 444 | .dataTables_wrapper .dataTables_processing { 445 | position: absolute; 446 | top: 50%; 447 | left: 50%; 448 | width: 100%; 449 | height: 40px; 450 | margin-left: -50%; 451 | margin-top: -25px; 452 | padding-top: 20px; 453 | text-align: center; 454 | font-size: 1.2em; 455 | background-color: white; 456 | background: -webkit-gradient(linear, left top, right top, color-stop(0%, rgba(56, 56, 56, 0)), color-stop(25%, rgba(56, 56, 56, 0.9)), color-stop(75%, rgba(56, 56, 56, 0.9)), color-stop(100%, rgba(255, 255, 255, 0))); 457 | background: -webkit-linear-gradient(left, rgba(56, 56, 56, 0) 0%, rgba(56, 56, 56, 0.9) 25%, rgba(56, 56, 56, 0.9) 75%, rgba(56, 56, 56, 0) 100%); 458 | background: -moz-linear-gradient(left, rgba(56, 56, 56, 0) 0%, rgba(56, 56, 56, 0.9) 25%, rgba(56, 56, 56, 0.9) 75%, rgba(56, 56, 56, 0) 100%); 459 | background: -ms-linear-gradient(left, rgba(56, 56, 56, 0) 0%, rgba(56, 56, 56, 0.9) 25%, rgba(56, 56, 56, 0.9) 75%, rgba(56, 56, 56, 0) 100%); 460 | background: -o-linear-gradient(left, rgba(56, 56, 56, 0) 0%, rgba(56, 56, 56, 0.9) 25%, rgba(56, 56, 56, 0.9) 75%, rgba(56, 56, 56, 0) 100%); 461 | background: linear-gradient(to right, rgba(56, 56, 56, 0) 0%, rgba(56, 56, 56, 0.9) 25%, rgba(56, 56, 56, 0.9) 75%, rgba(56, 56, 56, 0) 100%); 462 | } 463 | 464 | .dataTables_wrapper .dataTables_length, 465 | .dataTables_wrapper .dataTables_filter, 466 | .dataTables_wrapper .dataTables_info, 467 | .dataTables_wrapper .dataTables_processing, 468 | .dataTables_wrapper .dataTables_paginate { 469 | color: #ededed; 470 | } 471 | 472 | .dataTables_wrapper .dataTables_scroll { 473 | clear: both; 474 | } 475 | 476 | .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody { 477 | *margin-top: -1px; 478 | -webkit-overflow-scrolling: touch; 479 | } 480 | 481 | .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > thead > tr > th, .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > thead > tr > td, .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > tbody > tr > th, .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > tbody > tr > td { 482 | vertical-align: middle; 483 | } 484 | 485 | .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > thead > tr > th > div.dataTables_sizing, 486 | .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > thead > tr > td > div.dataTables_sizing, .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > tbody > tr > th > div.dataTables_sizing, 487 | .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > tbody > tr > td > div.dataTables_sizing { 488 | height: 0; 489 | overflow: hidden; 490 | margin: 0 !important; 491 | padding: 0 !important; 492 | } 493 | 494 | .dataTables_wrapper.no-footer .dataTables_scrollBody { 495 | border-bottom: 1px solid #111111; 496 | } 497 | 498 | .dataTables_wrapper.no-footer div.dataTables_scrollHead table.dataTable, 499 | .dataTables_wrapper.no-footer div.dataTables_scrollBody > table { 500 | border-bottom: none; 501 | } 502 | 503 | .dataTables_wrapper:after { 504 | visibility: hidden; 505 | display: block; 506 | content: ""; 507 | clear: both; 508 | height: 0; 509 | } 510 | 511 | @media screen and (max-width: 767px) { 512 | .dataTables_wrapper .dataTables_info, 513 | .dataTables_wrapper .dataTables_paginate { 514 | float: none; 515 | text-align: center; 516 | } 517 | 518 | .dataTables_wrapper .dataTables_paginate { 519 | margin-top: 0.5em; 520 | } 521 | } 522 | 523 | @media screen and (max-width: 640px) { 524 | .dataTables_wrapper .dataTables_length, 525 | .dataTables_wrapper .dataTables_filter { 526 | float: none; 527 | text-align: center; 528 | } 529 | 530 | .dataTables_wrapper .dataTables_filter { 531 | margin-top: 0.5em; 532 | } 533 | } -------------------------------------------------------------------------------- /static/css/libraries/datatables.css: -------------------------------------------------------------------------------- 1 | /* 2 | * This combined file was created by the DataTables downloader builder: 3 | * https://datatables.net/download 4 | * 5 | * To rebuild or modify this file with the latest versions of the included 6 | * software please visit: 7 | * https://datatables.net/download/#dt/dt-1.10.20 8 | * 9 | * Included libraries: 10 | * DataTables 1.10.20 11 | */ 12 | 13 | /* 14 | * Table styles 15 | */ 16 | table.dataTable { 17 | width: 100%; 18 | margin: 0 auto; 19 | clear: both; 20 | border-collapse: separate; 21 | border-spacing: 0; 22 | /* 23 | * Header and footer styles 24 | */ 25 | /* 26 | * Body styles 27 | */ 28 | } 29 | table.dataTable thead th, 30 | table.dataTable tfoot th { 31 | font-weight: bold; 32 | } 33 | table.dataTable thead th, 34 | table.dataTable thead td { 35 | padding: 10px 18px; 36 | border-bottom: 1px solid #111; 37 | } 38 | table.dataTable thead th:active, 39 | table.dataTable thead td:active { 40 | outline: none; 41 | } 42 | table.dataTable tfoot th, 43 | table.dataTable tfoot td { 44 | padding: 10px 18px 6px 18px; 45 | border-top: 1px solid #111; 46 | } 47 | table.dataTable thead .sorting, 48 | table.dataTable thead .sorting_asc, 49 | table.dataTable thead .sorting_desc, 50 | table.dataTable thead .sorting_asc_disabled, 51 | table.dataTable thead .sorting_desc_disabled { 52 | cursor: pointer; 53 | *cursor: hand; 54 | background-repeat: no-repeat; 55 | background-position: center right; 56 | } 57 | table.dataTable thead .sorting { 58 | background-image: url("DataTables-1.10.20/images/sort_both.png"); 59 | } 60 | table.dataTable thead .sorting_asc { 61 | background-image: url("DataTables-1.10.20/images/sort_asc.png"); 62 | } 63 | table.dataTable thead .sorting_desc { 64 | background-image: url("DataTables-1.10.20/images/sort_desc.png"); 65 | } 66 | table.dataTable thead .sorting_asc_disabled { 67 | background-image: url("DataTables-1.10.20/images/sort_asc_disabled.png"); 68 | } 69 | table.dataTable thead .sorting_desc_disabled { 70 | background-image: url("DataTables-1.10.20/images/sort_desc_disabled.png"); 71 | } 72 | table.dataTable tbody tr { 73 | background-color: #ffffff; 74 | } 75 | table.dataTable tbody tr.selected { 76 | background-color: #B0BED9; 77 | } 78 | table.dataTable tbody th, 79 | table.dataTable tbody td { 80 | padding: 8px 10px; 81 | } 82 | table.dataTable.row-border tbody th, table.dataTable.row-border tbody td, table.dataTable.display tbody th, table.dataTable.display tbody td { 83 | border-top: 1px solid #ddd; 84 | } 85 | table.dataTable.row-border tbody tr:first-child th, 86 | table.dataTable.row-border tbody tr:first-child td, table.dataTable.display tbody tr:first-child th, 87 | table.dataTable.display tbody tr:first-child td { 88 | border-top: none; 89 | } 90 | table.dataTable.cell-border tbody th, table.dataTable.cell-border tbody td { 91 | border-top: 1px solid #ddd; 92 | border-right: 1px solid #ddd; 93 | } 94 | table.dataTable.cell-border tbody tr th:first-child, 95 | table.dataTable.cell-border tbody tr td:first-child { 96 | border-left: 1px solid #ddd; 97 | } 98 | table.dataTable.cell-border tbody tr:first-child th, 99 | table.dataTable.cell-border tbody tr:first-child td { 100 | border-top: none; 101 | } 102 | table.dataTable.stripe tbody tr.odd, table.dataTable.display tbody tr.odd { 103 | background-color: #f9f9f9; 104 | } 105 | table.dataTable.stripe tbody tr.odd.selected, table.dataTable.display tbody tr.odd.selected { 106 | background-color: #acbad4; 107 | } 108 | table.dataTable.hover tbody tr:hover, table.dataTable.display tbody tr:hover { 109 | background-color: #f6f6f6; 110 | } 111 | table.dataTable.hover tbody tr:hover.selected, table.dataTable.display tbody tr:hover.selected { 112 | background-color: #aab7d1; 113 | } 114 | table.dataTable.order-column tbody tr > .sorting_1, 115 | table.dataTable.order-column tbody tr > .sorting_2, 116 | table.dataTable.order-column tbody tr > .sorting_3, table.dataTable.display tbody tr > .sorting_1, 117 | table.dataTable.display tbody tr > .sorting_2, 118 | table.dataTable.display tbody tr > .sorting_3 { 119 | background-color: #fafafa; 120 | } 121 | table.dataTable.order-column tbody tr.selected > .sorting_1, 122 | table.dataTable.order-column tbody tr.selected > .sorting_2, 123 | table.dataTable.order-column tbody tr.selected > .sorting_3, table.dataTable.display tbody tr.selected > .sorting_1, 124 | table.dataTable.display tbody tr.selected > .sorting_2, 125 | table.dataTable.display tbody tr.selected > .sorting_3 { 126 | background-color: #acbad5; 127 | } 128 | table.dataTable.display tbody tr.odd > .sorting_1, table.dataTable.order-column.stripe tbody tr.odd > .sorting_1 { 129 | background-color: #f1f1f1; 130 | } 131 | table.dataTable.display tbody tr.odd > .sorting_2, table.dataTable.order-column.stripe tbody tr.odd > .sorting_2 { 132 | background-color: #f3f3f3; 133 | } 134 | table.dataTable.display tbody tr.odd > .sorting_3, table.dataTable.order-column.stripe tbody tr.odd > .sorting_3 { 135 | background-color: whitesmoke; 136 | } 137 | table.dataTable.display tbody tr.odd.selected > .sorting_1, table.dataTable.order-column.stripe tbody tr.odd.selected > .sorting_1 { 138 | background-color: #a6b4cd; 139 | } 140 | table.dataTable.display tbody tr.odd.selected > .sorting_2, table.dataTable.order-column.stripe tbody tr.odd.selected > .sorting_2 { 141 | background-color: #a8b5cf; 142 | } 143 | table.dataTable.display tbody tr.odd.selected > .sorting_3, table.dataTable.order-column.stripe tbody tr.odd.selected > .sorting_3 { 144 | background-color: #a9b7d1; 145 | } 146 | table.dataTable.display tbody tr.even > .sorting_1, table.dataTable.order-column.stripe tbody tr.even > .sorting_1 { 147 | background-color: #fafafa; 148 | } 149 | table.dataTable.display tbody tr.even > .sorting_2, table.dataTable.order-column.stripe tbody tr.even > .sorting_2 { 150 | background-color: #fcfcfc; 151 | } 152 | table.dataTable.display tbody tr.even > .sorting_3, table.dataTable.order-column.stripe tbody tr.even > .sorting_3 { 153 | background-color: #fefefe; 154 | } 155 | table.dataTable.display tbody tr.even.selected > .sorting_1, table.dataTable.order-column.stripe tbody tr.even.selected > .sorting_1 { 156 | background-color: #acbad5; 157 | } 158 | table.dataTable.display tbody tr.even.selected > .sorting_2, table.dataTable.order-column.stripe tbody tr.even.selected > .sorting_2 { 159 | background-color: #aebcd6; 160 | } 161 | table.dataTable.display tbody tr.even.selected > .sorting_3, table.dataTable.order-column.stripe tbody tr.even.selected > .sorting_3 { 162 | background-color: #afbdd8; 163 | } 164 | table.dataTable.display tbody tr:hover > .sorting_1, table.dataTable.order-column.hover tbody tr:hover > .sorting_1 { 165 | background-color: #eaeaea; 166 | } 167 | table.dataTable.display tbody tr:hover > .sorting_2, table.dataTable.order-column.hover tbody tr:hover > .sorting_2 { 168 | background-color: #ececec; 169 | } 170 | table.dataTable.display tbody tr:hover > .sorting_3, table.dataTable.order-column.hover tbody tr:hover > .sorting_3 { 171 | background-color: #efefef; 172 | } 173 | table.dataTable.display tbody tr:hover.selected > .sorting_1, table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_1 { 174 | background-color: #a2aec7; 175 | } 176 | table.dataTable.display tbody tr:hover.selected > .sorting_2, table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_2 { 177 | background-color: #a3b0c9; 178 | } 179 | table.dataTable.display tbody tr:hover.selected > .sorting_3, table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_3 { 180 | background-color: #a5b2cb; 181 | } 182 | table.dataTable.no-footer { 183 | border-bottom: 1px solid #111; 184 | } 185 | table.dataTable.nowrap th, table.dataTable.nowrap td { 186 | white-space: nowrap; 187 | } 188 | table.dataTable.compact thead th, 189 | table.dataTable.compact thead td { 190 | padding: 4px 17px 4px 4px; 191 | } 192 | table.dataTable.compact tfoot th, 193 | table.dataTable.compact tfoot td { 194 | padding: 4px; 195 | } 196 | table.dataTable.compact tbody th, 197 | table.dataTable.compact tbody td { 198 | padding: 4px; 199 | } 200 | table.dataTable th.dt-left, 201 | table.dataTable td.dt-left { 202 | text-align: left; 203 | } 204 | table.dataTable th.dt-center, 205 | table.dataTable td.dt-center, 206 | table.dataTable td.dataTables_empty { 207 | text-align: center; 208 | } 209 | table.dataTable th.dt-right, 210 | table.dataTable td.dt-right { 211 | text-align: right; 212 | } 213 | table.dataTable th.dt-justify, 214 | table.dataTable td.dt-justify { 215 | text-align: justify; 216 | } 217 | table.dataTable th.dt-nowrap, 218 | table.dataTable td.dt-nowrap { 219 | white-space: nowrap; 220 | } 221 | table.dataTable thead th.dt-head-left, 222 | table.dataTable thead td.dt-head-left, 223 | table.dataTable tfoot th.dt-head-left, 224 | table.dataTable tfoot td.dt-head-left { 225 | text-align: left; 226 | } 227 | table.dataTable thead th.dt-head-center, 228 | table.dataTable thead td.dt-head-center, 229 | table.dataTable tfoot th.dt-head-center, 230 | table.dataTable tfoot td.dt-head-center { 231 | text-align: center; 232 | } 233 | table.dataTable thead th.dt-head-right, 234 | table.dataTable thead td.dt-head-right, 235 | table.dataTable tfoot th.dt-head-right, 236 | table.dataTable tfoot td.dt-head-right { 237 | text-align: right; 238 | } 239 | table.dataTable thead th.dt-head-justify, 240 | table.dataTable thead td.dt-head-justify, 241 | table.dataTable tfoot th.dt-head-justify, 242 | table.dataTable tfoot td.dt-head-justify { 243 | text-align: justify; 244 | } 245 | table.dataTable thead th.dt-head-nowrap, 246 | table.dataTable thead td.dt-head-nowrap, 247 | table.dataTable tfoot th.dt-head-nowrap, 248 | table.dataTable tfoot td.dt-head-nowrap { 249 | white-space: nowrap; 250 | } 251 | table.dataTable tbody th.dt-body-left, 252 | table.dataTable tbody td.dt-body-left { 253 | text-align: left; 254 | } 255 | table.dataTable tbody th.dt-body-center, 256 | table.dataTable tbody td.dt-body-center { 257 | text-align: center; 258 | } 259 | table.dataTable tbody th.dt-body-right, 260 | table.dataTable tbody td.dt-body-right { 261 | text-align: right; 262 | } 263 | table.dataTable tbody th.dt-body-justify, 264 | table.dataTable tbody td.dt-body-justify { 265 | text-align: justify; 266 | } 267 | table.dataTable tbody th.dt-body-nowrap, 268 | table.dataTable tbody td.dt-body-nowrap { 269 | white-space: nowrap; 270 | } 271 | 272 | table.dataTable, 273 | table.dataTable th, 274 | table.dataTable td { 275 | box-sizing: content-box; 276 | } 277 | 278 | /* 279 | * Control feature layout 280 | */ 281 | .dataTables_wrapper { 282 | position: relative; 283 | clear: both; 284 | *zoom: 1; 285 | zoom: 1; 286 | } 287 | .dataTables_wrapper .dataTables_length { 288 | float: left; 289 | } 290 | .dataTables_wrapper .dataTables_filter { 291 | float: right; 292 | text-align: right; 293 | } 294 | .dataTables_wrapper .dataTables_filter input { 295 | margin-left: 0.5em; 296 | } 297 | .dataTables_wrapper .dataTables_info { 298 | clear: both; 299 | float: left; 300 | padding-top: 0.755em; 301 | } 302 | .dataTables_wrapper .dataTables_paginate { 303 | float: right; 304 | text-align: right; 305 | padding-top: 0.25em; 306 | } 307 | .dataTables_wrapper .dataTables_paginate .paginate_button { 308 | box-sizing: border-box; 309 | display: inline-block; 310 | min-width: 1.5em; 311 | padding: 0.5em 1em; 312 | margin-left: 2px; 313 | text-align: center; 314 | text-decoration: none !important; 315 | cursor: pointer; 316 | *cursor: hand; 317 | color: #333 !important; 318 | border: 1px solid transparent; 319 | border-radius: 2px; 320 | } 321 | .dataTables_wrapper .dataTables_paginate .paginate_button.current, .dataTables_wrapper .dataTables_paginate .paginate_button.current:hover { 322 | color: #333 !important; 323 | border: 1px solid #979797; 324 | background-color: white; 325 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, white), color-stop(100%, #dcdcdc)); 326 | /* Chrome,Safari4+ */ 327 | background: -webkit-linear-gradient(top, white 0%, #dcdcdc 100%); 328 | /* Chrome10+,Safari5.1+ */ 329 | background: -moz-linear-gradient(top, white 0%, #dcdcdc 100%); 330 | /* FF3.6+ */ 331 | background: -ms-linear-gradient(top, white 0%, #dcdcdc 100%); 332 | /* IE10+ */ 333 | background: -o-linear-gradient(top, white 0%, #dcdcdc 100%); 334 | /* Opera 11.10+ */ 335 | background: linear-gradient(to bottom, white 0%, #dcdcdc 100%); 336 | /* W3C */ 337 | } 338 | .dataTables_wrapper .dataTables_paginate .paginate_button.disabled, .dataTables_wrapper .dataTables_paginate .paginate_button.disabled:hover, .dataTables_wrapper .dataTables_paginate .paginate_button.disabled:active { 339 | cursor: default; 340 | color: #666 !important; 341 | border: 1px solid transparent; 342 | background: transparent; 343 | box-shadow: none; 344 | } 345 | .dataTables_wrapper .dataTables_paginate .paginate_button:hover { 346 | color: white !important; 347 | border: 1px solid #111; 348 | background-color: #585858; 349 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #585858), color-stop(100%, #111)); 350 | /* Chrome,Safari4+ */ 351 | background: -webkit-linear-gradient(top, #585858 0%, #111 100%); 352 | /* Chrome10+,Safari5.1+ */ 353 | background: -moz-linear-gradient(top, #585858 0%, #111 100%); 354 | /* FF3.6+ */ 355 | background: -ms-linear-gradient(top, #585858 0%, #111 100%); 356 | /* IE10+ */ 357 | background: -o-linear-gradient(top, #585858 0%, #111 100%); 358 | /* Opera 11.10+ */ 359 | background: linear-gradient(to bottom, #585858 0%, #111 100%); 360 | /* W3C */ 361 | } 362 | .dataTables_wrapper .dataTables_paginate .paginate_button:active { 363 | outline: none; 364 | background-color: #2b2b2b; 365 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #2b2b2b), color-stop(100%, #0c0c0c)); 366 | /* Chrome,Safari4+ */ 367 | background: -webkit-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%); 368 | /* Chrome10+,Safari5.1+ */ 369 | background: -moz-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%); 370 | /* FF3.6+ */ 371 | background: -ms-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%); 372 | /* IE10+ */ 373 | background: -o-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%); 374 | /* Opera 11.10+ */ 375 | background: linear-gradient(to bottom, #2b2b2b 0%, #0c0c0c 100%); 376 | /* W3C */ 377 | box-shadow: inset 0 0 3px #111; 378 | } 379 | .dataTables_wrapper .dataTables_paginate .ellipsis { 380 | padding: 0 1em; 381 | } 382 | .dataTables_wrapper .dataTables_processing { 383 | position: absolute; 384 | top: 50%; 385 | left: 50%; 386 | width: 100%; 387 | height: 40px; 388 | margin-left: -50%; 389 | margin-top: -25px; 390 | padding-top: 20px; 391 | text-align: center; 392 | font-size: 1.2em; 393 | background-color: white; 394 | background: -webkit-gradient(linear, left top, right top, color-stop(0%, rgba(255, 255, 255, 0)), color-stop(25%, rgba(255, 255, 255, 0.9)), color-stop(75%, rgba(255, 255, 255, 0.9)), color-stop(100%, rgba(255, 255, 255, 0))); 395 | background: -webkit-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%); 396 | background: -moz-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%); 397 | background: -ms-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%); 398 | background: -o-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%); 399 | background: linear-gradient(to right, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%); 400 | } 401 | .dataTables_wrapper .dataTables_length, 402 | .dataTables_wrapper .dataTables_filter, 403 | .dataTables_wrapper .dataTables_info, 404 | .dataTables_wrapper .dataTables_processing, 405 | .dataTables_wrapper .dataTables_paginate { 406 | color: #333; 407 | } 408 | .dataTables_wrapper .dataTables_scroll { 409 | clear: both; 410 | } 411 | .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody { 412 | *margin-top: -1px; 413 | -webkit-overflow-scrolling: touch; 414 | } 415 | .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > thead > tr > th, .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > thead > tr > td, .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > tbody > tr > th, .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > tbody > tr > td { 416 | vertical-align: middle; 417 | } 418 | .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > thead > tr > th > div.dataTables_sizing, 419 | .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > thead > tr > td > div.dataTables_sizing, .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > tbody > tr > th > div.dataTables_sizing, 420 | .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > tbody > tr > td > div.dataTables_sizing { 421 | height: 0; 422 | overflow: hidden; 423 | margin: 0 !important; 424 | padding: 0 !important; 425 | } 426 | .dataTables_wrapper.no-footer .dataTables_scrollBody { 427 | border-bottom: 1px solid #111; 428 | } 429 | .dataTables_wrapper.no-footer div.dataTables_scrollHead table.dataTable, 430 | .dataTables_wrapper.no-footer div.dataTables_scrollBody > table { 431 | border-bottom: none; 432 | } 433 | .dataTables_wrapper:after { 434 | visibility: hidden; 435 | display: block; 436 | content: ""; 437 | clear: both; 438 | height: 0; 439 | } 440 | 441 | @media screen and (max-width: 767px) { 442 | .dataTables_wrapper .dataTables_info, 443 | .dataTables_wrapper .dataTables_paginate { 444 | float: none; 445 | text-align: center; 446 | } 447 | .dataTables_wrapper .dataTables_paginate { 448 | margin-top: 0.5em; 449 | } 450 | } 451 | @media screen and (max-width: 640px) { 452 | .dataTables_wrapper .dataTables_length, 453 | .dataTables_wrapper .dataTables_filter { 454 | float: none; 455 | text-align: center; 456 | } 457 | .dataTables_wrapper .dataTables_filter { 458 | margin-top: 0.5em; 459 | } 460 | } 461 | 462 | 463 | -------------------------------------------------------------------------------- /static/css/libraries/datatables.min.css: -------------------------------------------------------------------------------- 1 | table.dataTable{width:100%;margin:0 auto;clear:both;border-collapse:separate;border-spacing:0}table.dataTable thead th,table.dataTable tfoot th{font-weight:bold}table.dataTable thead th,table.dataTable thead td{padding:10px 18px;border-bottom:1px solid #111}table.dataTable thead th:active,table.dataTable thead td:active{outline:0}table.dataTable tfoot th,table.dataTable tfoot td{padding:10px 18px 6px 18px;border-top:1px solid #111}table.dataTable thead .sorting,table.dataTable thead .sorting_asc,table.dataTable thead .sorting_desc,table.dataTable thead .sorting_asc_disabled,table.dataTable thead .sorting_desc_disabled{cursor:pointer;*cursor:hand;background-repeat:no-repeat;background-position:center right}table.dataTable thead .sorting{background-image:url("DataTables-1.10.20/images/sort_both.png")}table.dataTable thead .sorting_asc{background-image:url("DataTables-1.10.20/images/sort_asc.png")}table.dataTable thead .sorting_desc{background-image:url("DataTables-1.10.20/images/sort_desc.png")}table.dataTable thead .sorting_asc_disabled{background-image:url("DataTables-1.10.20/images/sort_asc_disabled.png")}table.dataTable thead .sorting_desc_disabled{background-image:url("DataTables-1.10.20/images/sort_desc_disabled.png")}table.dataTable tbody tr{background-color:#fff}table.dataTable tbody tr.selected{background-color:#b0bed9}table.dataTable tbody th,table.dataTable tbody td{padding:8px 10px}table.dataTable.row-border tbody th,table.dataTable.row-border tbody td,table.dataTable.display tbody th,table.dataTable.display tbody td{border-top:1px solid #ddd}table.dataTable.row-border tbody tr:first-child th,table.dataTable.row-border tbody tr:first-child td,table.dataTable.display tbody tr:first-child th,table.dataTable.display tbody tr:first-child td{border-top:0}table.dataTable.cell-border tbody th,table.dataTable.cell-border tbody td{border-top:1px solid #ddd;border-right:1px solid #ddd}table.dataTable.cell-border tbody tr th:first-child,table.dataTable.cell-border tbody tr td:first-child{border-left:1px solid #ddd}table.dataTable.cell-border tbody tr:first-child th,table.dataTable.cell-border tbody tr:first-child td{border-top:0}table.dataTable.stripe tbody tr.odd,table.dataTable.display tbody tr.odd{background-color:#f9f9f9}table.dataTable.stripe tbody tr.odd.selected,table.dataTable.display tbody tr.odd.selected{background-color:#acbad4}table.dataTable.hover tbody tr:hover,table.dataTable.display tbody tr:hover{background-color:#f6f6f6}table.dataTable.hover tbody tr:hover.selected,table.dataTable.display tbody tr:hover.selected{background-color:#aab7d1}table.dataTable.order-column tbody tr>.sorting_1,table.dataTable.order-column tbody tr>.sorting_2,table.dataTable.order-column tbody tr>.sorting_3,table.dataTable.display tbody tr>.sorting_1,table.dataTable.display tbody tr>.sorting_2,table.dataTable.display tbody tr>.sorting_3{background-color:#fafafa}table.dataTable.order-column tbody tr.selected>.sorting_1,table.dataTable.order-column tbody tr.selected>.sorting_2,table.dataTable.order-column tbody tr.selected>.sorting_3,table.dataTable.display tbody tr.selected>.sorting_1,table.dataTable.display tbody tr.selected>.sorting_2,table.dataTable.display tbody tr.selected>.sorting_3{background-color:#acbad5}table.dataTable.display tbody tr.odd>.sorting_1,table.dataTable.order-column.stripe tbody tr.odd>.sorting_1{background-color:#f1f1f1}table.dataTable.display tbody tr.odd>.sorting_2,table.dataTable.order-column.stripe tbody tr.odd>.sorting_2{background-color:#f3f3f3}table.dataTable.display tbody tr.odd>.sorting_3,table.dataTable.order-column.stripe tbody tr.odd>.sorting_3{background-color:whitesmoke}table.dataTable.display tbody tr.odd.selected>.sorting_1,table.dataTable.order-column.stripe tbody tr.odd.selected>.sorting_1{background-color:#a6b4cd}table.dataTable.display tbody tr.odd.selected>.sorting_2,table.dataTable.order-column.stripe tbody tr.odd.selected>.sorting_2{background-color:#a8b5cf}table.dataTable.display tbody tr.odd.selected>.sorting_3,table.dataTable.order-column.stripe tbody tr.odd.selected>.sorting_3{background-color:#a9b7d1}table.dataTable.display tbody tr.even>.sorting_1,table.dataTable.order-column.stripe tbody tr.even>.sorting_1{background-color:#fafafa}table.dataTable.display tbody tr.even>.sorting_2,table.dataTable.order-column.stripe tbody tr.even>.sorting_2{background-color:#fcfcfc}table.dataTable.display tbody tr.even>.sorting_3,table.dataTable.order-column.stripe tbody tr.even>.sorting_3{background-color:#fefefe}table.dataTable.display tbody tr.even.selected>.sorting_1,table.dataTable.order-column.stripe tbody tr.even.selected>.sorting_1{background-color:#acbad5}table.dataTable.display tbody tr.even.selected>.sorting_2,table.dataTable.order-column.stripe tbody tr.even.selected>.sorting_2{background-color:#aebcd6}table.dataTable.display tbody tr.even.selected>.sorting_3,table.dataTable.order-column.stripe tbody tr.even.selected>.sorting_3{background-color:#afbdd8}table.dataTable.display tbody tr:hover>.sorting_1,table.dataTable.order-column.hover tbody tr:hover>.sorting_1{background-color:#eaeaea}table.dataTable.display tbody tr:hover>.sorting_2,table.dataTable.order-column.hover tbody tr:hover>.sorting_2{background-color:#ececec}table.dataTable.display tbody tr:hover>.sorting_3,table.dataTable.order-column.hover tbody tr:hover>.sorting_3{background-color:#efefef}table.dataTable.display tbody tr:hover.selected>.sorting_1,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_1{background-color:#a2aec7}table.dataTable.display tbody tr:hover.selected>.sorting_2,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_2{background-color:#a3b0c9}table.dataTable.display tbody tr:hover.selected>.sorting_3,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_3{background-color:#a5b2cb}table.dataTable.no-footer{border-bottom:1px solid #111}table.dataTable.nowrap th,table.dataTable.nowrap td{white-space:nowrap}table.dataTable.compact thead th,table.dataTable.compact thead td{padding:4px 17px 4px 4px}table.dataTable.compact tfoot th,table.dataTable.compact tfoot td{padding:4px}table.dataTable.compact tbody th,table.dataTable.compact tbody td{padding:4px}table.dataTable th.dt-left,table.dataTable td.dt-left{text-align:left}table.dataTable th.dt-center,table.dataTable td.dt-center,table.dataTable td.dataTables_empty{text-align:center}table.dataTable th.dt-right,table.dataTable td.dt-right{text-align:right}table.dataTable th.dt-justify,table.dataTable td.dt-justify{text-align:justify}table.dataTable th.dt-nowrap,table.dataTable td.dt-nowrap{white-space:nowrap}table.dataTable thead th.dt-head-left,table.dataTable thead td.dt-head-left,table.dataTable tfoot th.dt-head-left,table.dataTable tfoot td.dt-head-left{text-align:left}table.dataTable thead th.dt-head-center,table.dataTable thead td.dt-head-center,table.dataTable tfoot th.dt-head-center,table.dataTable tfoot td.dt-head-center{text-align:center}table.dataTable thead th.dt-head-right,table.dataTable thead td.dt-head-right,table.dataTable tfoot th.dt-head-right,table.dataTable tfoot td.dt-head-right{text-align:right}table.dataTable thead th.dt-head-justify,table.dataTable thead td.dt-head-justify,table.dataTable tfoot th.dt-head-justify,table.dataTable tfoot td.dt-head-justify{text-align:justify}table.dataTable thead th.dt-head-nowrap,table.dataTable thead td.dt-head-nowrap,table.dataTable tfoot th.dt-head-nowrap,table.dataTable tfoot td.dt-head-nowrap{white-space:nowrap}table.dataTable tbody th.dt-body-left,table.dataTable tbody td.dt-body-left{text-align:left}table.dataTable tbody th.dt-body-center,table.dataTable tbody td.dt-body-center{text-align:center}table.dataTable tbody th.dt-body-right,table.dataTable tbody td.dt-body-right{text-align:right}table.dataTable tbody th.dt-body-justify,table.dataTable tbody td.dt-body-justify{text-align:justify}table.dataTable tbody th.dt-body-nowrap,table.dataTable tbody td.dt-body-nowrap{white-space:nowrap}table.dataTable,table.dataTable th,table.dataTable td{box-sizing:content-box}.dataTables_wrapper{position:relative;clear:both;*zoom:1;zoom:1}.dataTables_wrapper .dataTables_length{float:left}.dataTables_wrapper .dataTables_filter{float:right;text-align:right}.dataTables_wrapper .dataTables_filter input{margin-left:.5em}.dataTables_wrapper .dataTables_info{clear:both;float:left;padding-top:.755em}.dataTables_wrapper .dataTables_paginate{float:right;text-align:right;padding-top:.25em}.dataTables_wrapper .dataTables_paginate .paginate_button{box-sizing:border-box;display:inline-block;min-width:1.5em;padding:.5em 1em;margin-left:2px;text-align:center;text-decoration:none !important;cursor:pointer;*cursor:hand;color:#333 !important;border:1px solid transparent;border-radius:2px}.dataTables_wrapper .dataTables_paginate .paginate_button.current,.dataTables_wrapper .dataTables_paginate .paginate_button.current:hover{color:#333 !important;border:1px solid #979797;background-color:white;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,white),color-stop(100%,#dcdcdc));background:-webkit-linear-gradient(top,white 0,#dcdcdc 100%);background:-moz-linear-gradient(top,white 0,#dcdcdc 100%);background:-ms-linear-gradient(top,white 0,#dcdcdc 100%);background:-o-linear-gradient(top,white 0,#dcdcdc 100%);background:linear-gradient(to bottom,white 0,#dcdcdc 100%)}.dataTables_wrapper .dataTables_paginate .paginate_button.disabled,.dataTables_wrapper .dataTables_paginate .paginate_button.disabled:hover,.dataTables_wrapper .dataTables_paginate .paginate_button.disabled:active{cursor:default;color:#666 !important;border:1px solid transparent;background:transparent;box-shadow:none}.dataTables_wrapper .dataTables_paginate .paginate_button:hover{color:white !important;border:1px solid #111;background-color:#585858;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#585858),color-stop(100%,#111));background:-webkit-linear-gradient(top,#585858 0,#111 100%);background:-moz-linear-gradient(top,#585858 0,#111 100%);background:-ms-linear-gradient(top,#585858 0,#111 100%);background:-o-linear-gradient(top,#585858 0,#111 100%);background:linear-gradient(to bottom,#585858 0,#111 100%)}.dataTables_wrapper .dataTables_paginate .paginate_button:active{outline:0;background-color:#2b2b2b;background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#2b2b2b),color-stop(100%,#0c0c0c));background:-webkit-linear-gradient(top,#2b2b2b 0,#0c0c0c 100%);background:-moz-linear-gradient(top,#2b2b2b 0,#0c0c0c 100%);background:-ms-linear-gradient(top,#2b2b2b 0,#0c0c0c 100%);background:-o-linear-gradient(top,#2b2b2b 0,#0c0c0c 100%);background:linear-gradient(to bottom,#2b2b2b 0,#0c0c0c 100%);box-shadow:inset 0 0 3px #111}.dataTables_wrapper .dataTables_paginate .ellipsis{padding:0 1em}.dataTables_wrapper .dataTables_processing{position:absolute;top:50%;left:50%;width:100%;height:40px;margin-left:-50%;margin-top:-25px;padding-top:20px;text-align:center;font-size:1.2em;background-color:white;background:-webkit-gradient(linear,left top,right top,color-stop(0,rgba(255,255,255,0)),color-stop(25%,rgba(255,255,255,0.9)),color-stop(75%,rgba(255,255,255,0.9)),color-stop(100%,rgba(255,255,255,0)));background:-webkit-linear-gradient(left,rgba(255,255,255,0) 0,rgba(255,255,255,0.9) 25%,rgba(255,255,255,0.9) 75%,rgba(255,255,255,0) 100%);background:-moz-linear-gradient(left,rgba(255,255,255,0) 0,rgba(255,255,255,0.9) 25%,rgba(255,255,255,0.9) 75%,rgba(255,255,255,0) 100%);background:-ms-linear-gradient(left,rgba(255,255,255,0) 0,rgba(255,255,255,0.9) 25%,rgba(255,255,255,0.9) 75%,rgba(255,255,255,0) 100%);background:-o-linear-gradient(left,rgba(255,255,255,0) 0,rgba(255,255,255,0.9) 25%,rgba(255,255,255,0.9) 75%,rgba(255,255,255,0) 100%);background:linear-gradient(to right,rgba(255,255,255,0) 0,rgba(255,255,255,0.9) 25%,rgba(255,255,255,0.9) 75%,rgba(255,255,255,0) 100%)}.dataTables_wrapper .dataTables_length,.dataTables_wrapper .dataTables_filter,.dataTables_wrapper .dataTables_info,.dataTables_wrapper .dataTables_processing,.dataTables_wrapper .dataTables_paginate{color:#333}.dataTables_wrapper .dataTables_scroll{clear:both}.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody{*margin-top:-1px;-webkit-overflow-scrolling:touch}.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>th,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>td,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>th,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>td{vertical-align:middle}.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>th>div.dataTables_sizing,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>td>div.dataTables_sizing,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>th>div.dataTables_sizing,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>td>div.dataTables_sizing{height:0;overflow:hidden;margin:0 !important;padding:0 !important}.dataTables_wrapper.no-footer .dataTables_scrollBody{border-bottom:1px solid #111}.dataTables_wrapper.no-footer div.dataTables_scrollHead table.dataTable,.dataTables_wrapper.no-footer div.dataTables_scrollBody>table{border-bottom:0}.dataTables_wrapper:after{visibility:hidden;display:block;content:"";clear:both;height:0}@media screen and (max-width:767px){.dataTables_wrapper .dataTables_info,.dataTables_wrapper .dataTables_paginate{float:none;text-align:center}.dataTables_wrapper .dataTables_paginate{margin-top:.5em}}@media screen and (max-width:640px){.dataTables_wrapper .dataTables_length,.dataTables_wrapper .dataTables_filter{float:none;text-align:center}.dataTables_wrapper .dataTables_filter{margin-top:.5em}} -------------------------------------------------------------------------------- /static/images/Final.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/primetime43/GAPS-2/81e1b9ad65331f02132209fa403ee737344d4fec/static/images/Final.png -------------------------------------------------------------------------------- /static/images/FinalGAPS.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/primetime43/GAPS-2/81e1b9ad65331f02132209fa403ee737344d4fec/static/images/FinalGAPS.jpg -------------------------------------------------------------------------------- /static/images/FinalGAPS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/primetime43/GAPS-2/81e1b9ad65331f02132209fa403ee737344d4fec/static/images/FinalGAPS.png -------------------------------------------------------------------------------- /static/images/arrow-clockwise.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /static/images/broken-bridge-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/primetime43/GAPS-2/81e1b9ad65331f02132209fa403ee737344d4fec/static/images/broken-bridge-2.jpg -------------------------------------------------------------------------------- /static/images/collection-fill.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /static/images/dead_end.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/primetime43/GAPS-2/81e1b9ad65331f02132209fa403ee737344d4fec/static/images/dead_end.png -------------------------------------------------------------------------------- /static/images/exclamation-triangle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /static/images/final-1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /static/images/final-2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /static/images/final-gaps.svg: -------------------------------------------------------------------------------- 1 | 2 | image/svg+xml 48 | 57 | 60 | 66 | 73 | 75 | 77 | 81 | 82 | 83 | 89 | 96 | 98 | 100 | 104 | 105 | 106 | 112 | 119 | 121 | 123 | 127 | 128 | 129 | 135 | 142 | 144 | 146 | 150 | 151 | 152 | 153 | 154 | -------------------------------------------------------------------------------- /static/images/gaps.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/primetime43/GAPS-2/81e1b9ad65331f02132209fa403ee737344d4fec/static/images/gaps.ico -------------------------------------------------------------------------------- /static/images/gear.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /static/images/info-circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /static/images/list-ul.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/images/mind_the_gap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/primetime43/GAPS-2/81e1b9ad65331f02132209fa403ee737344d4fec/static/images/mind_the_gap.png -------------------------------------------------------------------------------- /static/images/rss.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 16 | 24 | 30 | 31 | 32 | 42 | 50 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /static/images/sort_asc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/primetime43/GAPS-2/81e1b9ad65331f02132209fa403ee737344d4fec/static/images/sort_asc.png -------------------------------------------------------------------------------- /static/images/sort_asc_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/primetime43/GAPS-2/81e1b9ad65331f02132209fa403ee737344d4fec/static/images/sort_asc_disabled.png -------------------------------------------------------------------------------- /static/images/sort_both.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/primetime43/GAPS-2/81e1b9ad65331f02132209fa403ee737344d4fec/static/images/sort_both.png -------------------------------------------------------------------------------- /static/images/sort_desc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/primetime43/GAPS-2/81e1b9ad65331f02132209fa403ee737344d4fec/static/images/sort_desc.png -------------------------------------------------------------------------------- /static/images/sort_desc_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/primetime43/GAPS-2/81e1b9ad65331f02132209fa403ee737344d4fec/static/images/sort_desc_disabled.png -------------------------------------------------------------------------------- /static/images/wrench.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /static/js/libraries/stomp-2.3.3.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.7.1 2 | 3 | /* 4 | Stomp Over WebSocket http://www.jmesnil.net/stomp-websocket/doc/ | Apache License V2.0 5 | 6 | Copyright (C) 2010-2013 [Jeff Mesnil](http://jmesnil.net/) 7 | Copyright (C) 2012 [FuseSource, Inc.](http://fusesource.com) 8 | */ 9 | 10 | (function() { 11 | var Byte, Client, Frame, Stomp, 12 | __hasProp = {}.hasOwnProperty, 13 | __slice = [].slice; 14 | 15 | Byte = { 16 | LF: '\x0A', 17 | NULL: '\x00' 18 | }; 19 | 20 | Frame = (function() { 21 | var unmarshallSingle; 22 | 23 | function Frame(command, headers, body) { 24 | this.command = command; 25 | this.headers = headers != null ? headers : {}; 26 | this.body = body != null ? body : ''; 27 | } 28 | 29 | Frame.prototype.toString = function() { 30 | var lines, name, skipContentLength, value, _ref; 31 | lines = [this.command]; 32 | skipContentLength = this.headers['content-length'] === false ? true : false; 33 | if (skipContentLength) { 34 | delete this.headers['content-length']; 35 | } 36 | _ref = this.headers; 37 | for (name in _ref) { 38 | if (!__hasProp.call(_ref, name)) continue; 39 | value = _ref[name]; 40 | lines.push("" + name + ":" + value); 41 | } 42 | if (this.body && !skipContentLength) { 43 | lines.push("content-length:" + (Frame.sizeOfUTF8(this.body))); 44 | } 45 | lines.push(Byte.LF + this.body); 46 | return lines.join(Byte.LF); 47 | }; 48 | 49 | Frame.sizeOfUTF8 = function(s) { 50 | if (s) { 51 | return encodeURI(s).match(/%..|./g).length; 52 | } else { 53 | return 0; 54 | } 55 | }; 56 | 57 | unmarshallSingle = function(data) { 58 | var body, chr, command, divider, headerLines, headers, i, idx, len, line, start, trim, _i, _j, _len, _ref, _ref1; 59 | divider = data.search(RegExp("" + Byte.LF + Byte.LF)); 60 | headerLines = data.substring(0, divider).split(Byte.LF); 61 | command = headerLines.shift(); 62 | headers = {}; 63 | trim = function(str) { 64 | return str.replace(/^\s+|\s+$/g, ''); 65 | }; 66 | _ref = headerLines.reverse(); 67 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 68 | line = _ref[_i]; 69 | idx = line.indexOf(':'); 70 | headers[trim(line.substring(0, idx))] = trim(line.substring(idx + 1)); 71 | } 72 | body = ''; 73 | start = divider + 2; 74 | if (headers['content-length']) { 75 | len = parseInt(headers['content-length']); 76 | body = ('' + data).substring(start, start + len); 77 | } else { 78 | chr = null; 79 | for (i = _j = start, _ref1 = data.length; start <= _ref1 ? _j < _ref1 : _j > _ref1; i = start <= _ref1 ? ++_j : --_j) { 80 | chr = data.charAt(i); 81 | if (chr === Byte.NULL) { 82 | break; 83 | } 84 | body += chr; 85 | } 86 | } 87 | return new Frame(command, headers, body); 88 | }; 89 | 90 | Frame.unmarshall = function(datas) { 91 | var data; 92 | return (function() { 93 | var _i, _len, _ref, _results; 94 | _ref = datas.split(RegExp("" + Byte.NULL + Byte.LF + "*")); 95 | _results = []; 96 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 97 | data = _ref[_i]; 98 | if ((data != null ? data.length : void 0) > 0) { 99 | _results.push(unmarshallSingle(data)); 100 | } 101 | } 102 | return _results; 103 | })(); 104 | }; 105 | 106 | Frame.marshall = function(command, headers, body) { 107 | var frame; 108 | frame = new Frame(command, headers, body); 109 | return frame.toString() + Byte.NULL; 110 | }; 111 | 112 | return Frame; 113 | 114 | })(); 115 | 116 | Client = (function() { 117 | var now; 118 | 119 | function Client(ws) { 120 | this.ws = ws; 121 | this.ws.binaryType = "arraybuffer"; 122 | this.counter = 0; 123 | this.connected = false; 124 | this.heartbeat = { 125 | outgoing: 10000, 126 | incoming: 10000 127 | }; 128 | this.maxWebSocketFrameSize = 16 * 1024; 129 | this.subscriptions = {}; 130 | } 131 | 132 | Client.prototype.debug = function(message) { 133 | var _ref; 134 | return typeof window !== "undefined" && window !== null ? (_ref = window.console) != null ? _ref.log(message) : void 0 : void 0; 135 | }; 136 | 137 | now = function() { 138 | if (Date.now) { 139 | return Date.now(); 140 | } else { 141 | return new Date().valueOf; 142 | } 143 | }; 144 | 145 | Client.prototype._transmit = function(command, headers, body) { 146 | var out; 147 | out = Frame.marshall(command, headers, body); 148 | if (typeof this.debug === "function") { 149 | this.debug(">>> " + out); 150 | } 151 | while (true) { 152 | if (out.length > this.maxWebSocketFrameSize) { 153 | this.ws.send(out.substring(0, this.maxWebSocketFrameSize)); 154 | out = out.substring(this.maxWebSocketFrameSize); 155 | if (typeof this.debug === "function") { 156 | this.debug("remaining = " + out.length); 157 | } 158 | } else { 159 | return this.ws.send(out); 160 | } 161 | } 162 | }; 163 | 164 | Client.prototype._setupHeartbeat = function(headers) { 165 | var serverIncoming, serverOutgoing, ttl, v, _ref, _ref1; 166 | if ((_ref = headers.version) !== Stomp.VERSIONS.V1_1 && _ref !== Stomp.VERSIONS.V1_2) { 167 | return; 168 | } 169 | _ref1 = (function() { 170 | var _i, _len, _ref1, _results; 171 | _ref1 = headers['heart-beat'].split(","); 172 | _results = []; 173 | for (_i = 0, _len = _ref1.length; _i < _len; _i++) { 174 | v = _ref1[_i]; 175 | _results.push(parseInt(v)); 176 | } 177 | return _results; 178 | })(), serverOutgoing = _ref1[0], serverIncoming = _ref1[1]; 179 | if (!(this.heartbeat.outgoing === 0 || serverIncoming === 0)) { 180 | ttl = Math.max(this.heartbeat.outgoing, serverIncoming); 181 | if (typeof this.debug === "function") { 182 | this.debug("send PING every " + ttl + "ms"); 183 | } 184 | this.pinger = Stomp.setInterval(ttl, (function(_this) { 185 | return function() { 186 | _this.ws.send(Byte.LF); 187 | return typeof _this.debug === "function" ? _this.debug(">>> PING") : void 0; 188 | }; 189 | })(this)); 190 | } 191 | if (!(this.heartbeat.incoming === 0 || serverOutgoing === 0)) { 192 | ttl = Math.max(this.heartbeat.incoming, serverOutgoing); 193 | if (typeof this.debug === "function") { 194 | this.debug("check PONG every " + ttl + "ms"); 195 | } 196 | return this.ponger = Stomp.setInterval(ttl, (function(_this) { 197 | return function() { 198 | var delta; 199 | delta = now() - _this.serverActivity; 200 | if (delta > ttl * 2) { 201 | if (typeof _this.debug === "function") { 202 | _this.debug("did not receive server activity for the last " + delta + "ms"); 203 | } 204 | return _this.ws.close(); 205 | } 206 | }; 207 | })(this)); 208 | } 209 | }; 210 | 211 | Client.prototype._parseConnect = function() { 212 | var args, connectCallback, errorCallback, headers; 213 | args = 1 <= arguments.length ? __slice.call(arguments, 0) : []; 214 | headers = {}; 215 | switch (args.length) { 216 | case 2: 217 | headers = args[0], connectCallback = args[1]; 218 | break; 219 | case 3: 220 | if (args[1] instanceof Function) { 221 | headers = args[0], connectCallback = args[1], errorCallback = args[2]; 222 | } else { 223 | headers.login = args[0], headers.passcode = args[1], connectCallback = args[2]; 224 | } 225 | break; 226 | case 4: 227 | headers.login = args[0], headers.passcode = args[1], connectCallback = args[2], errorCallback = args[3]; 228 | break; 229 | default: 230 | headers.login = args[0], headers.passcode = args[1], connectCallback = args[2], errorCallback = args[3], headers.host = args[4]; 231 | } 232 | return [headers, connectCallback, errorCallback]; 233 | }; 234 | 235 | Client.prototype.connect = function() { 236 | var args, errorCallback, headers, out; 237 | args = 1 <= arguments.length ? __slice.call(arguments, 0) : []; 238 | out = this._parseConnect.apply(this, args); 239 | headers = out[0], this.connectCallback = out[1], errorCallback = out[2]; 240 | if (typeof this.debug === "function") { 241 | this.debug("Opening Web Socket..."); 242 | } 243 | this.ws.onmessage = (function(_this) { 244 | return function(evt) { 245 | var arr, c, client, data, frame, messageID, onreceive, subscription, _i, _len, _ref, _results; 246 | data = typeof ArrayBuffer !== 'undefined' && evt.data instanceof ArrayBuffer ? (arr = new Uint8Array(evt.data), typeof _this.debug === "function" ? _this.debug("--- got data length: " + arr.length) : void 0, ((function() { 247 | var _i, _len, _results; 248 | _results = []; 249 | for (_i = 0, _len = arr.length; _i < _len; _i++) { 250 | c = arr[_i]; 251 | _results.push(String.fromCharCode(c)); 252 | } 253 | return _results; 254 | })()).join('')) : evt.data; 255 | _this.serverActivity = now(); 256 | if (data === Byte.LF) { 257 | if (typeof _this.debug === "function") { 258 | _this.debug("<<< PONG"); 259 | } 260 | return; 261 | } 262 | if (typeof _this.debug === "function") { 263 | _this.debug("<<< " + data); 264 | } 265 | _ref = Frame.unmarshall(data); 266 | _results = []; 267 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 268 | frame = _ref[_i]; 269 | switch (frame.command) { 270 | case "CONNECTED": 271 | if (typeof _this.debug === "function") { 272 | _this.debug("connected to server " + frame.headers.server); 273 | } 274 | _this.connected = true; 275 | _this._setupHeartbeat(frame.headers); 276 | _results.push(typeof _this.connectCallback === "function" ? _this.connectCallback(frame) : void 0); 277 | break; 278 | case "MESSAGE": 279 | subscription = frame.headers.subscription; 280 | onreceive = _this.subscriptions[subscription] || _this.onreceive; 281 | if (onreceive) { 282 | client = _this; 283 | messageID = frame.headers["message-id"]; 284 | frame.ack = function(headers) { 285 | if (headers == null) { 286 | headers = {}; 287 | } 288 | return client.ack(messageID, subscription, headers); 289 | }; 290 | frame.nack = function(headers) { 291 | if (headers == null) { 292 | headers = {}; 293 | } 294 | return client.nack(messageID, subscription, headers); 295 | }; 296 | _results.push(onreceive(frame)); 297 | } else { 298 | _results.push(typeof _this.debug === "function" ? _this.debug("Unhandled received MESSAGE: " + frame) : void 0); 299 | } 300 | break; 301 | case "RECEIPT": 302 | _results.push(typeof _this.onreceipt === "function" ? _this.onreceipt(frame) : void 0); 303 | break; 304 | case "ERROR": 305 | _results.push(typeof errorCallback === "function" ? errorCallback(frame) : void 0); 306 | break; 307 | default: 308 | _results.push(typeof _this.debug === "function" ? _this.debug("Unhandled frame: " + frame) : void 0); 309 | } 310 | } 311 | return _results; 312 | }; 313 | })(this); 314 | this.ws.onclose = (function(_this) { 315 | return function() { 316 | var msg; 317 | msg = "Whoops! Lost connection to " + _this.ws.url; 318 | if (typeof _this.debug === "function") { 319 | _this.debug(msg); 320 | } 321 | _this._cleanUp(); 322 | return typeof errorCallback === "function" ? errorCallback(msg) : void 0; 323 | }; 324 | })(this); 325 | return this.ws.onopen = (function(_this) { 326 | return function() { 327 | if (typeof _this.debug === "function") { 328 | _this.debug('Web Socket Opened...'); 329 | } 330 | headers["accept-version"] = Stomp.VERSIONS.supportedVersions(); 331 | headers["heart-beat"] = [_this.heartbeat.outgoing, _this.heartbeat.incoming].join(','); 332 | return _this._transmit("CONNECT", headers); 333 | }; 334 | })(this); 335 | }; 336 | 337 | Client.prototype.disconnect = function(disconnectCallback, headers) { 338 | if (headers == null) { 339 | headers = {}; 340 | } 341 | this._transmit("DISCONNECT", headers); 342 | this.ws.onclose = null; 343 | this.ws.close(); 344 | this._cleanUp(); 345 | return typeof disconnectCallback === "function" ? disconnectCallback() : void 0; 346 | }; 347 | 348 | Client.prototype._cleanUp = function() { 349 | this.connected = false; 350 | if (this.pinger) { 351 | Stomp.clearInterval(this.pinger); 352 | } 353 | if (this.ponger) { 354 | return Stomp.clearInterval(this.ponger); 355 | } 356 | }; 357 | 358 | Client.prototype.send = function(destination, headers, body) { 359 | if (headers == null) { 360 | headers = {}; 361 | } 362 | if (body == null) { 363 | body = ''; 364 | } 365 | headers.destination = destination; 366 | return this._transmit("SEND", headers, body); 367 | }; 368 | 369 | Client.prototype.subscribe = function(destination, callback, headers) { 370 | var client; 371 | if (headers == null) { 372 | headers = {}; 373 | } 374 | if (!headers.id) { 375 | headers.id = "sub-" + this.counter++; 376 | } 377 | headers.destination = destination; 378 | this.subscriptions[headers.id] = callback; 379 | this._transmit("SUBSCRIBE", headers); 380 | client = this; 381 | return { 382 | id: headers.id, 383 | unsubscribe: function() { 384 | return client.unsubscribe(headers.id); 385 | } 386 | }; 387 | }; 388 | 389 | Client.prototype.unsubscribe = function(id) { 390 | delete this.subscriptions[id]; 391 | return this._transmit("UNSUBSCRIBE", { 392 | id: id 393 | }); 394 | }; 395 | 396 | Client.prototype.begin = function(transaction) { 397 | var client, txid; 398 | txid = transaction || "tx-" + this.counter++; 399 | this._transmit("BEGIN", { 400 | transaction: txid 401 | }); 402 | client = this; 403 | return { 404 | id: txid, 405 | commit: function() { 406 | return client.commit(txid); 407 | }, 408 | abort: function() { 409 | return client.abort(txid); 410 | } 411 | }; 412 | }; 413 | 414 | Client.prototype.commit = function(transaction) { 415 | return this._transmit("COMMIT", { 416 | transaction: transaction 417 | }); 418 | }; 419 | 420 | Client.prototype.abort = function(transaction) { 421 | return this._transmit("ABORT", { 422 | transaction: transaction 423 | }); 424 | }; 425 | 426 | Client.prototype.ack = function(messageID, subscription, headers) { 427 | if (headers == null) { 428 | headers = {}; 429 | } 430 | headers["message-id"] = messageID; 431 | headers.subscription = subscription; 432 | return this._transmit("ACK", headers); 433 | }; 434 | 435 | Client.prototype.nack = function(messageID, subscription, headers) { 436 | if (headers == null) { 437 | headers = {}; 438 | } 439 | headers["message-id"] = messageID; 440 | headers.subscription = subscription; 441 | return this._transmit("NACK", headers); 442 | }; 443 | 444 | return Client; 445 | 446 | })(); 447 | 448 | Stomp = { 449 | VERSIONS: { 450 | V1_0: '1.0', 451 | V1_1: '1.1', 452 | V1_2: '1.2', 453 | supportedVersions: function() { 454 | return '1.1,1.0'; 455 | } 456 | }, 457 | client: function(url, protocols) { 458 | var klass, ws; 459 | if (protocols == null) { 460 | protocols = ['v10.stomp', 'v11.stomp']; 461 | } 462 | klass = Stomp.WebSocketClass || WebSocket; 463 | ws = new klass(url, protocols); 464 | return new Client(ws); 465 | }, 466 | over: function(ws) { 467 | return new Client(ws); 468 | }, 469 | Frame: Frame 470 | }; 471 | 472 | if (typeof exports !== "undefined" && exports !== null) { 473 | exports.Stomp = Stomp; 474 | } 475 | 476 | if (typeof window !== "undefined" && window !== null) { 477 | Stomp.setInterval = function(interval, f) { 478 | return window.setInterval(f, interval); 479 | }; 480 | Stomp.clearInterval = function(id) { 481 | return window.clearInterval(id); 482 | }; 483 | window.Stomp = Stomp; 484 | } else if (!exports) { 485 | self.Stomp = Stomp; 486 | } 487 | 488 | }).call(this); 489 | -------------------------------------------------------------------------------- /static/js/libraries/stomp-2.3.3.min.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.7.1 2 | /* 3 | Stomp Over WebSocket http://www.jmesnil.net/stomp-websocket/doc/ | Apache License V2.0 4 | 5 | Copyright (C) 2010-2013 [Jeff Mesnil](http://jmesnil.net/) 6 | Copyright (C) 2012 [FuseSource, Inc.](http://fusesource.com) 7 | */ 8 | (function(){var t,e,n,i,r={}.hasOwnProperty,o=[].slice;t={LF:"\n",NULL:"\x00"};n=function(){var e;function n(t,e,n){this.command=t;this.headers=e!=null?e:{};this.body=n!=null?n:""}n.prototype.toString=function(){var e,i,o,s,u;e=[this.command];o=this.headers["content-length"]===false?true:false;if(o){delete this.headers["content-length"]}u=this.headers;for(i in u){if(!r.call(u,i))continue;s=u[i];e.push(""+i+":"+s)}if(this.body&&!o){e.push("content-length:"+n.sizeOfUTF8(this.body))}e.push(t.LF+this.body);return e.join(t.LF)};n.sizeOfUTF8=function(t){if(t){return encodeURI(t).match(/%..|./g).length}else{return 0}};e=function(e){var i,r,o,s,u,a,c,f,h,l,p,d,g,b,m,v,y;s=e.search(RegExp(""+t.LF+t.LF));u=e.substring(0,s).split(t.LF);o=u.shift();a={};d=function(t){return t.replace(/^\s+|\s+$/g,"")};v=u.reverse();for(g=0,m=v.length;gy;c=p<=y?++b:--b){r=e.charAt(c);if(r===t.NULL){break}i+=r}}return new n(o,a,i)};n.unmarshall=function(n){var i;return function(){var r,o,s,u;s=n.split(RegExp(""+t.NULL+t.LF+"*"));u=[];for(r=0,o=s.length;r0){u.push(e(i))}}return u}()};n.marshall=function(e,i,r){var o;o=new n(e,i,r);return o.toString()+t.NULL};return n}();e=function(){var e;function r(t){this.ws=t;this.ws.binaryType="arraybuffer";this.counter=0;this.connected=false;this.heartbeat={outgoing:1e4,incoming:1e4};this.maxWebSocketFrameSize=16*1024;this.subscriptions={}}r.prototype.debug=function(t){var e;return typeof window!=="undefined"&&window!==null?(e=window.console)!=null?e.log(t):void 0:void 0};e=function(){if(Date.now){return Date.now()}else{return(new Date).valueOf}};r.prototype._transmit=function(t,e,i){var r;r=n.marshall(t,e,i);if(typeof this.debug==="function"){this.debug(">>> "+r)}while(true){if(r.length>this.maxWebSocketFrameSize){this.ws.send(r.substring(0,this.maxWebSocketFrameSize));r=r.substring(this.maxWebSocketFrameSize);if(typeof this.debug==="function"){this.debug("remaining = "+r.length)}}else{return this.ws.send(r)}}};r.prototype._setupHeartbeat=function(n){var r,o,s,u,a,c;if((a=n.version)!==i.VERSIONS.V1_1&&a!==i.VERSIONS.V1_2){return}c=function(){var t,e,i,r;i=n["heart-beat"].split(",");r=[];for(t=0,e=i.length;t>> PING"):void 0}}(this))}if(!(this.heartbeat.incoming===0||o===0)){s=Math.max(this.heartbeat.incoming,o);if(typeof this.debug==="function"){this.debug("check PONG every "+s+"ms")}return this.ponger=i.setInterval(s,function(t){return function(){var n;n=e()-t.serverActivity;if(n>s*2){if(typeof t.debug==="function"){t.debug("did not receive server activity for the last "+n+"ms")}return t.ws.close()}}}(this))}};r.prototype._parseConnect=function(){var t,e,n,i;t=1<=arguments.length?o.call(arguments,0):[];i={};switch(t.length){case 2:i=t[0],e=t[1];break;case 3:if(t[1]instanceof Function){i=t[0],e=t[1],n=t[2]}else{i.login=t[0],i.passcode=t[1],e=t[2]}break;case 4:i.login=t[0],i.passcode=t[1],e=t[2],n=t[3];break;default:i.login=t[0],i.passcode=t[1],e=t[2],n=t[3],i.host=t[4]}return[i,e,n]};r.prototype.connect=function(){var r,s,u,a;r=1<=arguments.length?o.call(arguments,0):[];a=this._parseConnect.apply(this,r);u=a[0],this.connectCallback=a[1],s=a[2];if(typeof this.debug==="function"){this.debug("Opening Web Socket...")}this.ws.onmessage=function(i){return function(r){var o,u,a,c,f,h,l,p,d,g,b,m;c=typeof ArrayBuffer!=="undefined"&&r.data instanceof ArrayBuffer?(o=new Uint8Array(r.data),typeof i.debug==="function"?i.debug("--- got data length: "+o.length):void 0,function(){var t,e,n;n=[];for(t=0,e=o.length;t 2 | 3 | 4 | 5 | Gaps 2 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | {% include 'fragments/header.html' %} 18 | 19 |
20 | Gaps Logo 21 | 22 |

About

23 | 24 |

25 | GAPS 2 is a rewrite of the original GAPS project, now written in Python instead of Java. GAPS (Gaps A Plex Server) finds movies you're missing in your Plex Server. It's a great way to find additional movies that you might be interested in based on collections from movies in your Plex Server. 26 |

27 |

28 | The GAPS 2 project aims to bring the same functionality with the simplicity and versatility of Python. 29 |

30 | 31 |

Gaps searches through your Plex Server. It then queries 32 | for known 33 | movies in the same 34 | collection using The MovieDB, which you will need to get. Don't worry, we'll walk you through 35 | those 36 | steps. Anyway, if those movies don't exist in your library, Gaps will recommend getting those 37 | movies, legally of course.

38 | 39 |

An example would be having a copy of 'Alien (1979)' and Gaps 40 | recommending 41 | 'Aliens (1986)' and 42 | 'Alien³ (1992)' to be added to your collection.

43 |
44 | 45 |
46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /templates/configuration.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Gaps 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | {% include 'fragments/header.html' %} 20 | 21 |
22 |

Settings

23 | 24 | 38 |
39 | 40 |
41 |

To use Gaps, you'll need a MovieDB api key. Navigate over to The 43 | Movie DB, 44 | create an account, and make an API Key. Copy that key and paste it below.

45 | 46 |
47 |
48 | 49 |
50 | 51 | 53 |
54 | Please enter a Movie Database Key. 55 |
56 |
57 |
58 | 59 | Test 61 | Save 63 | 64 |
65 |
66 | 67 | 73 | 74 | 75 | 76 |
77 | 78 | 79 | 155 | 156 | 157 |
158 | 159 |
160 |
161 | 162 |
163 |
164 | 165 |
166 |
167 | 168 |
169 |
170 | 171 |
172 | 173 |
174 | 177 |
178 | Please select a Plex server. 179 |
180 |
181 | These are Plex servers/devices on the 182 | signed in account. 183 |
184 | 185 |
186 | 187 |
188 | 190 |
191 | 193 |
194 |
195 | Here is the token of the selected 196 | server/device. 197 | 199 | 201 |
202 |

Servers

203 |
204 |
205 |
206 | 207 |
208 |
209 |
210 | 211 | 212 |
213 | Please enter your Jellyfin server address. 214 |
215 |
216 |
217 | 218 | 219 |
220 | Please enter your Jellyfin username. 221 |
222 |
223 |
224 | 225 |
226 | 227 |
228 | 229 |
230 |
231 |
232 | Please enter your Jellyfin password. 233 |
234 |
235 | 236 |
237 |
238 | 239 |
240 |
241 |
242 | 243 |
244 | 245 | 249 | 250 | 254 | 255 | 256 | 262 | 263 | 264 | 270 | 271 | 272 | 273 | 274 | 285 | 286 | 298 | 299 | 300 | 325 | 326 | 456 | 457 | 458 | 492 | 493 | 495 | 497 | 499 | 501 | 503 | 504 | 505 | 506 | -------------------------------------------------------------------------------- /templates/emptyState.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Gaps 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | {% include 'fragments/header.html' %} 17 | 18 |
19 |
20 |
21 | ... 22 |
23 |
Your movies are really missing
24 |

You need to run Gaps at least once to have found the missing movies. Once you have 25 | you can come back here and see 26 | the RSS feed.

27 | Back 28 |
29 |
30 |
31 |
32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /templates/error.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Gaps 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | {% include 'fragments/header.html' %} 18 | 19 |
20 |
21 |
22 | ... 23 |
24 |
The gap is only getting bigger...
25 |

Check the logs to see what went wrong. You can post an issue on the GAPS 2 GitHub if you need 26 | more help.

27 |
28 |
29 |
30 |
31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /templates/error/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Gaps 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 57 | 58 |
59 |
60 |
61 | ... 62 |
63 |
You're lost
64 |

Let's head back to the landing page and try again.

65 | Back 66 |
67 |
68 |
69 |
70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /templates/fragments/common.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Common 5 | 6 | 7 |
8 | 9 |
10 | 11 | 12 | -------------------------------------------------------------------------------- /templates/fragments/header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Header 6 | 7 | 8 | 9 |
10 | 56 |
57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Gaps 2 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | {% include '/fragments/header.html' %} 17 | 18 |
19 | Gaps Logo 20 | 21 |

22 | GAPS 2 is a rewrite of the original GAPS project, now written in Python instead of Java. GAPS (Gaps A Plex Server) finds movies you're missing in your Plex Server. It's a great way to find additional movies that you might be interested in based on collections from movies in your Plex Server. 23 |

24 |

25 | The GAPS 2 project aims to bring the same functionality with the simplicity and versatility of Python. 26 |

27 | 28 |

Gaps searches through your Plex Server. It then queries 29 | for known 30 | movies in the same 31 | collection using The MovieDB, which you will need to get. Don't worry, we'll walk you through 32 | those 33 | steps. Anyway, if those movies don't exist in your library, Gaps will recommend getting those 34 | movies, legally of course.

35 | 36 |

An example would be having a copy of 'Alien (1979)' and Gaps 37 | recommending 38 | 'Aliens (1986)' and 39 | 'Alien³ (1992)' to be added to your collection.

40 | 41 |

Getting Started

42 |

To get started, head to configuration and enter your TMDB ID and 43 | Plex Server 44 | information.

45 | 46 |
47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /templates/libraries.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Gaps 6 | 7 | 8 | 9 | 11 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | {% include 'fragments/header.html' %} 24 | 25 |
26 | 27 |
28 |

Libraries

29 | 30 |
31 | 64 | 65 | 75 | 76 | 86 | 87 | 88 | 101 |
102 |
103 |
104 |
105 | ... 107 |
108 |
Your movies are really missing
109 |

You need to configure Gaps before you can search for missing movies. 110 |

111 |
112 |
113 |
114 | 115 |
116 | 117 | 118 | 125 | 126 | 148 | 149 | 271 | 272 | 273 | 275 | 276 | 278 | 279 | 280 | 281 | -------------------------------------------------------------------------------- /templates/recommended.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Gaps 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | {% include 'fragments/header.html' %} 19 | 20 |
21 |

Missing Movies

22 | 23 |
24 | 25 |
26 |
27 | 28 | 35 |
36 | 37 |
38 | 39 |
40 |
41 | 42 | 43 | 56 |
57 |
58 |
59 | ... 61 |
62 |
Your movies are really missing
63 |

You need to configure Gaps before you can search for missing movies. 64 |

65 |
66 |
67 |
68 | 78 |
79 | 80 | 81 |
82 | 83 | 94 | 95 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | -------------------------------------------------------------------------------- /templates/updates.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Gaps 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | {% include 'fragments/header.html' %} 17 | 18 |
19 | Gaps Logo 20 | 21 |

Updates

22 | 23 |

v1.0.2

24 | 28 | 29 |

v1.0.1

30 |
    31 |
  • Adds the necessary files to create and run the program on a docker image/containers
  • 32 |
33 | 34 |

v1.0.0

35 | 54 |

55 | GAPS 2 is a rewrite of the original GAPS project, now written in Python instead of Java. GAPS (Gaps A Plex Server) finds movies you're missing in your Plex Server. It's a great way to find additional movies that you might be interested in based on collections from movies in your Plex Server. 56 |

57 |

58 | The GAPS 2 project aims to bring the same functionality with the simplicity and versatility of Python. 59 |

60 | 61 |
62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /wsgi.py: -------------------------------------------------------------------------------- 1 | import importlib 2 | 3 | app = importlib.import_module("GAPS 2").app 4 | 5 | if __name__ == "__main__": 6 | app.run(host='0.0.0.0') 7 | --------------------------------------------------------------------------------