diff options
436 files changed, 29816 insertions, 0 deletions
diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/.dockerignore diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..526c8a38 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.sh text eol=lf
\ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..e31adcb6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +# JetBrains platform +.idea/ + +# Credential setup file +keys.json + +# pyenv version files +.python-version +mongodb/opendc_testing/* + +# macOS-specific files +.DS_Store diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..a90c1fb5 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,33 @@ +# Contributing to the OpenDC Frontend + +First of all, thanks for wanting to contribute! 🎉 + + +## 💬 Have a question or general feedback relating to OpenDC? + +Contact us at 📧[opendc@atlarge-research.com](mailto:opendc@atlarge-research.com)! + + +## 🐞 Want to report a bug or suggest a feature? + +Please file an issue in one of our GitHub repos! OpenDC is a stack of a number of components, so here is a list of the different components with their respective domains. Please go to the one fits your bug or feature best: + +* Docker or database setup? Go to [the main OpenDC repo](https://github.com/atlarge-research/opendc/issues). +* The web application? Go to [the frontend repo](https://github.com/atlarge-research/opendc-frontend/issues). +* The web server? Go to [the web server repo](https://github.com/atlarge-research/opendc-web-server/issues). +* The simulator? Go to [the simulator repo](https://github.com/atlarge-research/opendc-simulator/issues). + +Once you are on the appropriate page for your issue report, have a look if someone has already filed an issue addressing your concern. + +If there already is such an issue, feel free to comment on the issue to show your support for it, or to add additional information that might be helpful. You can also just react with a thumbs-up 👍 to the issue, to indicate that you'd be interested in its resolution. This can help us prioritize what we spend our development time on. + +If you can't find an issue that fits your problem or feature request, open a new one. Describe actual and expected behavior, and be as detailed as you can. We'll get back to you asap. + + +## 💻 Want to contribute code? + +Great! If your contribution concerns overall stack setup (relating to Docker or the database), this repo is the right place to be! However, if you rather want to contribute to one of the components (the frontend, the web server, or the simulator), go to their respective repositories and look for documentation and guidelines for contribution there. + +If you want to contribute to the main repository, [fork it](https://github.com/atlarge-research/opendc/new/master) and submit a PR here when you're ready! Be sure to describe *what* you changed and *why* you changed it, to help us understand what your contribution is about. + +A quick note on commit messages: Please follow common Git standards when writing commit messages, see [this post](https://chris.beams.io/posts/git-commit/) for details. diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..f7f36d87 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,35 @@ +FROM node:14.2.0 +MAINTAINER Sacheendra Talluri <sacheendra.t@gmail.com> + +# Adding the mongodb repo and installing the client +RUN wget -qO - https://www.mongodb.org/static/pgp/server-4.2.asc | apt-key add - \ + && echo "deb http://repo.mongodb.org/apt/debian stretch/mongodb-org/4.2 main" | tee /etc/apt/sources.list.d/mongodb-org-4.2.list \ + && apt-get update \ + && apt-get install -y mongodb-org + +# Installing python and web-server dependencies +RUN echo "deb http://ftp.debian.org/debian stretch main" >> /etc/apt/sources.list \ + && apt-get update \ + && apt-get install -y python3 python3-pip yarn git sed mysql-client pymongo \ + && pip3 install oauth2client eventlet flask-socketio flask-compress mysql-connector-python-rf \ + && pip3 install --upgrade pyasn1-modules \ + && rm -rf /var/lib/apt/lists/* + +# Copy OpenDC directory +COPY ./ /opendc + +# Setting up simulator +RUN pip install -e /opendc/opendc-web-server \ + && python /opendc/opendc-web-server/setup.py install \ + && chmod 555 /opendc/build/configure.sh \ + && cd /opendc/opendc-frontend \ + && rm -rf ./build \ + && rm -rf ./node_modules \ + && yarn \ + && export REACT_APP_OAUTH_CLIENT_ID=$(cat ../keys.json | python -c "import sys, json; print json.load(sys.stdin)['OAUTH_CLIENT_ID']") \ + && yarn build + +# Set working directory +WORKDIR /opendc + +CMD ["sh", "-c", "./build/configure.sh && python3 opendc-web-server/main.py keys.json"] diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 00000000..57288ae2 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 atlarge-research + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 00000000..2bb734e8 --- /dev/null +++ b/README.md @@ -0,0 +1,97 @@ +<h1 align="center"> + <img src="images/logo.png" width="100" alt="OpenDC"> + <br> + OpenDC +</h1> +<p align="center"> + Collaborative Datacenter Simulation and Exploration for Everybody +</p> + +<br> + +OpenDC is an open-source simulator for datacenters aimed at both research and education. + + + +Users can construct datacenters (see above) and define experiments to see how these datacenters perform under different workloads and schedulers (see below). + + + +The simulator is accessible both as a ready-to-use website hosted by Delft University of Technology at [opendc.org](http://opendc.org), and as source code that users can run locally on their own machine. + +OpenDC is a project by the [@Large Research Group](http://atlarge-research.com). + +## Architecture + +OpenDC consists of four components: a Kotlin simulator, a MariaDB database, a Python Flask web server, and a React.js frontend. + +<p align="center"> + <img src="https://raw.githubusercontent.com/tudelft-atlarge/opendc/master/images/opendc-component-diagram.png" alt="OpenDC Component Diagram"> +</p> + +On the frontend, users can construct a topology by specifying a datacenter's rooms, racks and machines, and create experiments to see how a workload trace runs on that topology. The frontend communicates with the web server over SocketIO, through a custom REST request/response layer. For example, the frontend might make a `GET` request to `/api/v1/users/{userId}`, but this request is completed via SocketIO, not plain HTTP requests. + +The (Swagger/ OpenAPI compliant) API spec specifies what requests the frontend can make to the web server. To view this specification, go to the [Swagger UI](http://petstore.swagger.io/) and "Explore" [opendc-api-spec.json](https://raw.githubusercontent.com/tudelft-atlarge/opendc/master/opendc-api-spec.json). + +The web server receives API requests and processes them in the SQLite database. When the frontend requests to run a new experiment, the web server adds it to the `experiments` table in the database and sets is `state` as `QUEUED`. + +The simulator monitors the database for `QUEUED` experiments, and simulates them as they are submitted. It writes the resulting `machine_states` and `task_states` to the database, which the frontend can then again retrieve via the web server. + +## Setup + +### Preamble + +The official way to run OpenDC is using Docker. Other options include building and running locally, and building and running to deploy on a server. + +For all of these options, you have to create a Google API Console project and client ID, which the OpenDC frontend and web server will use to authenticate users and requests. Follow [these steps](https://developers.google.com/identity/sign-in/web/devconsole-project) to make such a project. In the 'Authorized JavaScript origins' field, be sure to add `http://localhost:8081` as origin. Download the JSON of the OAuth 2.0 client ID you created from the Credentials tab, and specifically note the `client_id`, which you'll need to build OpenDC. + +### Installing Docker + +GNU/Linux, Mac OS X and Windows 10 Professional users can install Docker by following the instructions [here](https://www.docker.com/products/docker). + +Users of Windows 10 Home and previous editions of Windows can use [Docker Toolbox](https://www.docker.com/products/docker-toolbox). If you're using the toolbox, don't forget to setup port forwarding (see the following subsection if you haven't done that, yet). + +#### Port Forwarding + +Open VirtualBox, navigate to the settings of your default docker VM, and go to the 'Network' tab. There, hidden in the 'Advanced' panel, is the 'Port forwarding' feature, where you can set a rule for exposing a port of the VM to the host OS. Add one from guest IP `10.0.2.15` to host IP `127.0.0.1`, both on port `8081`. This enables you to open a browser on your host OS and navigate to `http://localhost:8081`, once the server is running. + +### Running OpenDC + +To build and run the full OpenDC stack locally on Linux or Mac, you first need to clone the project: + +```bash +# Clone the repo and its submodules +git clone --recursive https://github.com/atlarge-research/opendc.git + +# Enter the directory +cd opendc/ + +# If you're on Windows: +# Turn off automatic line-ending conversion in the simulator sub-repository +cd opendc-simulator/ +git config core.autocrlf false +cd .. +``` + +In the directory you just entered, you need to set up a small configuration file. To do this, create a file called `keys.json` in the `opendc` folder. In this file, simply replace `your-google-oauth-client-id` with your `client_id` from the OAuth client ID you created. For a standard setup, you can leave the other settings as-is. + +```json +{ + "FLASK_SECRET": "This is a super duper secret flask key", + "OAUTH_CLIENT_ID": "your-google-oauth-client-id", + "ROOT_DIR": "/opendc", + "SERVER_BASE_URL": "http://localhost:8081" +} +``` + +Now, start the server: + +```bash +# Build the Docker image +docker-compose build + +# Start the OpenDC container and the database container +docker-compose up +``` + +Wait a few seconds and open `http://localhost:8081` in your browser to use OpenDC. diff --git a/build/configure.sh b/build/configure.sh new file mode 100755 index 00000000..ceb1e616 --- /dev/null +++ b/build/configure.sh @@ -0,0 +1,43 @@ +if [ -z "$MONGO_DB" ]; then + echo "MONGO_DB environment variable not specified" + exit 1 +fi + +if [ -z "$MONGO_DB_USER" ]; then + echo "MONGO_DB_USER environment variable not specified" + exit 1 +fi + +if [ -z "$MONGO_DB_PASSWORD" ]; then + echo "MONGO_DB_PASSWORD environment variable not specified" + exit 1 +fi + +#MYSQL_COMMAND="mysql -h mariadb -u $MYSQL_USER --password=$MYSQL_PASSWORD" + +MONGO_COMMAND="mongo $MONGO_DB -h $MONGO_DB_HOST --port $MONGO_DB_PORT -u $MONGO_DB_USERNAME -p $MONGO_DB_PASSWORD --authenticationDatabase $MONGO_DB" + +until eval $MONGO_COMMAND --eval 'db.getCollectionNames();' ; do + echo "MongoDB is unavailable - sleeping" + sleep 1 +done + +echo "MongoDB available" + +#NUM_TABLES=$(eval "$MYSQL_COMMAND -B --disable-column-names -e \"SELECT count(*) FROM information_schema.tables WHERE table_schema='$MYSQL_DATABASE';\"") + +# Check if database is empty +#if [ "$NUM_TABLES" -eq 0 ]; then +# eval $MYSQL_COMMAND "$MYSQL_DATABASE" < ./database/schema.sql +# eval $MYSQL_COMMAND "$MYSQL_DATABASE" < ./database/test.sql +#fi + +# Writing databse config values to keys.json +cat keys.json | python -c "import os, sys, json; ks = json.load(sys.stdin); \ + ks['MONGODB_HOST'] = os.environ['MONGO_DB_HOST']; \ + ks['MONGODB_PORT'] = os.environ['MONGO_DB_PORT']; \ + ks['MONGODB_DATABASE'] = os.environ['MONGO_DB']; \ + ks['MYSQL_USER'] = os.environ['MONGO_DB_USER']; \ + ks['MYSQL_PASSWORD'] = os.environ['MONGO_DB_PASSWORD']; \ + print json.dumps(ks, indent=4)" > new_keys.json +mv new_keys.json keys.json diff --git a/build/supervisord.conf b/build/supervisord.conf new file mode 100644 index 00000000..37b5cc16 --- /dev/null +++ b/build/supervisord.conf @@ -0,0 +1,9 @@ +[supervisord] +nodaemon=true + +[program:web-server] +command=/usr/bin/python2.7 /opendc/opendc-web-server/main.py /opendc/keys.json +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 diff --git a/database/Dockerfile b/database/Dockerfile new file mode 100644 index 00000000..e30aed51 --- /dev/null +++ b/database/Dockerfile @@ -0,0 +1,8 @@ +FROM mariadb:10.1 +MAINTAINER Fabian Mastenbroek <f.s.mastenbroek@student.tudelft.nl> + +# Import schema into database +ADD schema.sql /docker-entrypoint-initdb.d + +# Add test data into database +#ADD test.sql /docker-entrypoint-initdb.d diff --git a/database/README.md b/database/README.md new file mode 100644 index 00000000..9fba2d5c --- /dev/null +++ b/database/README.md @@ -0,0 +1,13 @@ +# OpenDC Database + +To rebuild the database at a location (or in this directory if none is specified): + +```bash +python rebuild-database.py "path/to/database/directory" +``` + +To view a table in the database: + +```bash +python view-table.py "path/to/database/directory" table_name +``` diff --git a/database/gwf_converter/gwf_converter.py b/database/gwf_converter/gwf_converter.py new file mode 100644 index 00000000..902bd93f --- /dev/null +++ b/database/gwf_converter/gwf_converter.py @@ -0,0 +1,115 @@ +import os +import sys + +import mysql.connector as mariadb + + +class Job: + def __init__(self, gwf_id): + self.gwf_id = gwf_id + self.db_id = -1 + self.tasks = [] + + +class Task: + def __init__(self, gwf_id, job, submit_time, run_time, num_processors, dependency_gwf_ids): + self.gwf_id = gwf_id + self.job = job + self.submit_time = submit_time + self.run_time = run_time + self.cores = num_processors + self.flops = 4000 * run_time * num_processors + self.dependency_gwf_ids = dependency_gwf_ids + self.db_id = -1 + self.dependencies = [] + + +def get_jobs_from_gwf_file(file_name): + jobs = {} + tasks = {} + + with open(file_name, "r") as f: + # Skip first CSV header line + f.readline() + + for line in f: + if line.startswith("#") or len(line.strip()) == 0: + continue + + values = [col.strip() for col in line.split(",")] + cast_values = [int(values[i]) for i in range(len(values) - 1)] + job_id, task_id, submit_time, run_time, num_processors, req_num_processors = cast_values + dependency_gwf_ids = [int(val) for val in values[-1].split(" ") if val != ""] + + if job_id not in jobs: + jobs[job_id] = Job(job_id) + + new_task = Task(task_id, jobs[job_id], submit_time, run_time, num_processors, dependency_gwf_ids) + tasks[task_id] = new_task + jobs[job_id].tasks.append(new_task) + + for task in tasks.values(): + for dependency_gwf_id in task.dependency_gwf_ids: + if dependency_gwf_id in tasks: + task.dependencies.append(tasks[dependency_gwf_id]) + + return jobs.values() + + +def write_to_db(conn, trace_name, jobs): + cursor = conn.cursor() + + trace_id = execute_insert_query(conn, cursor, "INSERT INTO traces (name) VALUES ('%s')" % trace_name) + + for job in jobs: + job.db_id = execute_insert_query(conn, cursor, "INSERT INTO jobs (name, trace_id) VALUES ('%s',%d)" + % ("Job %d" % job.gwf_id, trace_id)) + + for task in job.tasks: + task.db_id = execute_insert_query(conn, cursor, + "INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) " + "VALUES (%d,%d,%d,%d)" + % (task.submit_time, task.flops, task.cores, job.db_id)) + + for job in jobs: + for task in job.tasks: + for dependency in task.dependencies: + execute_insert_query(conn, cursor, "INSERT INTO task_dependencies (first_task_id, second_task_id) " + "VALUES (%d,%d)" + % (dependency.db_id, task.db_id)) + +def execute_insert_query(conn, cursor, sql): + try: + cursor.execute(sql) + except mariadb.Error as error: + print("SQL Error: {}".format(error)) + + conn.commit() + return cursor.lastrowid + + +def main(trace_path): + trace_name = sys.argv[2] if (len(sys.argv) > 2) else \ + os.path.splitext(os.path.basename(trace_path))[0] + gwf_jobs = get_jobs_from_gwf_file(trace_path) + + host = os.environ.get('PERSISTENCE_HOST','localhost') + user = os.environ.get('PERSISTENCE_USER','opendc') + password = os.environ.get('PERSISTENCE_PASSWORD','opendcpassword') + database = os.environ.get('PERSISTENCE_DATABASE','opendc') + conn = mariadb.connect(host=host, user=user, password=password, database=database) + write_to_db(conn, trace_name, gwf_jobs) + conn.close() + + +if __name__ == "__main__": + if len(sys.argv) < 2: + sys.exit("Usage: %s file [name]" % sys.argv[0]) + + if sys.argv[1] in ("-a", "--all"): + for f in os.listdir("traces"): + if f.endswith(".gwf"): + print("Converting {}".format(f)) + main(os.path.join("traces", f)) + else: + main(sys.argv[1]) diff --git a/database/gwf_converter/requirements.txt b/database/gwf_converter/requirements.txt new file mode 100644 index 00000000..0eaebf12 --- /dev/null +++ b/database/gwf_converter/requirements.txt @@ -0,0 +1 @@ +mysql diff --git a/database/gwf_converter/traces/default.gwf b/database/gwf_converter/traces/default.gwf new file mode 100644 index 00000000..b1c55a17 --- /dev/null +++ b/database/gwf_converter/traces/default.gwf @@ -0,0 +1,6 @@ +WorkflowID, JobID , SubmitTime , RunTime , NProcs , ReqNProcs , Dependencies +0 , 1 , 1 , 1 , 1 , 1, 5 4 3 +0 , 2 , 2 , 2 , 2 , 2, 3 +0 , 3 , 3 , 3 , 3 , 3, 5 +0 , 4 , 4 , 4 , 4 , 4, +0 , 5 , 5 , 5 , 5 , 5, diff --git a/database/rebuild-database.py b/database/rebuild-database.py new file mode 100644 index 00000000..0cbeb27a --- /dev/null +++ b/database/rebuild-database.py @@ -0,0 +1,32 @@ +import os +import sqlite3 +import sys + +sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0) + +try: + BASE_DIR = directory_name=sys.argv[1] +except: + BASE_DIR = os.path.dirname(os.path.abspath(__file__)) +db_location = os.path.join(BASE_DIR, 'opendc.db') + +if os.path.exists(db_location): + print "Removing old database..." + os.remove(db_location) + +print "Connecting to new database..." +conn = sqlite3.connect(db_location) +c = conn.cursor() + +print "Importing schema..." +with open('schema.sql') as schema: + c.executescript(schema.read()) + +print "Importing test data..." +with open('test.sql') as test: + c.executescript(test.read()) + +conn.commit() +conn.close() + +print "Done." diff --git a/database/rebuild.bat b/database/rebuild.bat new file mode 100644 index 00000000..c0f38da1 --- /dev/null +++ b/database/rebuild.bat @@ -0,0 +1,3 @@ +del database.db +sqlite3 database.db < schema.sql +sqlite3 database.db < test.sql
\ No newline at end of file diff --git a/database/schema.sql b/database/schema.sql new file mode 100644 index 00000000..f6286260 --- /dev/null +++ b/database/schema.sql @@ -0,0 +1,818 @@ +-- Tables referred to in foreign key constraints are defined after the constraints are defined +SET FOREIGN_KEY_CHECKS = 0; + +/* +* A user is identified by their google_id, which the server gets by authenticating with Google. +*/ + +-- Users +DROP TABLE IF EXISTS users; +CREATE TABLE users ( + id INTEGER PRIMARY KEY NOT NULL AUTO_INCREMENT, + google_id TEXT NOT NULL, + email TEXT, + given_name TEXT, + family_name TEXT +); + +/* +* The authorizations table defines which users are authorized to "OWN", "EDIT", or "VIEW" a simulation. The +* authorization_level table defines the permission levels. +*/ + +-- User authorizations +DROP TABLE IF EXISTS authorizations; +CREATE TABLE authorizations ( + user_id INTEGER NOT NULL, + simulation_id INTEGER NOT NULL, + authorization_level VARCHAR(50) NOT NULL, + + FOREIGN KEY (user_id) REFERENCES users (id) + ON DELETE CASCADE + ON UPDATE CASCADE, + FOREIGN KEY (simulation_id) REFERENCES simulations (id) + ON DELETE CASCADE + ON UPDATE CASCADE, + FOREIGN KEY (authorization_level) REFERENCES authorization_levels (level) +); + +CREATE UNIQUE INDEX authorizations_index + ON authorizations ( + user_id, + simulation_id + ); + +-- Authorization levels +DROP TABLE IF EXISTS authorization_levels; +CREATE TABLE authorization_levels ( + level VARCHAR(50) PRIMARY KEY NOT NULL +); +INSERT INTO authorization_levels (level) VALUES ('OWN'); +INSERT INTO authorization_levels (level) VALUES ('EDIT'); +INSERT INTO authorization_levels (level) VALUES ('VIEW'); + +/* +* A Simulation has several Paths, which define the topology of the datacenter at different times. A Simulation also +* has several Experiments, which can be run on a combination of Paths, Schedulers and Traces. Simulations also serve +* as the scope to which different Users can be Authorized. +* +* The datetime_created and datetime_last_edited columns are in a subset of ISO-8601 (second fractions are ommitted): +* YYYY-MM-DDTHH:MM:SS, where... +* - YYYY is the four-digit year, +* - MM is the two-digit month (1-12) +* - DD is the two-digit day of the month (1-31) +* - HH is the two-digit hours part (0-23) +* - MM is the two-digit minutes part (0-59) +* - SS is the two-digit seconds part (0-59) +*/ + +-- Simulation +DROP TABLE IF EXISTS simulations; +CREATE TABLE simulations ( + id INTEGER PRIMARY KEY NOT NULL AUTO_INCREMENT, + datetime_created VARCHAR(50) NOT NULL CHECK (datetime_created LIKE '____-__-__T__:__:__'), + datetime_last_edited VARCHAR(50) NOT NULL CHECK (datetime_last_edited LIKE '____-__-__T__:__:__'), + name VARCHAR(50) NOT NULL +); + +/* +* An Experiment consists of a Path, a Scheduler, and a Trace. The Path defines the topology of the datacenter at +* different times in the simulation. The Scheduler defines which scheduler to use to simulate this experiment. The +* Trace defines which tasks have to be run in the simulation. +*/ + +DROP TABLE IF EXISTS experiments; +CREATE TABLE experiments ( + id INTEGER PRIMARY KEY NOT NULL AUTO_INCREMENT, + simulation_id INTEGER NOT NULL, + path_id INTEGER NOT NULL, + trace_id INTEGER NOT NULL, + scheduler_name VARCHAR(50) NOT NULL, + name TEXT NOT NULL, + state TEXT NOT NULL, + last_simulated_tick INTEGER NOT NULL DEFAULT 0 CHECK (last_simulated_tick >= 0), + + FOREIGN KEY (simulation_id) REFERENCES simulations (id) + ON DELETE CASCADE + ON UPDATE CASCADE, + FOREIGN KEY (path_id) REFERENCES paths (id) + ON DELETE CASCADE + ON UPDATE CASCADE, + FOREIGN KEY (trace_id) REFERENCES traces (id) + ON DELETE CASCADE + ON UPDATE CASCADE, + FOREIGN KEY (scheduler_name) REFERENCES schedulers (name) + ON DELETE CASCADE + ON UPDATE CASCADE +); + +/* +* A Simulation has several Paths, which each contain Sections. A Section details which Datacenter topology to use +* starting at which point in time (known internally as a "tick"). So, combining the several Sections in a Path +* tells us which Datacenter topology to use at each tick. +*/ + +-- Path +DROP TABLE IF EXISTS paths; +CREATE TABLE paths ( + id INTEGER PRIMARY KEY NOT NULL AUTO_INCREMENT, + simulation_id INTEGER NOT NULL, + name TEXT, + datetime_created VARCHAR(50) NOT NULL CHECK (datetime_created LIKE '____-__-__T__:__:__'), + + FOREIGN KEY (simulation_id) REFERENCES simulations (id) + ON DELETE CASCADE + ON UPDATE CASCADE +); + +-- Sections +DROP TABLE IF EXISTS sections; +CREATE TABLE sections ( + id INTEGER PRIMARY KEY NOT NULL AUTO_INCREMENT, + path_id INTEGER NOT NULL, + datacenter_id INTEGER NOT NULL, + start_tick INTEGER NOT NULL CHECK (start_tick >= 0), + + FOREIGN KEY (path_id) REFERENCES paths (id) + ON DELETE CASCADE + ON UPDATE CASCADE, + FOREIGN KEY (datacenter_id) REFERENCES datacenters (id) + ON DELETE CASCADE + ON UPDATE CASCADE +); + +-- Scheduler names +DROP TABLE IF EXISTS schedulers; +CREATE TABLE schedulers ( + name VARCHAR(50) PRIMARY KEY NOT NULL +); +INSERT INTO schedulers (name) VALUES ('FIFO-FIRSTFIT'); +INSERT INTO schedulers (name) VALUES ('FIFO-BESTFIT'); +INSERT INTO schedulers (name) VALUES ('FIFO-WORSTFIT'); +INSERT INTO schedulers (name) VALUES ('FIFO-RANDOM'); +INSERT INTO schedulers (name) VALUES ('SRTF-FIRSTFIT'); +INSERT INTO schedulers (name) VALUES ('SRTF-BESTFIT'); +INSERT INTO schedulers (name) VALUES ('SRTF-WORSTFIT'); +INSERT INTO schedulers (name) VALUES ('SRTF-RANDOM'); +INSERT INTO schedulers (name) VALUES ('RANDOM-FIRSTFIT'); +INSERT INTO schedulers (name) VALUES ('RANDOM-BESTFIT'); +INSERT INTO schedulers (name) VALUES ('RANDOM-WORSTFIT'); +INSERT INTO schedulers (name) VALUES ('RANDOM-RANDOM'); + +/* +* Each simulation has a single trace. A trace contains tasks and their start times. +*/ + +-- A trace describes when tasks arrives in a datacenter +DROP TABLE IF EXISTS traces; +CREATE TABLE traces ( + id INTEGER PRIMARY KEY NOT NULL AUTO_INCREMENT, + name TEXT NOT NULL +); + +-- A job +DROP TABLE IF EXISTS jobs; +CREATE TABLE jobs ( + id INTEGER PRIMARY KEY NOT NULL AUTO_INCREMENT, + name TEXT NOT NULL, + trace_id INTEGER NOT NULL, + + FOREIGN KEY (trace_id) REFERENCES traces (id) + ON DELETE CASCADE + ON UPDATE CASCADE +); + +-- A task that's defined in terms of how many flops (floating point operations) it takes to complete +DROP TABLE IF EXISTS tasks; +CREATE TABLE tasks ( + id INTEGER PRIMARY KEY NOT NULL AUTO_INCREMENT, + start_tick INTEGER NOT NULL CHECK (start_tick >= 0), + total_flop_count BIGINT NOT NULL CHECK (total_flop_count >= 0), + core_count INTEGER NOT NULL CHECK (core_count >= 0), + job_id INTEGER NOT NULL, + + FOREIGN KEY (job_id) REFERENCES jobs (id) + ON DELETE CASCADE + ON UPDATE CASCADE +); + +-- A dependency between two tasks. +DROP TABLE IF EXISTS task_dependencies; +CREATE TABLE task_dependencies ( + first_task_id INTEGER NOT NULL, + second_task_id INTEGER NOT NULL, + + PRIMARY KEY (first_task_id, second_task_id), + FOREIGN KEY (first_task_id) REFERENCES tasks (id) + ON DELETE CASCADE + ON UPDATE CASCADE, + FOREIGN KEY (second_task_id) REFERENCES tasks (id) + ON DELETE CASCADE + ON UPDATE CASCADE +); + +/* +* A task_state describes how much of a task has already been completed at the time of the current tick. Several +* machine_states show which machines worked on the task. +*/ + +-- A state for a task_flop +DROP TABLE IF EXISTS task_states; +CREATE TABLE task_states ( + id INTEGER PRIMARY KEY NOT NULL AUTO_INCREMENT, + task_id INTEGER NOT NULL, + experiment_id INTEGER NOT NULL, + tick INTEGER NOT NULL CHECK (tick >= 0), + flops_left INTEGER NOT NULL CHECK (flops_left >= 0), + cores_used INTEGER NOT NULL CHECK (cores_used >= 0), + + FOREIGN KEY (task_id) REFERENCES tasks (id), + FOREIGN KEY (experiment_id) REFERENCES experiments (id) + ON DELETE CASCADE + ON UPDATE CASCADE +); + +-- The measurements of a single stage +DROP TABLE IF EXISTS stage_measurements; +CREATE TABLE stage_measurements ( + id INTEGER PRIMARY KEY NOT NULL AUTO_INCREMENT, + experiment_id INTEGER NOT NULL, + tick INTEGER NOT NULL CHECK (tick >= 0), + stage INTEGER NOT NULL CHECK (stage >= 0), + cpu BIGINT NOT NULL CHECK (cpu >= 0), + wall BIGINT NOT NULL CHECK (wall >= 0), + size INTEGER NOT NULL CHECK (size >= 0), + iterations INTEGER NOT NULL CHECK (iterations >= 0), + + FOREIGN KEY (experiment_id) REFERENCES experiments (id) + ON DELETE CASCADE + ON UPDATE CASCADE +); + +-- Metrics of a job task +DROP TABLE IF EXISTS job_metrics; +CREATE TABLE job_metrics ( + id INTEGER PRIMARY KEY NOT NULL AUTO_INCREMENT, + experiment_id INTEGER NOT NULL, + job_id INTEGER NOT NULL, + critical_path INTEGER NOT NULL CHECK (critical_path >= 0), + critical_path_length INTEGER NOT NULL CHECK (critical_path_length >= 0), + waiting_time INTEGER NOT NULL CHECK (waiting_time >= 0), + makespan INTEGER NOT NULL CHECK (makespan >= 0), + nsl INTEGER NOT NULL CHECK (nsl >= 0), + + FOREIGN KEY (experiment_id) REFERENCES experiments (id) + ON DELETE CASCADE + ON UPDATE CASCADE, + FOREIGN KEY (job_id) REFERENCES jobs (id) + ON DELETE CASCADE + ON UPDATE CASCADE +); + +-- Metrics of a single task +DROP TABLE IF EXISTS task_metrics; +CREATE TABLE task_metrics ( + id INTEGER PRIMARY KEY NOT NULL AUTO_INCREMENT, + experiment_id INTEGER NOT NULL, + task_id INTEGER NOT NULL, + job_id INTEGER NOT NULL, + waiting INTEGER NOT NULL CHECK (waiting >= 0), + execution INTEGER NOT NULL CHECK (execution >= 0), + turnaround INTEGER NOT NULL CHECK (turnaround >= 0), + + FOREIGN KEY (experiment_id) REFERENCES experiments (id) + ON DELETE CASCADE + ON UPDATE CASCADE, + FOREIGN KEY (task_id) REFERENCES tasks (id) + ON DELETE CASCADE + ON UPDATE CASCADE, + FOREIGN KEY (job_id) REFERENCES jobs (id) + ON DELETE CASCADE + ON UPDATE CASCADE +); + +-- A machine state +DROP TABLE IF EXISTS machine_states; +CREATE TABLE machine_states ( + id INTEGER PRIMARY KEY NOT NULL AUTO_INCREMENT, + machine_id INTEGER NOT NULL, + experiment_id INTEGER NOT NULL, + tick INTEGER NOT NULL, + temperature_c REAL, + in_use_memory_mb INTEGER, + load_fraction REAL CHECK (load_fraction >= 0 AND load_fraction <= 1), + + FOREIGN KEY (machine_id) REFERENCES machines (id) + ON DELETE CASCADE + ON UPDATE CASCADE, + FOREIGN KEY (experiment_id) REFERENCES experiments (id) + ON DELETE CASCADE + ON UPDATE CASCADE +); + +/* +* A Section references a Datacenter topology, which can be used by multiple Sections to create Paths that go back and +* forth between different topologies. +*/ + +-- Datacenters +DROP TABLE IF EXISTS datacenters; +CREATE TABLE datacenters ( + id INTEGER PRIMARY KEY NOT NULL AUTO_INCREMENT, + simulation_id INTEGER NOT NULL, + starred INTEGER CHECK (starred = 0 OR starred = 1), + + FOREIGN KEY (simulation_id) REFERENCES simulations (id) + ON DELETE CASCADE + ON UPDATE CASCADE +); + +/* +* A datacenter consists of several rooms. A room has a type that specifies what kind of objects can be in it. +*/ + +-- Rooms in a datacenter +DROP TABLE IF EXISTS rooms; +CREATE TABLE rooms ( + id INTEGER PRIMARY KEY NOT NULL AUTO_INCREMENT, + name TEXT NOT NULL, + datacenter_id INTEGER NOT NULL, + type VARCHAR(50) NOT NULL, + topology_id INTEGER, + + FOREIGN KEY (datacenter_id) REFERENCES datacenters (id) + ON DELETE CASCADE + ON UPDATE CASCADE, + FOREIGN KEY (type) REFERENCES room_types (name) + ON DELETE CASCADE + ON UPDATE CASCADE, + FOREIGN KEY (topology_id) REFERENCES rooms (id) + ON DELETE NO ACTION + ON UPDATE CASCADE +); + +DROP TABLE IF EXISTS room_types; +CREATE TABLE room_types ( + name VARCHAR(50) PRIMARY KEY NOT NULL +); +INSERT INTO room_types (name) VALUES ('SERVER'); +INSERT INTO room_types (name) VALUES ('HALLWAY'); +INSERT INTO room_types (name) VALUES ('OFFICE'); +INSERT INTO room_types (name) VALUES ('POWER'); +INSERT INTO room_types (name) VALUES ('COOLING'); + +/* +* A room consists of tiles that have a quantized (x,y) position. The same tile can't be in multiple rooms. All tiles +* in a room must touch at least one edge to another tile in that room. A tile is occupied by a single object, which +* has a type from the object_types table. +*/ + +-- Tiles in a room +DROP TABLE IF EXISTS tiles; +CREATE TABLE tiles ( + id INTEGER PRIMARY KEY NOT NULL AUTO_INCREMENT, + position_x INTEGER NOT NULL, + position_y INTEGER NOT NULL, + room_id INTEGER NOT NULL, + object_id INTEGER, + topology_id INTEGER, + + FOREIGN KEY (room_id) REFERENCES rooms (id) + ON DELETE CASCADE + ON UPDATE CASCADE, + FOREIGN KEY (object_id) REFERENCES objects (id), + FOREIGN KEY (topology_id) REFERENCES tiles (id) + ON DELETE NO ACTION + ON UPDATE CASCADE, + + UNIQUE (position_x, position_y, room_id), -- only one tile can be in the same position in a room + UNIQUE (object_id) -- an object can only be on one tile +); + +DELIMITER // + +-- Make sure this datacenter doesn't already have a tile in this location +-- and tiles in a room are connected. +DROP TRIGGER IF EXISTS before_insert_tiles_check_existence; +CREATE TRIGGER before_insert_tiles_check_existence + BEFORE INSERT + ON tiles + FOR EACH ROW + BEGIN + -- checking tile overlap + -- a tile already exists such that.. + IF EXISTS(SELECT datacenter_id + FROM tiles + JOIN rooms ON tiles.room_id = rooms.id + WHERE ( + + -- it's in the same datacenter as the new tile... + datacenter_id = (SELECT datacenter_id + FROM rooms + WHERE rooms.id = NEW.room_id) + + -- and in the the same position as the new tile. + AND NEW.position_x = tiles.position_x AND NEW.position_y = tiles.position_y + )) + THEN + -- raise an error + SIGNAL SQLSTATE '45000' + SET MESSAGE_TEXT = 'OccupiedTilePosition'; + END IF; + + -- checking tile adjacency + -- this isn't the first tile, ... + IF (EXISTS(SELECT * + FROM tiles + WHERE (NEW.room_id = tiles.room_id)) + + -- and the new tile isn't directly to right, to the left, above, or below an exisiting tile. + AND NOT EXISTS(SELECT * + FROM tiles + WHERE ( + NEW.room_id = tiles.room_id AND ( + (NEW.position_x + 1 = tiles.position_x AND NEW.position_y = tiles.position_y) -- right + OR (NEW.position_x - 1 = tiles.position_x AND NEW.position_y = tiles.position_y) -- left + OR (NEW.position_x = tiles.position_x AND NEW.position_y + 1 = tiles.position_y) -- above + OR (NEW.position_x = tiles.position_x AND NEW.position_y - 1 = tiles.position_y) -- below + ) + ))) + THEN + -- raise an error + SIGNAL SQLSTATE '45000' + SET MESSAGE_TEXT = 'InvalidTilePosition'; + END IF; + END// + +DELIMITER ; + +/* +* Objects are on tiles and have a type. They form an extra abstraction layer to make it easier to find what object is +* on a tile, as well as to enforce that only objects of the right type are in a certain room. +* +* To add a PSU, cooling item, or rack to a tile, first add an object. Then use that object's ID as the value for the +* object_id column of the PSU, cooling item, or rack table. +* +* The allowed_object table specifies what types of objects are allowed in what types of rooms. +*/ + +-- Objects +DROP TABLE IF EXISTS objects; +CREATE TABLE objects ( + id INTEGER PRIMARY KEY NOT NULL AUTO_INCREMENT, + type VARCHAR(50) NOT NULL, + + FOREIGN KEY (type) REFERENCES object_types (name) +); + +-- Object types +DROP TABLE IF EXISTS object_types; +CREATE TABLE object_types ( + name VARCHAR(50) PRIMARY KEY NOT NULL +); +INSERT INTO object_types (name) VALUES ('PSU'); +INSERT INTO object_types (name) VALUES ('COOLING_ITEM'); +INSERT INTO object_types (name) VALUES ('RACK'); + +-- Allowed objects table +DROP TABLE IF EXISTS allowed_objects; +CREATE TABLE allowed_objects ( + room_type VARCHAR(50) NOT NULL, + object_type VARCHAR(50) NOT NULL, + + FOREIGN KEY (room_type) REFERENCES room_types (name), + FOREIGN KEY (object_type) REFERENCES object_types (name) +); + +-- Allowed objects per room +INSERT INTO allowed_objects (room_type, object_type) VALUES ('SERVER', 'RACK'); +-- INSERT INTO allowed_objects (room_type, object_type) VALUES ('POWER', 'PSU'); +-- INSERT INTO allowed_objects (room_type, object_type) VALUES ('COOLING', 'COOLING_ITEM'); + +DELIMITER // + +-- Make sure objects are added to tiles in rooms they're allowed to be in. +DROP TRIGGER IF EXISTS before_update_tiles; +CREATE TRIGGER before_update_tiles + BEFORE UPDATE + ON tiles + FOR EACH ROW + BEGIN + + IF ((NEW.object_id IS NOT NULL) AND ( + + -- the type of the object being added to the tile... + ( + SELECT objects.type + FROM objects + JOIN tiles ON tiles.object_id = objects.id + WHERE tiles.id = NEW.id + ) + + -- is not in the set of allowed object types for the room the tile is in. + NOT IN ( + SELECT object_type + FROM allowed_objects + JOIN rooms ON rooms.type = allowed_objects.room_type + WHERE rooms.id = NEW.room_id + ) + )) + THEN + -- raise an error + SIGNAL SQLSTATE '45000' + SET MESSAGE_TEXT = 'ForbiddenObjectType'; + END IF; + END// + +DELIMITER ; + +/* +* PSUs are a type of object. +*/ + +-- PSUs on tiles +DROP TABLE IF EXISTS psus; +CREATE TABLE psus ( + id INTEGER NOT NULL, + energy_kwh INTEGER NOT NULL CHECK (energy_kwh > 0), + type VARCHAR(50) NOT NULL, + failure_model_id INTEGER NOT NULL, + + FOREIGN KEY (id) REFERENCES objects (id) + ON DELETE CASCADE + ON UPDATE CASCADE, + FOREIGN KEY (failure_model_id) REFERENCES failure_models (id) + ON DELETE CASCADE + ON UPDATE CASCADE, + + PRIMARY KEY (id) +); + +/* +* Cooling items are a type of object. +*/ + +-- Cooling items on tiles +DROP TABLE IF EXISTS cooling_items; +CREATE TABLE cooling_items ( + id INTEGER NOT NULL, + energy_consumption_w INTEGER NOT NULL CHECK (energy_consumption_w > 0), + type VARCHAR(50) NOT NULL, + failure_model_id INTEGER NOT NULL, + + FOREIGN KEY (id) REFERENCES objects (id) + ON DELETE CASCADE + ON UPDATE CASCADE, + FOREIGN KEY (failure_model_id) REFERENCES failure_models (id) + ON DELETE CASCADE + ON UPDATE CASCADE, + + PRIMARY KEY (id) +); + +/* +* Racks are a type of object. +*/ + +-- Racks on tiles +DROP TABLE IF EXISTS racks; +CREATE TABLE racks ( + id INTEGER NOT NULL, + name TEXT, + capacity INTEGER NOT NULL CHECK (capacity > 0), + power_capacity_w INTEGER NOT NULL CHECK (power_capacity_w > 0), + topology_id INTEGER, + + FOREIGN KEY (id) REFERENCES objects (id) + ON DELETE CASCADE + ON UPDATE CASCADE, + FOREIGN KEY (topology_id) REFERENCES racks (id) + ON DELETE NO ACTION + ON UPDATE CASCADE, + + PRIMARY KEY (id) +); + +/* +* A rack contains a number of machines. A rack cannot have more than its capacity of machines in it. No more than one +* machine can occupy a position in a rack at the same time. +*/ + +-- Machines in racks +DROP TABLE IF EXISTS machines; +CREATE TABLE machines ( + id INTEGER PRIMARY KEY NOT NULL AUTO_INCREMENT, + rack_id INTEGER NOT NULL, + position INTEGER NOT NULL CHECK (position > 0), + topology_id INTEGER, + + FOREIGN KEY (rack_id) REFERENCES racks (id) + ON DELETE CASCADE + ON UPDATE CASCADE, + FOREIGN KEY (topology_id) REFERENCES machines (id) + ON DELETE NO ACTION + ON UPDATE CASCADE, + + -- Prevent machines from occupying the same position in a rack. + UNIQUE (rack_id, position) +); + +DELIMITER // + +-- Make sure a machine is not inserted at a position that does not exist for its rack. +DROP TRIGGER IF EXISTS before_insert_machine; +CREATE TRIGGER before_insert_machine + BEFORE INSERT + ON machines + FOR EACH ROW + BEGIN + IF ( + NEW.position > (SELECT capacity + FROM racks + WHERE racks.id = NEW.rack_id) + ) + THEN + -- raise an error + SIGNAL SQLSTATE '45000' + SET MESSAGE_TEXT = 'InvalidMachinePosition'; + END IF; + END// + +DELIMITER ; + +/* +* A machine can have a tag for easy search and filtering. +*/ + +-- Tags for machines +DROP TABLE IF EXISTS machine_tags; +CREATE TABLE machine_tags ( + name TEXT NOT NULL, + machine_id INTEGER NOT NULL, + + FOREIGN KEY (machine_id) REFERENCES machines (id) + ON DELETE CASCADE + ON UPDATE CASCADE +); + +/* +* A failure model defines the probability of a machine breaking at any given time. +*/ + +-- Failure models +DROP TABLE IF EXISTS failure_models; +CREATE TABLE failure_models ( + id INTEGER PRIMARY KEY NOT NULL AUTO_INCREMENT, + name TEXT NOT NULL, + rate REAL NOT NULL CHECK (rate >= 0 AND rate <= 1) +); + +/* +* A cpu stores information about a type of cpu. The machine_cpu table keeps track of which cpus are in which machines. +*/ + +-- CPU specs +DROP TABLE IF EXISTS cpus; +CREATE TABLE cpus ( + id INTEGER PRIMARY KEY NOT NULL AUTO_INCREMENT, + manufacturer TEXT NOT NULL, + family TEXT NOT NULL, + generation TEXT NOT NULL, + model TEXT NOT NULL, + clock_rate_mhz INTEGER NOT NULL CHECK (clock_rate_mhz > 0), + number_of_cores INTEGER NOT NULL CHECK (number_of_cores > 0), + energy_consumption_w REAL NOT NULL CHECK (energy_consumption_w > 0), + failure_model_id INTEGER NOT NULL, + + FOREIGN KEY (failure_model_id) REFERENCES failure_models (id) + ON DELETE CASCADE + ON UPDATE CASCADE +); + +-- CPUs in machines +DROP TABLE IF EXISTS machine_cpus; +CREATE TABLE machine_cpus ( + id INTEGER PRIMARY KEY NOT NULL AUTO_INCREMENT, + machine_id INTEGER NOT NULL, + cpu_id INTEGER NOT NULL, + + FOREIGN KEY (machine_id) REFERENCES machines (id) + ON DELETE CASCADE + ON UPDATE CASCADE, + FOREIGN KEY (cpu_id) REFERENCES cpus (id) + ON DELETE CASCADE + ON UPDATE CASCADE +); + +/* +* A gpu stores information about a type of gpu. The machine_gpu table keeps track of which gpus are in which machines. +*/ + +-- GPU specs +DROP TABLE IF EXISTS gpus; +CREATE TABLE gpus ( + id INTEGER PRIMARY KEY NOT NULL AUTO_INCREMENT, + manufacturer TEXT NOT NULL, + family TEXT NOT NULL, + generation TEXT NOT NULL, + model TEXT NOT NULL, + clock_rate_mhz INTEGER NOT NULL CHECK (clock_rate_mhz > 0), + number_of_cores INTEGER NOT NULL CHECK (number_of_cores > 0), + energy_consumption_w REAL NOT NULL CHECK (energy_consumption_w > 0), + failure_model_id INTEGER NOT NULL, + + FOREIGN KEY (failure_model_id) REFERENCES failure_models (id) + ON DELETE CASCADE + ON UPDATE CASCADE +); + +-- GPUs in machines +DROP TABLE IF EXISTS machine_gpus; +CREATE TABLE machine_gpus ( + id INTEGER PRIMARY KEY NOT NULL AUTO_INCREMENT, + machine_id INTEGER NOT NULL, + gpu_id INTEGER NOT NULL, + + FOREIGN KEY (machine_id) REFERENCES machines (id) + ON DELETE CASCADE + ON UPDATE CASCADE, + FOREIGN KEY (gpu_id) REFERENCES gpus (id) + ON DELETE CASCADE + ON UPDATE CASCADE +); + +/* +* A memory stores information about a type of memory. The machine_memory table keeps track of which memories are in +* which machines. +*/ + +-- Memory specs +DROP TABLE IF EXISTS memories; +CREATE TABLE memories ( + id INTEGER PRIMARY KEY NOT NULL AUTO_INCREMENT, + manufacturer TEXT NOT NULL, + family TEXT NOT NULL, + generation TEXT NOT NULL, + model TEXT NOT NULL, + speed_mb_per_s INTEGER NOT NULL CHECK (speed_mb_per_s > 0), + size_mb INTEGER NOT NULL CHECK (size_mb > 0), + energy_consumption_w REAL NOT NULL CHECK (energy_consumption_w > 0), + failure_model_id INTEGER NOT NULL, + + FOREIGN KEY (failure_model_id) REFERENCES failure_models (id) + ON DELETE CASCADE + ON UPDATE CASCADE +); + +-- Memory in machines +DROP TABLE IF EXISTS machine_memories; +CREATE TABLE machine_memories ( + id INTEGER PRIMARY KEY NOT NULL AUTO_INCREMENT, + machine_id INTEGER NOT NULL, + memory_id INTEGER NOT NULL, + + FOREIGN KEY (machine_id) REFERENCES machines (id) + ON DELETE CASCADE + ON UPDATE CASCADE, + FOREIGN KEY (memory_id) REFERENCES memories (id) + ON DELETE CASCADE + ON UPDATE CASCADE +); + +/* +* A storage stores information about a type of storage. The machine_storage table keeps track of which storages are in +* which machines. +*/ + +-- Storage specs +DROP TABLE IF EXISTS storages; +CREATE TABLE storages ( + id INTEGER PRIMARY KEY NOT NULL AUTO_INCREMENT, + manufacturer TEXT NOT NULL, + family TEXT NOT NULL, + generation TEXT NOT NULL, + model TEXT NOT NULL, + speed_mb_per_s INTEGER NOT NULL CHECK (speed_mb_per_s > 0), + size_mb INTEGER NOT NULL CHECK (size_mb > 0), + energy_consumption_w REAL NOT NULL CHECK (energy_consumption_w > 0), + failure_model_id INTEGER NOT NULL, + + FOREIGN KEY (failure_model_id) REFERENCES failure_models (id) + ON DELETE CASCADE + ON UPDATE CASCADE +); + +-- Storage in machines +DROP TABLE IF EXISTS machine_storages; +CREATE TABLE machine_storages ( + id INTEGER PRIMARY KEY NOT NULL AUTO_INCREMENT, + machine_id INTEGER NOT NULL, + storage_id INTEGER NOT NULL, + + FOREIGN KEY (machine_id) REFERENCES machines (id) + ON DELETE CASCADE + ON UPDATE CASCADE, + FOREIGN KEY (storage_id) REFERENCES storages (id) + ON DELETE CASCADE + ON UPDATE CASCADE +); diff --git a/database/test.sql b/database/test.sql new file mode 100644 index 00000000..55801b76 --- /dev/null +++ b/database/test.sql @@ -0,0 +1,381 @@ +-- Users +INSERT INTO users (google_id, email, given_name, family_name) +VALUES ('106671218963420759042', 'l.overweel@gmail.com', 'Leon', 'Overweel'); +INSERT INTO users (google_id, email, given_name, family_name) +VALUES ('118147174005839766927', 'jorgos.andreadis@gmail.com', 'Jorgos', 'Andreadis'); + +-- Simulations +INSERT INTO simulations (name, datetime_created, datetime_last_edited) +VALUES ('Test Simulation 1', '2016-07-11T11:00:00', '2016-07-11T11:00:00'); + +-- Authorizations +INSERT INTO authorizations (user_id, simulation_id, authorization_level) +VALUES (1, 1, 'OWN'); +INSERT INTO authorizations (user_id, simulation_id, authorization_level) +VALUES (2, 1, 'OWN'); + +-- Paths +INSERT INTO paths (simulation_id, datetime_created) +VALUES (1, '2016-07-11T11:00:00'); +INSERT INTO paths (simulation_id, datetime_created) +VALUES (1, '2016-07-18T09:00:00'); + +-- Datacenter +INSERT INTO datacenters (starred, simulation_id) VALUES (0, 1); +INSERT INTO datacenters (starred, simulation_id) VALUES (0, 1); +INSERT INTO datacenters (starred, simulation_id) VALUES (0, 1); + +-- Sections +INSERT INTO sections (path_id, datacenter_id, start_tick) VALUES (1, 1, 0); +INSERT INTO sections (path_id, datacenter_id, start_tick) VALUES (1, 2, 50); +INSERT INTO sections (path_id, datacenter_id, start_tick) VALUES (1, 3, 100); + +INSERT INTO sections (path_id, datacenter_id, start_tick) VALUES (2, 3, 0); + +-- Default Test Trace +INSERT INTO traces (name) VALUES ('Default'); + +-- Jobs +INSERT INTO jobs (name, trace_id) VALUES ('Default', 1); + +-- Tasks +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (0, 400000, 1, 1); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (25, 10000, 1, 1); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (25, 10000, 1, 1); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (26, 10000, 1, 1); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (80, 200000, 1, 1); + +INSERT INTO task_dependencies (first_task_id, second_task_id) VALUES (1, 5); + +-- Image Processing Trace +INSERT INTO traces (name) VALUES ('Image Processing'); + +-- Jobs +INSERT INTO jobs (name, trace_id) VALUES ('Image Processing', 2); + +-- Tasks +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (0, 100000, 1, 2); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (10, 100000, 1, 2); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (20, 100000, 1, 2); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (0, 100000, 1, 2); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (10, 100000, 1, 2); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (20, 100000, 1, 2); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (1, 100000, 1, 2); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (11, 100000, 1, 2); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (21, 100000, 1, 2); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (1, 100000, 1, 2); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (11, 100000, 1, 2); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (21, 100000, 1, 2); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (0, 100000, 1, 2); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (10, 100000, 1, 2); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (20, 100000, 1, 2); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (0, 100000, 1, 2); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (10, 100000, 1, 2); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (20, 100000, 1, 2); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (1, 100000, 1, 2); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (11, 100000, 1, 2); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (21, 100000, 1, 2); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (1, 100000, 1, 2); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (11, 100000, 1, 2); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (21, 100000, 1, 2); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (0, 100000, 1, 2); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (10, 100000, 1, 2); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (20, 100000, 1, 2); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (0, 100000, 1, 2); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (10, 100000, 1, 2); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (20, 100000, 1, 2); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (1, 100000, 1, 2); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (11, 100000, 1, 2); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (21, 100000, 1, 2); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (1, 100000, 1, 2); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (11, 100000, 1, 2); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (21, 100000, 1, 2); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (0, 100000, 1, 2); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (10, 100000, 1, 2); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (20, 100000, 1, 2); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (0, 100000, 1, 2); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (10, 100000, 1, 2); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (20, 100000, 1, 2); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (1, 100000, 1, 2); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (11, 100000, 1, 2); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (21, 100000, 1, 2); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (1, 100000, 1, 2); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (11, 100000, 1, 2); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (21, 100000, 1, 2); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (0, 100000, 1, 2); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (10, 100000, 1, 2); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (20, 100000, 1, 2); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (0, 100000, 1, 2); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (10, 100000, 1, 2); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (20, 100000, 1, 2); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (1, 100000, 1, 2); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (11, 100000, 1, 2); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (21, 100000, 1, 2); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (1, 100000, 1, 2); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (11, 100000, 1, 2); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (21, 100000, 1, 2); + +-- Path Planning Trace +INSERT INTO traces (name) VALUES ('Path planning'); + +-- Jobs +INSERT INTO jobs (name, trace_id) VALUES ('Path planning', 3); + +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (0, 1000000, 1, 3); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (11, 200000, 1, 3); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (12, 200000, 1, 3); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (13, 200000, 1, 3); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (14, 200000, 1, 3); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (11, 200000, 1, 3); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (12, 200000, 1, 3); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (13, 200000, 1, 3); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (14, 200000, 1, 3); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (11, 200000, 1, 3); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (12, 200000, 1, 3); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (13, 200000, 1, 3); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (14, 200000, 1, 3); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (11, 200000, 1, 3); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (12, 200000, 1, 3); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (13, 200000, 1, 3); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (14, 200000, 1, 3); + +INSERT INTO task_dependencies (first_task_id, second_task_id) VALUES (66, 67); +INSERT INTO task_dependencies (first_task_id, second_task_id) VALUES (66, 68); +INSERT INTO task_dependencies (first_task_id, second_task_id) VALUES (66, 69); +INSERT INTO task_dependencies (first_task_id, second_task_id) VALUES (66, 70); +INSERT INTO task_dependencies (first_task_id, second_task_id) VALUES (66, 71); +INSERT INTO task_dependencies (first_task_id, second_task_id) VALUES (66, 72); +INSERT INTO task_dependencies (first_task_id, second_task_id) VALUES (66, 73); +INSERT INTO task_dependencies (first_task_id, second_task_id) VALUES (66, 74); +INSERT INTO task_dependencies (first_task_id, second_task_id) VALUES (66, 75); +INSERT INTO task_dependencies (first_task_id, second_task_id) VALUES (66, 76); +INSERT INTO task_dependencies (first_task_id, second_task_id) VALUES (66, 77); +INSERT INTO task_dependencies (first_task_id, second_task_id) VALUES (66, 78); +INSERT INTO task_dependencies (first_task_id, second_task_id) VALUES (66, 79); +INSERT INTO task_dependencies (first_task_id, second_task_id) VALUES (66, 80); +INSERT INTO task_dependencies (first_task_id, second_task_id) VALUES (66, 81); +INSERT INTO task_dependencies (first_task_id, second_task_id) VALUES (66, 82); + +-- Parallelizable Trace +INSERT INTO traces (name) VALUES ('Parallel heavy trace'); + +-- Jobs +INSERT INTO jobs (name, trace_id) VALUES ('Parallel heavy trace', 4); + +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (0, 100000, 1, 4); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (0, 900000, 1, 4); + +-- Sequential Trace +INSERT INTO traces (name) VALUES ('Sequential heavy trace'); + +-- Jobs +INSERT INTO jobs (name, trace_id) VALUES ('Sequential heavy trace', 5); + +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (0, 100000, 1, 5); +INSERT INTO tasks (start_tick, total_flop_count, core_count, job_id) VALUES (0, 900000, 1, 5); + +-- Experiments +INSERT INTO experiments (simulation_id, path_id, trace_id, scheduler_name, name, state, last_simulated_tick) +VALUES (1, 1, 3, 'fifo-bestfit', 'Path planning trace, FIFO', 'QUEUED', 0); +INSERT INTO experiments (simulation_id, path_id, trace_id, scheduler_name, name, state, last_simulated_tick) +VALUES (1, 1, 1, 'srtf-firstfit', 'Default trace, SRTF', 'QUEUED', 0); +INSERT INTO experiments (simulation_id, path_id, trace_id, scheduler_name, name, state, last_simulated_tick) +VALUES (1, 1, 2, 'srtf-firstfit', 'Image processing trace, SRTF', 'QUEUED', 0); +INSERT INTO experiments (simulation_id, path_id, trace_id, scheduler_name, name, state, last_simulated_tick) +VALUES (1, 1, 3, 'fifo-firstfit', 'Path planning trace, FIFO', 'QUEUED', 0); + +-- Rooms +INSERT INTO rooms (name, datacenter_id, type) VALUES ('room 1', 1, 'SERVER'); +INSERT INTO rooms (name, datacenter_id, type, topology_id) VALUES ('room 1', 2, 'SERVER', 1); +INSERT INTO rooms (name, datacenter_id, type, topology_id) VALUES ('room 1', 3, 'SERVER', 1); +INSERT INTO rooms (name, datacenter_id, type) VALUES ('room 2', 3, 'SERVER'); +INSERT INTO rooms (name, datacenter_id, type) VALUES ('Power Room', 1, 'POWER'); + +-- Tiles +INSERT INTO tiles (position_x, position_y, room_id) VALUES (10, 10, 1); +INSERT INTO tiles (position_x, position_y, room_id) VALUES (9, 10, 1); +INSERT INTO tiles (position_x, position_y, room_id) VALUES (10, 11, 1); + +INSERT INTO tiles (position_x, position_y, room_id, topology_id) VALUES (10, 10, 2, 1); +INSERT INTO tiles (position_x, position_y, room_id, topology_id) VALUES (9, 10, 2, 2); +INSERT INTO tiles (position_x, position_y, room_id, topology_id) VALUES (10, 11, 2, 3); +INSERT INTO tiles (position_x, position_y, room_id) VALUES (11, 11, 2); + +INSERT INTO tiles (position_x, position_y, room_id, topology_id) VALUES (10, 10, 3, 1); +INSERT INTO tiles (position_x, position_y, room_id, topology_id) VALUES (9, 10, 3, 2); +INSERT INTO tiles (position_x, position_y, room_id, topology_id) VALUES (10, 11, 3, 3); +INSERT INTO tiles (position_x, position_y, room_id, topology_id) VALUES (11, 11, 3, 7); + +INSERT INTO tiles (position_x, position_y, room_id) VALUES (11, 10, 4); +INSERT INTO tiles (position_x, position_y, room_id) VALUES (12, 10, 4); + +INSERT INTO tiles (position_x, position_y, room_id) VALUES (10, 12, 5); +INSERT INTO tiles (position_x, position_y, room_id) VALUES (10, 13, 5); + +-- Racks +INSERT INTO objects (type) VALUES ('RACK'); +INSERT INTO racks (id, capacity, name, power_capacity_w) VALUES (1, 42, 'Rack 1', 5000); +UPDATE tiles +SET object_id = 1 +WHERE id = 1; +INSERT INTO objects (type) VALUES ('RACK'); +INSERT INTO racks (id, capacity, name, power_capacity_w) VALUES (2, 42, 'Rack 2', 5000); +UPDATE tiles +SET object_id = 2 +WHERE id = 2; + +INSERT INTO objects (type) VALUES ('RACK'); +INSERT INTO racks (id, capacity, name, power_capacity_w, topology_id) VALUES (3, 42, 'Rack 1', 5000, 1); +UPDATE tiles +SET object_id = 3 +WHERE id = 4; +INSERT INTO objects (type) VALUES ('RACK'); +INSERT INTO racks (id, capacity, name, power_capacity_w, topology_id) VALUES (4, 42, 'Rack 2', 5000, 2); +UPDATE tiles +SET object_id = 4 +WHERE id = 5; +INSERT INTO objects (type) VALUES ('RACK'); +INSERT INTO racks (id, capacity, name, power_capacity_w) VALUES (5, 42, 'Rack 3', 5000); +UPDATE tiles +SET object_id = 5 +WHERE id = 7; + +INSERT INTO objects (type) VALUES ('RACK'); +INSERT INTO racks (id, capacity, name, power_capacity_w, topology_id) VALUES (6, 42, 'Rack 1', 5000, 1); +UPDATE tiles +SET object_id = 6 +WHERE id = 8; + +INSERT INTO objects (type) VALUES ('RACK'); +INSERT INTO racks (id, capacity, name, power_capacity_w, topology_id) VALUES (7, 42, 'Rack 2', 5000, 2); +UPDATE tiles +SET object_id = 7 +WHERE id = 9; + +INSERT INTO objects (type) VALUES ('RACK'); +INSERT INTO racks (id, capacity, name, power_capacity_w, topology_id) VALUES (8, 42, 'Rack 3', 5000, 5); +UPDATE tiles +SET object_id = 8 +WHERE id = 11; + +INSERT INTO objects (type) VALUES ('RACK'); +INSERT INTO racks (id, capacity, name, power_capacity_w) VALUES (9, 42, 'Rack 4', 5000); +UPDATE tiles +SET object_id = 9 +WHERE id = 12; + +-- Machines +INSERT INTO machines (rack_id, position) VALUES (1, 1); +INSERT INTO machines (rack_id, position) VALUES (1, 2); +INSERT INTO machines (rack_id, position) VALUES (1, 6); +INSERT INTO machines (rack_id, position) VALUES (1, 10); +INSERT INTO machines (rack_id, position) VALUES (2, 1); +INSERT INTO machines (rack_id, position) VALUES (2, 2); + +INSERT INTO machines (rack_id, position, topology_id) VALUES (3, 1, 1); +INSERT INTO machines (rack_id, position, topology_id) VALUES (3, 2, 2); +INSERT INTO machines (rack_id, position, topology_id) VALUES (3, 6, 3); +INSERT INTO machines (rack_id, position, topology_id) VALUES (3, 10, 4); +INSERT INTO machines (rack_id, position, topology_id) VALUES (4, 1, 5); +INSERT INTO machines (rack_id, position, topology_id) VALUES (4, 2, 6); +INSERT INTO machines (rack_id, position) VALUES (5, 1); +INSERT INTO machines (rack_id, position) VALUES (5, 2); +INSERT INTO machines (rack_id, position) VALUES (5, 3); + +INSERT INTO machines (rack_id, position, topology_id) VALUES (6, 1, 1); +INSERT INTO machines (rack_id, position, topology_id) VALUES (6, 2, 2); +INSERT INTO machines (rack_id, position, topology_id) VALUES (6, 6, 3); +INSERT INTO machines (rack_id, position, topology_id) VALUES (6, 10, 4); +INSERT INTO machines (rack_id, position, topology_id) VALUES (7, 1, 5); +INSERT INTO machines (rack_id, position, topology_id) VALUES (7, 2, 6); +INSERT INTO machines (rack_id, position, topology_id) VALUES (8, 1, 13); +INSERT INTO machines (rack_id, position, topology_id) VALUES (8, 2, 14); +INSERT INTO machines (rack_id, position, topology_id) VALUES (8, 3, 15); +INSERT INTO machines (rack_id, position) VALUES (9, 4); +INSERT INTO machines (rack_id, position) VALUES (9, 5); +INSERT INTO machines (rack_id, position) VALUES (9, 6); +INSERT INTO machines (rack_id, position) VALUES (9, 7); + +-- Tags +INSERT INTO machine_tags (name, machine_id) VALUES ('my fave machine', 1); +INSERT INTO machine_tags (name, machine_id) VALUES ('my best machine', 2); + +-- Failure models +INSERT INTO failure_models (name, rate) VALUES ('test_model', 0); + +-- CPUs +INSERT INTO cpus (manufacturer, family, generation, model, clock_rate_mhz, number_of_cores, energy_consumption_w, + failure_model_id) VALUES ('intel', 'i7', 'v6', '6700k', 4100, 4, 70, 1); +INSERT INTO cpus (manufacturer, family, generation, model, clock_rate_mhz, number_of_cores, energy_consumption_w, + failure_model_id) VALUES ('intel', 'i5', 'v6', '6700k', 3500, 2, 50, 1); + +-- GPUs +INSERT INTO gpus (manufacturer, family, generation, model, clock_rate_mhz, number_of_cores, energy_consumption_w, + failure_model_id) VALUES ('NVIDIA', 'GTX', '4', '1080', 1200, 200, 250, 1); + +-- CPUs in machines +INSERT INTO machine_cpus (machine_id, cpu_id) VALUES (1, 1); +INSERT INTO machine_cpus (machine_id, cpu_id) VALUES (1, 1); +INSERT INTO machine_cpus (machine_id, cpu_id) VALUES (1, 2); +INSERT INTO machine_cpus (machine_id, cpu_id) VALUES (2, 2); +INSERT INTO machine_cpus (machine_id, cpu_id) VALUES (2, 2); +INSERT INTO machine_cpus (machine_id, cpu_id) VALUES (3, 1); +INSERT INTO machine_cpus (machine_id, cpu_id) VALUES (3, 2); +INSERT INTO machine_cpus (machine_id, cpu_id) VALUES (3, 1); +INSERT INTO machine_cpus (machine_id, cpu_id) VALUES (4, 1); +INSERT INTO machine_cpus (machine_id, cpu_id) VALUES (4, 1); +INSERT INTO machine_cpus (machine_id, cpu_id) VALUES (4, 1); +INSERT INTO machine_cpus (machine_id, cpu_id) VALUES (5, 1); +INSERT INTO machine_cpus (machine_id, cpu_id) VALUES (6, 1); + +INSERT INTO machine_cpus (machine_id, cpu_id) VALUES (7, 1); +INSERT INTO machine_cpus (machine_id, cpu_id) VALUES (7, 1); +INSERT INTO machine_cpus (machine_id, cpu_id) VALUES (7, 2); +INSERT INTO machine_cpus (machine_id, cpu_id) VALUES (8, 2); +INSERT INTO machine_cpus (machine_id, cpu_id) VALUES (8, 2); +INSERT INTO machine_cpus (machine_id, cpu_id) VALUES (9, 1); +INSERT INTO machine_cpus (machine_id, cpu_id) VALUES (9, 2); +INSERT INTO machine_cpus (machine_id, cpu_id) VALUES (9, 1); +INSERT INTO machine_cpus (machine_id, cpu_id) VALUES (10, 1); +INSERT INTO machine_cpus (machine_id, cpu_id) VALUES (10, 1); +INSERT INTO machine_cpus (machine_id, cpu_id) VALUES (10, 1); +INSERT INTO machine_cpus (machine_id, cpu_id) VALUES (11, 1); +INSERT INTO machine_cpus (machine_id, cpu_id) VALUES (12, 1); +INSERT INTO machine_cpus (machine_id, cpu_id) VALUES (13, 1); +INSERT INTO machine_cpus (machine_id, cpu_id) VALUES (14, 1); +INSERT INTO machine_cpus (machine_id, cpu_id) VALUES (15, 1); + +INSERT INTO machine_cpus (machine_id, cpu_id) VALUES (16, 1); +INSERT INTO machine_cpus (machine_id, cpu_id) VALUES (16, 1); +INSERT INTO machine_cpus (machine_id, cpu_id) VALUES (16, 2); +INSERT INTO machine_cpus (machine_id, cpu_id) VALUES (17, 2); +INSERT INTO machine_cpus (machine_id, cpu_id) VALUES (17, 2); +INSERT INTO machine_cpus (machine_id, cpu_id) VALUES (18, 1); +INSERT INTO machine_cpus (machine_id, cpu_id) VALUES (18, 2); +INSERT INTO machine_cpus (machine_id, cpu_id) VALUES (18, 1); +INSERT INTO machine_cpus (machine_id, cpu_id) VALUES (19, 1); +INSERT INTO machine_cpus (machine_id, cpu_id) VALUES (19, 1); +INSERT INTO machine_cpus (machine_id, cpu_id) VALUES (19, 1); +INSERT INTO machine_cpus (machine_id, cpu_id) VALUES (20, 1); +INSERT INTO machine_cpus (machine_id, cpu_id) VALUES (21, 1); +INSERT INTO machine_cpus (machine_id, cpu_id) VALUES (22, 1); +INSERT INTO machine_cpus (machine_id, cpu_id) VALUES (23, 1); +INSERT INTO machine_cpus (machine_id, cpu_id) VALUES (24, 1); +INSERT INTO machine_cpus (machine_id, cpu_id) VALUES (25, 2); +INSERT INTO machine_cpus (machine_id, cpu_id) VALUES (26, 2); +INSERT INTO machine_cpus (machine_id, cpu_id) VALUES (27, 2); +INSERT INTO machine_cpus (machine_id, cpu_id) VALUES (28, 2); + +-- GPUs +INSERT INTO gpus (manufacturer, family, generation, model, clock_rate_mhz, number_of_cores, energy_consumption_w, + failure_model_id) VALUES ('nvidia', 'GeForce GTX Series', '10', '80', 1607, 2560, 70, 1); + +-- Memories + +INSERT INTO memories (manufacturer, family, generation, model, speed_mb_per_s, size_mb, energy_consumption_w, + failure_model_id) VALUES ('samsung', 'PC DRAM', 'K4A4G045WD', 'DDR4', 16000, 4000, 10, 1); + +-- Storages + +INSERT INTO storages (manufacturer, family, generation, model, speed_mb_per_s, size_mb, energy_consumption_w, + failure_model_id) VALUES ('samsung', 'EVO', '2016', 'SATA III', 6000, 250000, 10, 1); diff --git a/database/view-table.py b/database/view-table.py new file mode 100644 index 00000000..615b4081 --- /dev/null +++ b/database/view-table.py @@ -0,0 +1,17 @@ +import os +import sqlite3 +import sys + +try: + BASE_DIR = directory_name=sys.argv[1] +except: + BASE_DIR = os.path.dirname(os.path.abspath(__file__)) +db_location = os.path.join(BASE_DIR, 'opendc.db') + +conn = sqlite3.connect(db_location) +c = conn.cursor() + +rows = c.execute('SELECT * FROM ' + sys.argv[2]) + +for row in rows: + print row diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..3f4ad20a --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,84 @@ +version: "3" +services: + frontend: + build: ./ + image: frontend + restart: on-failure + ports: + - "8081:8081" + links: + - mariadb + depends_on: + - mariadb + environment: + - MYSQL_DATABASE=opendc + - MYSQL_USER=opendc + - MYSQL_PASSWORD=opendcpassword + - MONGO_DB=opendc + - MONGO_DB_USERNAME=opendc + - MONGO_DB_PASSWORD=opendcpassword + - MONGO_DB_HOST=mongo + - MONGO_DB_PORT=27017 + + simulator: + build: + context: ./opendc-simulator + dockerfile: opendc-model-odc/setup/Dockerfile + image: simulator + restart: on-failure + links: + - mariadb + depends_on: + - mariadb + environment: + - PERSISTENCE_URL=jdbc:mysql://mariadb:3306/opendc + - PERSISTENCE_USER=opendc + - PERSISTENCE_PASSWORD=opendcpassword + - COLLECT_MACHINE_STATES=ON + - COLLECT_TASK_STATES=ON + - COLLECT_STAGE_MEASUREMENTS=OFF + - COLLECT_TASK_METRICS=OFF + - COLLECT_JOB_METRICS=OFF + mariadb: + build: + context: ./database + image: database + restart: on-failure + ports: + - "3306:3306" # comment this line out in production + environment: + - MYSQL_DATABASE=opendc + - MYSQL_USER=opendc + - MYSQL_PASSWORD=opendcpassword + - MYSQL_RANDOM_ROOT_PASSWORD=yes + # uncomment in production + # volumes: + # - "/data/mariadb:/var/lib/mysql" + mongo: + build: + context: ./mongodb + restart: on-failure + environment: + - MONGO_INITDB_ROOT_USERNAME=root + - MONGO_INITDB_ROOT_PASSWORD=rootpassword + - MONGO_INITDB_DATABASE=admin + - OPENDC_DB=opendc + - OPENDC_DB_USERNAME=opendc + - OPENDC_DB_PASSWORD=opendcpassword + ports: + - 27017:27017 + #volumes: + # - mongo-volume:/data/db + + mongo-express: + image: mongo-express + restart: on-failure + ports: + - 8082:8081 + environment: + ME_CONFIG_MONGODB_ADMINUSERNAME: root + ME_CONFIG_MONGODB_ADMINPASSWORD: rootpassword + +volumes: + mongo-volume: + external: false
\ No newline at end of file diff --git a/frontend/.editorconfig b/frontend/.editorconfig new file mode 100644 index 00000000..823e6853 --- /dev/null +++ b/frontend/.editorconfig @@ -0,0 +1,15 @@ +# EditorConfig helps developers define and maintain consistent +# coding styles between different editors and IDEs +# editorconfig.org +root = true + +[*] +indent_style = space +indent_size = 2 + +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 00000000..415295d9 --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,28 @@ +# Dependencies +/node_modules + +# Testing +/coverage + +# Production +/build + +# Misc. +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# IntelliJ IDEA +/.idea + +# Environment variables +.env + +# Sass output +*.css diff --git a/frontend/.travis.yml b/frontend/.travis.yml new file mode 100644 index 00000000..c3554fe4 --- /dev/null +++ b/frontend/.travis.yml @@ -0,0 +1,9 @@ +language: node_js +node_js: + - 10 +cache: + directories: + - node_modules +script: + - npm run build + - npm test diff --git a/frontend/CONTRIBUTING.md b/frontend/CONTRIBUTING.md new file mode 100644 index 00000000..152ab5aa --- /dev/null +++ b/frontend/CONTRIBUTING.md @@ -0,0 +1,28 @@ +# Contributing to the OpenDC Frontend + +First of all, thanks for wanting to contribute! 🎉 + + +## 💬 Have a question or general feedback relating to the OpenDC Frontend? + +Contact us at 📧[opendc@atlarge-research.com](mailto:opendc@atlarge-research.com)! + + +## 🐞 Want to report a bug or suggest a feature? + +Encountered what you deem to be undesirable behavior? Have an idea for a feature that could be added? Please go to our [GitHub issues page](https://github.com/atlarge-research/opendc-frontend/issues) and have a look if there already is an issue addressing your concern. + +If there already is an issue, feel free to comment on the issue to show your support for it, or to add additional information that might be helpful. You can also just react with a thumbs-up 👍 to the issue or feature, to indicate that you'd be interested in its resolution. This can help us prioritize what we spend our development time on. + +If you can't find an issue that fits your problem or feature request, [open a new one](https://github.com/atlarge-research/opendc-frontend/issues/new). Describe actual and expected behavior, and be as detailed as you can. We'll get back to you asap. + + +## 💻 Want to contribute code? + +Great! [Fork this repo](https://github.com/atlarge-research/opendc-frontend/new/master) and submit a PR here when you're ready! Be sure to describe *what* you changed and *why* you changed it, to help us understand what your contribution is about. + +* A couple of notes on the code itself: + * Before you make changes to the codebase, have a look at the rest of the codebase (especially parts that are similar to what you want to achieve). Do you understand what they do? If not, feel free to [contact us](mailto:opendc@atlarge-research.com). + * Try to write clean, concise, self-documenting code. + * Don't worry too much about the formatting of your code: our `prettier` pre-commit hook takes care of formatting your code for you, automatically. +* A quick note on commit messages: Please follow common Git standards when writing commit messages, see [this post](https://chris.beams.io/posts/git-commit/) for details. diff --git a/frontend/LICENSE.md b/frontend/LICENSE.md new file mode 100644 index 00000000..57288ae2 --- /dev/null +++ b/frontend/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 atlarge-research + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 00000000..44858b69 --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,102 @@ +<h1 align="center"> + <img src="public/img/logo.png" width="100" alt="OpenDC"> + <br> + OpenDC Frontend +</h1> +<p align="center"> + Collaborative Datacenter Simulation and Exploration for Everybody +</p> + +<p align="center"> + <a href="https://travis-ci.org/atlarge-research/opendc-frontend"><img src="https://travis-ci.org/atlarge-research/opendc-frontend.svg?branch=master" alt="Build Status"></a> + <a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a> + <a href="https://github.com/prettier/prettier"><img src="https://img.shields.io/badge/styled_with-prettier-ff69b4.svg" alt="styled with prettier"></a><br/> +</p> + +The user-facing component of the OpenDC stack, allowing users to build and interact with their own (virtual) datacenters. Built in *React.js* and *Redux*, with the help of `create-react-app`. + + +## Get Up and Running + +Looking for the full OpenDC stack? Check out [the main OpenDC repo](https://github.com/atlarge-research/opendc) for instructions on how to set up a Docker container with all of OpenDC, without the hassle of running each of the components manually. + +### Installation + +To get started, you'll need the [Node.js environment](https://nodejs.org) and the [Yarn package manager](https://yarnpkg.com). Once you have those installed, run the following command from the root directory of this repo: + +```bash +yarn +``` + +### Running the development server + +First, you need to have a Google OAuth client ID set up. Check the [documentation of the main OpenDC repo](https://github.com/atlarge-research/opendc) if you're not sure how to do this. Once you have such an ID, you need to set it as environment variable `REACT_APP_OAUTH_CLIENT_ID`. One way of doing this is to create an `.env` file with content `REACT_APP_OAUTH_CLIENT_ID=YOUR_ID` (`YOUR_ID` without quotes), in the root directory of this repo. + +Once you've set this variable, you're ready to start the development server: + +```bash +yarn start +``` + +This will start a development server running on [`localhost:3000`](http://localhost:3000), watching for changes you make to the code and rebuilding automatically when you save changes. + +To compile everything for camera-ready deployment, use the following command: + +```bash +yarn build +``` + +**Note:** Perhaps this goes without saying, but for any functionality beyond visiting the entry page, a server backend running in the background is necessary. The easiest way to do this is to have an OpenDC docker container running, see [the main repo](https://github.com/atlarge-research/opendc) for more information on how to do this. + + +## Architecture + +The codebase follows a standard React.js structure, with static assets being contained in the `public` folder, while dynamic components and their styles are contained in `src`. The app uses client-side routing (with `react-router`), meaning that the only HTML file needed to be served is a `index.html` file. + +### Pages + +All pages are represented by a component in the `src/pages` directory. There are components for the following pages: + +**Home.js** - Entry page (`/`) + +**Simulations.js** - Overview of simulations of the user (`/simulations`) + +**App.js** - Main application, with datacenter construction and simulation UI (`/simulations/:simulationId` and `/simulations/:simulationId/experiments/:experimentId`) + +**Experiments.js** - Overview of experiments of the current simulation (`/simulations/:simulationId/experiments`) + +**Profile.js** - Profile of the current user (`/profile`) + +**NotFound.js** - 404 page to appear when the route is invalid (`/*`) + +### Components & Containers + +The building blocks of the UI are divided into so-called *components* and *containers* ([as encouraged](https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0) by the author of Redux). *Components* are considered 'pure', rendered as a function of input properties. *Containers*, on the other hand, are wrappers around *components*, injecting state through the properties of the components they wrap. + +Even the canvas (the main component of the app) is built using React components, with the help of the `react-konva` module. To illustrate: A rectangular object on the canvas is defined in a way that is not very different from how we define a standard `div` element on the splashpage. + +### State Management + +Almost all state is kept in a central Redux store. State is kept there in an immutable form, only to be modified through actions being dispatched. These actions are contained in the `src/actions` folder, and the reducers (managing how state is updated according to dispatched actions) are located in `src/reducers`. If you're not familiar with the Redux approach to state management, have a look at their [official documentation](http://redux.js.org/). + +### API Interaction + +The web-app needs to pull data in from the API of a backend running on a server. The functions that call routes are located in `src/api`. The actual logic responsible for calling these functions is contained in `src/sagas`. These API fetch procedures are written with the help of `redux-saga`. The [official documentation](https://redux-saga.js.org/) of `redux-saga` can be a helpful aid in understanding that part of the codebase. + + +## Tests + +Files containing tests can be recognized by the `.test.js` suffix. They are usually located right next to the source code they are testing, to make discovery easier. + +### Running all tests + +The following command runs all tests in the codebase. On top of this, it also watches the code for changes and reruns the tests whenever any file is saved. + +```bash +yarn test +``` + + +## License + +The code is released under the MIT license. See `LICENSE.md`. diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 00000000..692ca8ad --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,77 @@ +{ + "name": "opendc-frontend", + "version": "0.1.0", + "description": "The user-facing component of the OpenDC stack, allowing users to build and interact with their own (virtual) datacenters.", + "keywords": [ + "opendc", + "simulation", + "datacenter", + "frontend" + ], + "homepage": "http://opendc.org", + "bugs": { + "url": "https://github.com/atlarge-research/opendc-frontend/issues", + "email": "opendc@atlarge-research.com" + }, + "author": "Georgios Andreadis <g.andreadis@atlarge-research.com> (http://gandreadis.com/)", + "license": "MIT", + "private": true, + "proxy": "http://localhost:8081", + "dependencies": { + "approximate-number": "~2.0.0", + "classnames": "~2.2.5", + "husky": "~4.2.5", + "konva": "~6.0.0", + "lint-staged": "~10.2.2", + "node-sass-chokidar": "~1.4.0", + "npm-run-all": "~4.1.2", + "prettier": "~2.0.5", + "prop-types": "~15.7.2", + "react": "~16.13.1", + "react-document-title": "~2.0.3", + "react-dom": "~16.13.1", + "react-fontawesome": "~1.7.1", + "react-google-login": "~5.1.14", + "react-konva": "~16.13.0-2", + "react-redux": "~7.2.0", + "react-router-dom": "~5.1.2", + "react-scripts": "~3.4.1", + "react-shortcuts": "~2.1.0", + "redux": "~4.0.5", + "redux-localstorage": "~0.4.1", + "redux-logger": "~3.0.6", + "redux-saga": "~1.1.3", + "redux-thunk": "~2.3.0", + "socket.io-client": "~2.3.0", + "svgsaver": "~0.9.0", + "victory": "~34.2.1" + }, + "lint-staged": { + "src/**/*.{js,jsx,json}": [ + "prettier --write", + "git add" + ] + }, + "scripts": { + "precommit": "lint-staged", + "build-css": "node-sass-chokidar src/ -o src/", + "watch-css": "npm run build-css && node-sass-chokidar src/ -o src/ --watch --recursive", + "start-js": "react-scripts start", + "start": "npm-run-all -p watch-css start-js", + "build": "npm run build-css && react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + } +} diff --git a/frontend/public/favicon.ico b/frontend/public/favicon.ico Binary files differnew file mode 100644 index 00000000..c2f40a0d --- /dev/null +++ b/frontend/public/favicon.ico diff --git a/frontend/public/humans.txt b/frontend/public/humans.txt new file mode 100644 index 00000000..d037fcfd --- /dev/null +++ b/frontend/public/humans.txt @@ -0,0 +1,31 @@ +/* TEAM */ +Benevolent Dictator for Life: Alexandru Iosup. +Site: http://www.ds.ewi.tudelft.nl/~iosup/ +Twitter: aiosup. +Location: Delft, Netherlands. + +Backend Engineer: Leon Overweel. +Site: http://leonoverweel.com/ +Twitter: layon_overwhale. +Location: Delft, Netherlands. + +Frontend Engineer: Georgios Andreadis. +Site: https://github.com/gandreadis +Location: Delft, Netherlands. + +Simulation Engineer: Fabian Mastenbroek. +Site: https://github.com/fabianishere +Location: Delft, Netherlands. + +Simulation Engineer: Matthijs Bijman. +Site: https://github.com/MDBijman +Location: Delft, Netherlands. + +/* THANKS */ +Executive Producer: Vincent van Beek. +Executive Producer: Tim Hegeman. + +/* SITE */ +Standards: HTML5, Sass, ES6 +Components: React.js, Redux, create-react-app, react-konva +Software: WebStorm, Vim, Visual Studio diff --git a/frontend/public/img/datacenter-drawing.png b/frontend/public/img/datacenter-drawing.png Binary files differnew file mode 100644 index 00000000..401168e3 --- /dev/null +++ b/frontend/public/img/datacenter-drawing.png diff --git a/frontend/public/img/logo.png b/frontend/public/img/logo.png Binary files differnew file mode 100644 index 00000000..d743038b --- /dev/null +++ b/frontend/public/img/logo.png diff --git a/frontend/public/img/portraits/aiosup.png b/frontend/public/img/portraits/aiosup.png Binary files differnew file mode 100644 index 00000000..30de349c --- /dev/null +++ b/frontend/public/img/portraits/aiosup.png diff --git a/frontend/public/img/portraits/fmastenbroek.png b/frontend/public/img/portraits/fmastenbroek.png Binary files differnew file mode 100644 index 00000000..fd0d9de1 --- /dev/null +++ b/frontend/public/img/portraits/fmastenbroek.png diff --git a/frontend/public/img/portraits/gandreadis.png b/frontend/public/img/portraits/gandreadis.png Binary files differnew file mode 100644 index 00000000..403870fa --- /dev/null +++ b/frontend/public/img/portraits/gandreadis.png diff --git a/frontend/public/img/portraits/loverweel.png b/frontend/public/img/portraits/loverweel.png Binary files differnew file mode 100644 index 00000000..d12a9e86 --- /dev/null +++ b/frontend/public/img/portraits/loverweel.png diff --git a/frontend/public/img/stakeholders/Developer.png b/frontend/public/img/stakeholders/Developer.png Binary files differnew file mode 100644 index 00000000..d2638e6c --- /dev/null +++ b/frontend/public/img/stakeholders/Developer.png diff --git a/frontend/public/img/stakeholders/Manager.png b/frontend/public/img/stakeholders/Manager.png Binary files differnew file mode 100644 index 00000000..92db7459 --- /dev/null +++ b/frontend/public/img/stakeholders/Manager.png diff --git a/frontend/public/img/stakeholders/Researcher.png b/frontend/public/img/stakeholders/Researcher.png Binary files differnew file mode 100644 index 00000000..d87edd39 --- /dev/null +++ b/frontend/public/img/stakeholders/Researcher.png diff --git a/frontend/public/img/stakeholders/Sales.png b/frontend/public/img/stakeholders/Sales.png Binary files differnew file mode 100644 index 00000000..5b7c3a72 --- /dev/null +++ b/frontend/public/img/stakeholders/Sales.png diff --git a/frontend/public/img/stakeholders/Student.png b/frontend/public/img/stakeholders/Student.png Binary files differnew file mode 100644 index 00000000..a4900303 --- /dev/null +++ b/frontend/public/img/stakeholders/Student.png diff --git a/frontend/public/img/topology/cpu-icon.png b/frontend/public/img/topology/cpu-icon.png Binary files differnew file mode 100644 index 00000000..07cfbd31 --- /dev/null +++ b/frontend/public/img/topology/cpu-icon.png diff --git a/frontend/public/img/topology/gpu-icon.png b/frontend/public/img/topology/gpu-icon.png Binary files differnew file mode 100644 index 00000000..55d4fb05 --- /dev/null +++ b/frontend/public/img/topology/gpu-icon.png diff --git a/frontend/public/img/topology/memory-icon.png b/frontend/public/img/topology/memory-icon.png Binary files differnew file mode 100644 index 00000000..36e8a44e --- /dev/null +++ b/frontend/public/img/topology/memory-icon.png diff --git a/frontend/public/img/topology/rack-energy-icon.png b/frontend/public/img/topology/rack-energy-icon.png Binary files differnew file mode 100644 index 00000000..1088c61b --- /dev/null +++ b/frontend/public/img/topology/rack-energy-icon.png diff --git a/frontend/public/img/topology/rack-space-icon.png b/frontend/public/img/topology/rack-space-icon.png Binary files differnew file mode 100644 index 00000000..387d7ea6 --- /dev/null +++ b/frontend/public/img/topology/rack-space-icon.png diff --git a/frontend/public/img/topology/storage-icon.png b/frontend/public/img/topology/storage-icon.png Binary files differnew file mode 100644 index 00000000..7a39cb6f --- /dev/null +++ b/frontend/public/img/topology/storage-icon.png diff --git a/frontend/public/img/tudelft-icon.png b/frontend/public/img/tudelft-icon.png Binary files differnew file mode 100644 index 00000000..a7a2d56a --- /dev/null +++ b/frontend/public/img/tudelft-icon.png diff --git a/frontend/public/index.html b/frontend/public/index.html new file mode 100644 index 00000000..e88cca42 --- /dev/null +++ b/frontend/public/index.html @@ -0,0 +1,70 @@ +<!doctype html> +<html lang="en"> +<head> + <meta charset="utf-8"> + <title>OpenDC</title> + + <!-- Standard meta tags --> + <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> + <meta name="theme-color" content="#00A6D6"> + <meta name="description" content="Collaborative Datacenter Simulation and Exploration for Everybody"> + <meta name="author" content="@Large Research"> + <meta name="keywords" content="OpenDC, Datacenter, Simulation, Simulator, Collaborative, Distributed, Cluster"> + <link rel="manifest" href="/manifest.json"> + <link rel="shortcut icon" href="/favicon.ico"> + + <!-- Twitter Card data --> + <meta name="twitter:card" content="summary"> + <meta name="twitter:site" content="@LargeResearch"> + <meta name="twitter:title" content="OpenDC"> + <meta name="twitter:description" content="Collaborative Datacenter Simulation and Exploration for Everybody"> + <meta name="twitter:creator" content="@LargeResearch"> + <meta name="twitter:image" content="http://opendc.org/img/logo.png"> + + <!-- OpenGraph meta tags --> + <meta property="og:title" content="OpenDC"> + <meta property="og:site_name" content="OpenDC"> + <meta property="og:type" content="website"> + <meta property="og:image" content="http://opendc.org/img/logo.png"> + <meta property="og:url" content="http://opendc.org/"> + <meta property="og:description" + content="OpenDC provides collaborative online datacenter modeling, diverse and effective datacenter + simulation, and exploratory datacenter performance feedback."> + <meta property="og:locale" content="en_US"> + + <!-- Google meta tags --> + <meta name="google-signin-client_id" content="%REACT_APP_OAUTH_CLIENT_ID%"> + <meta name="google-site-verification" content="YIR4LkQTv6WmOdWv8MkeiUKni-0Yu3WHylLp4VvUMig"/> + + <!-- CDN dependencies --> + <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" + integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous"> + <link href="https://fonts.googleapis.com/css?family=Roboto" rel="stylesheet"> + <script src="https://use.fontawesome.com/ece66a2e7c.js"></script> + + <!-- Google Analytics --> + <script async src="https://www.googletagmanager.com/gtag/js?id=UA-84285092-3"></script> + <script> + window.dataLayer = window.dataLayer || []; + function gtag() {dataLayer.push(arguments);} + gtag('js', new Date()); + gtag('config', 'UA-84285092-3'); + </script> +</head> +<body data-spy="scroll" data-target="#navbar"> +<noscript> + You need to enable JavaScript to run this app. +</noscript> +<div id="root"></div> + +<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js" + integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4=" + crossorigin="anonymous"></script> +<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.11.0/umd/popper.min.js" + integrity="sha384-b/U6ypiBEHpOf/4+1nzFpr53nxSS+GLCkfwBdFNTxtclqqenISfwAzpKaMNFNmj4" + crossorigin="anonymous"></script> +<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/js/bootstrap.min.js" + integrity="sha384-h0AbiXch4ZDo7tp9hKZ4TsHbi047NrKGLO3SEJAg45jXxnGIfYzk4Si90RDIqNm1" + crossorigin="anonymous"></script> +</body> +</html> diff --git a/frontend/public/manifest.json b/frontend/public/manifest.json new file mode 100644 index 00000000..adb82218 --- /dev/null +++ b/frontend/public/manifest.json @@ -0,0 +1,15 @@ +{ + "short_name": "OpenDC", + "name": "OpenDC", + "icons": [ + { + "src": "favicon.ico", + "sizes": "16x16", + "type": "image/png" + } + ], + "start_url": "./index.html", + "display": "standalone", + "theme_color": "#00A6D6", + "background_color": "#eeeeee" +} diff --git a/frontend/public/robots.txt b/frontend/public/robots.txt new file mode 100644 index 00000000..165a1ea9 --- /dev/null +++ b/frontend/public/robots.txt @@ -0,0 +1,3 @@ +User-agent: * +Disallow: /simulations/ +Disallow: /profile/ diff --git a/frontend/src/actions/auth.js b/frontend/src/actions/auth.js new file mode 100644 index 00000000..45e2eb35 --- /dev/null +++ b/frontend/src/actions/auth.js @@ -0,0 +1,23 @@ +export const LOG_IN = "LOG_IN"; +export const LOG_IN_SUCCEEDED = "LOG_IN_SUCCEEDED"; +export const LOG_OUT = "LOG_OUT"; + +export function logIn(payload) { + return { + type: LOG_IN, + payload + }; +} + +export function logInSucceeded(payload) { + return { + type: LOG_IN_SUCCEEDED, + payload + }; +} + +export function logOut() { + return { + type: LOG_OUT + }; +} diff --git a/frontend/src/actions/experiments.js b/frontend/src/actions/experiments.js new file mode 100644 index 00000000..b5709981 --- /dev/null +++ b/frontend/src/actions/experiments.js @@ -0,0 +1,34 @@ +export const FETCH_EXPERIMENTS_OF_SIMULATION = + "FETCH_EXPERIMENTS_OF_SIMULATION"; +export const ADD_EXPERIMENT = "ADD_EXPERIMENT"; +export const DELETE_EXPERIMENT = "DELETE_EXPERIMENT"; +export const OPEN_EXPERIMENT_SUCCEEDED = "OPEN_EXPERIMENT_SUCCEEDED"; + +export function fetchExperimentsOfSimulation(simulationId) { + return { + type: FETCH_EXPERIMENTS_OF_SIMULATION, + simulationId + }; +} + +export function addExperiment(experiment) { + return { + type: ADD_EXPERIMENT, + experiment + }; +} + +export function deleteExperiment(id) { + return { + type: DELETE_EXPERIMENT, + id + }; +} + +export function openExperimentSucceeded(simulationId, experimentId) { + return { + type: OPEN_EXPERIMENT_SUCCEEDED, + simulationId, + experimentId + }; +} diff --git a/frontend/src/actions/interaction-level.js b/frontend/src/actions/interaction-level.js new file mode 100644 index 00000000..31120146 --- /dev/null +++ b/frontend/src/actions/interaction-level.js @@ -0,0 +1,50 @@ +export const GO_FROM_BUILDING_TO_ROOM = "GO_FROM_BUILDING_TO_ROOM"; +export const GO_FROM_ROOM_TO_RACK = "GO_FROM_ROOM_TO_RACK"; +export const GO_FROM_RACK_TO_MACHINE = "GO_FROM_RACK_TO_MACHINE"; +export const GO_DOWN_ONE_INTERACTION_LEVEL = "GO_DOWN_ONE_INTERACTION_LEVEL"; + +export function goFromBuildingToRoom(roomId) { + return (dispatch, getState) => { + const { interactionLevel } = getState(); + if (interactionLevel.mode !== "BUILDING") { + return; + } + + dispatch({ + type: GO_FROM_BUILDING_TO_ROOM, + roomId + }); + }; +} + +export function goFromRoomToRack(tileId) { + return (dispatch, getState) => { + const { interactionLevel } = getState(); + if (interactionLevel.mode !== "ROOM") { + return; + } + dispatch({ + type: GO_FROM_ROOM_TO_RACK, + tileId + }); + }; +} + +export function goFromRackToMachine(position) { + return (dispatch, getState) => { + const { interactionLevel } = getState(); + if (interactionLevel.mode !== "RACK") { + return; + } + dispatch({ + type: GO_FROM_RACK_TO_MACHINE, + position + }); + }; +} + +export function goDownOneInteractionLevel() { + return { + type: GO_DOWN_ONE_INTERACTION_LEVEL + }; +} diff --git a/frontend/src/actions/map.js b/frontend/src/actions/map.js new file mode 100644 index 00000000..82546c00 --- /dev/null +++ b/frontend/src/actions/map.js @@ -0,0 +1,93 @@ +import { + MAP_MAX_SCALE, + MAP_MIN_SCALE, + MAP_SCALE_PER_EVENT, + MAP_SIZE_IN_PIXELS +} from "../components/app/map/MapConstants"; + +export const SET_MAP_POSITION = "SET_MAP_POSITION"; +export const SET_MAP_DIMENSIONS = "SET_MAP_DIMENSIONS"; +export const SET_MAP_SCALE = "SET_MAP_SCALE"; + +export function setMapPosition(x, y) { + return { + type: SET_MAP_POSITION, + x, + y + }; +} + +export function setMapDimensions(width, height) { + return { + type: SET_MAP_DIMENSIONS, + width, + height + }; +} + +export function setMapScale(scale) { + return { + type: SET_MAP_SCALE, + scale + }; +} + +export function zoomInOnCenter(zoomIn) { + return (dispatch, getState) => { + const state = getState(); + + dispatch( + zoomInOnPosition( + zoomIn, + state.map.dimensions.width / 2, + state.map.dimensions.height / 2 + ) + ); + }; +} + +export function zoomInOnPosition(zoomIn, x, y) { + return (dispatch, getState) => { + const state = getState(); + + const centerPoint = { + x: x / state.map.scale - state.map.position.x / state.map.scale, + y: y / state.map.scale - state.map.position.y / state.map.scale + }; + const newScale = zoomIn + ? state.map.scale * MAP_SCALE_PER_EVENT + : state.map.scale / MAP_SCALE_PER_EVENT; + const boundedScale = Math.min( + Math.max(MAP_MIN_SCALE, newScale), + MAP_MAX_SCALE + ); + + const newX = -(centerPoint.x - x / boundedScale) * boundedScale; + const newY = -(centerPoint.y - y / boundedScale) * boundedScale; + + dispatch(setMapPositionWithBoundsCheck(newX, newY)); + dispatch(setMapScale(boundedScale)); + }; +} + +export function setMapPositionWithBoundsCheck(x, y) { + return (dispatch, getState) => { + const state = getState(); + + const scaledMapSize = MAP_SIZE_IN_PIXELS * state.map.scale; + const updatedX = + x > 0 + ? 0 + : x < -scaledMapSize + state.map.dimensions.width + ? -scaledMapSize + state.map.dimensions.width + : x; + const updatedY = + y > 0 + ? 0 + : y < -scaledMapSize + state.map.dimensions.height + ? -scaledMapSize + state.map.dimensions.height + : y; + + dispatch(setMapPosition(updatedX, updatedY)); + }; +} diff --git a/frontend/src/actions/modals/experiments.js b/frontend/src/actions/modals/experiments.js new file mode 100644 index 00000000..df939fa5 --- /dev/null +++ b/frontend/src/actions/modals/experiments.js @@ -0,0 +1,14 @@ +export const OPEN_NEW_EXPERIMENT_MODAL = "OPEN_NEW_EXPERIMENT_MODAL"; +export const CLOSE_NEW_EXPERIMENT_MODAL = "CLOSE_EXPERIMENT_MODAL"; + +export function openNewExperimentModal() { + return { + type: OPEN_NEW_EXPERIMENT_MODAL + }; +} + +export function closeNewExperimentModal() { + return { + type: CLOSE_NEW_EXPERIMENT_MODAL + }; +} diff --git a/frontend/src/actions/modals/profile.js b/frontend/src/actions/modals/profile.js new file mode 100644 index 00000000..ee52610c --- /dev/null +++ b/frontend/src/actions/modals/profile.js @@ -0,0 +1,14 @@ +export const OPEN_DELETE_PROFILE_MODAL = "OPEN_DELETE_PROFILE_MODAL"; +export const CLOSE_DELETE_PROFILE_MODAL = "CLOSE_DELETE_PROFILE_MODAL"; + +export function openDeleteProfileModal() { + return { + type: OPEN_DELETE_PROFILE_MODAL + }; +} + +export function closeDeleteProfileModal() { + return { + type: CLOSE_DELETE_PROFILE_MODAL + }; +} diff --git a/frontend/src/actions/modals/simulations.js b/frontend/src/actions/modals/simulations.js new file mode 100644 index 00000000..b11d356c --- /dev/null +++ b/frontend/src/actions/modals/simulations.js @@ -0,0 +1,14 @@ +export const OPEN_NEW_SIMULATION_MODAL = "OPEN_NEW_SIMULATION_MODAL"; +export const CLOSE_NEW_SIMULATION_MODAL = "CLOSE_SIMULATION_MODAL"; + +export function openNewSimulationModal() { + return { + type: OPEN_NEW_SIMULATION_MODAL + }; +} + +export function closeNewSimulationModal() { + return { + type: CLOSE_NEW_SIMULATION_MODAL + }; +} diff --git a/frontend/src/actions/modals/topology.js b/frontend/src/actions/modals/topology.js new file mode 100644 index 00000000..7ee16522 --- /dev/null +++ b/frontend/src/actions/modals/topology.js @@ -0,0 +1,70 @@ +export const OPEN_EDIT_ROOM_NAME_MODAL = "OPEN_EDIT_ROOM_NAME_MODAL"; +export const CLOSE_EDIT_ROOM_NAME_MODAL = "CLOSE_EDIT_ROOM_NAME_MODAL"; +export const OPEN_DELETE_ROOM_MODAL = "OPEN_DELETE_ROOM_MODAL"; +export const CLOSE_DELETE_ROOM_MODAL = "CLOSE_DELETE_ROOM_MODAL"; +export const OPEN_EDIT_RACK_NAME_MODAL = "OPEN_EDIT_RACK_NAME_MODAL"; +export const CLOSE_EDIT_RACK_NAME_MODAL = "CLOSE_EDIT_RACK_NAME_MODAL"; +export const OPEN_DELETE_RACK_MODAL = "OPEN_DELETE_RACK_MODAL"; +export const CLOSE_DELETE_RACK_MODAL = "CLOSE_DELETE_RACK_MODAL"; +export const OPEN_DELETE_MACHINE_MODAL = "OPEN_DELETE_MACHINE_MODAL"; +export const CLOSE_DELETE_MACHINE_MODAL = "CLOSE_DELETE_MACHINE_MODAL"; + +export function openEditRoomNameModal() { + return { + type: OPEN_EDIT_ROOM_NAME_MODAL + }; +} + +export function closeEditRoomNameModal() { + return { + type: CLOSE_EDIT_ROOM_NAME_MODAL + }; +} + +export function openDeleteRoomModal() { + return { + type: OPEN_DELETE_ROOM_MODAL + }; +} + +export function closeDeleteRoomModal() { + return { + type: CLOSE_DELETE_ROOM_MODAL + }; +} + +export function openEditRackNameModal() { + return { + type: OPEN_EDIT_RACK_NAME_MODAL + }; +} + +export function closeEditRackNameModal() { + return { + type: CLOSE_EDIT_RACK_NAME_MODAL + }; +} + +export function openDeleteRackModal() { + return { + type: OPEN_DELETE_RACK_MODAL + }; +} + +export function closeDeleteRackModal() { + return { + type: CLOSE_DELETE_RACK_MODAL + }; +} + +export function openDeleteMachineModal() { + return { + type: OPEN_DELETE_MACHINE_MODAL + }; +} + +export function closeDeleteMachineModal() { + return { + type: CLOSE_DELETE_MACHINE_MODAL + }; +} diff --git a/frontend/src/actions/objects.js b/frontend/src/actions/objects.js new file mode 100644 index 00000000..80b56c0c --- /dev/null +++ b/frontend/src/actions/objects.js @@ -0,0 +1,48 @@ +export const ADD_TO_STORE = "ADD_TO_STORE"; +export const ADD_PROP_TO_STORE_OBJECT = "ADD_PROP_TO_STORE_OBJECT"; +export const ADD_ID_TO_STORE_OBJECT_LIST_PROP = + "ADD_ID_TO_STORE_OBJECT_LIST_PROP"; +export const REMOVE_ID_FROM_STORE_OBJECT_LIST_PROP = + "REMOVE_ID_FROM_STORE_OBJECT_LIST_PROP"; + +export function addToStore(objectType, object) { + return { + type: ADD_TO_STORE, + objectType, + object + }; +} + +export function addPropToStoreObject(objectType, objectId, propObject) { + return { + type: ADD_PROP_TO_STORE_OBJECT, + objectType, + objectId, + propObject + }; +} + +export function addIdToStoreObjectListProp(objectType, objectId, propName, id) { + return { + type: ADD_ID_TO_STORE_OBJECT_LIST_PROP, + objectType, + objectId, + propName, + id + }; +} + +export function removeIdFromStoreObjectListProp( + objectType, + objectId, + propName, + id +) { + return { + type: REMOVE_ID_FROM_STORE_OBJECT_LIST_PROP, + objectType, + objectId, + propName, + id + }; +} diff --git a/frontend/src/actions/profile.js b/frontend/src/actions/profile.js new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/frontend/src/actions/profile.js diff --git a/frontend/src/actions/simulation/load-metric.js b/frontend/src/actions/simulation/load-metric.js new file mode 100644 index 00000000..c59e3596 --- /dev/null +++ b/frontend/src/actions/simulation/load-metric.js @@ -0,0 +1,8 @@ +export const CHANGE_LOAD_METRIC = "CHANGE_LOAD_METRIC"; + +export function changeLoadMetric(metric) { + return { + type: CHANGE_LOAD_METRIC, + metric + }; +} diff --git a/frontend/src/actions/simulation/playback.js b/frontend/src/actions/simulation/playback.js new file mode 100644 index 00000000..8e913914 --- /dev/null +++ b/frontend/src/actions/simulation/playback.js @@ -0,0 +1,15 @@ +export const SET_PLAYING = "SET_PLAYING"; + +export function playSimulation() { + return { + type: SET_PLAYING, + playing: true + }; +} + +export function pauseSimulation() { + return { + type: SET_PLAYING, + playing: false + }; +} diff --git a/frontend/src/actions/simulation/tick.js b/frontend/src/actions/simulation/tick.js new file mode 100644 index 00000000..a629b340 --- /dev/null +++ b/frontend/src/actions/simulation/tick.js @@ -0,0 +1,49 @@ +import { getDatacenterIdOfTick } from "../../util/timeline"; +import { setCurrentDatacenter } from "../topology/building"; + +export const GO_TO_TICK = "GO_TO_TICK"; +export const SET_LAST_SIMULATED_TICK = "SET_LAST_SIMULATED_TICK"; + +export function incrementTick() { + return (dispatch, getState) => { + const { currentTick } = getState(); + dispatch(goToTick(currentTick + 1)); + }; +} + +export function goToTick(tick) { + return (dispatch, getState) => { + const state = getState(); + + let sections = []; + if (state.currentExperimentId !== -1) { + const sectionIds = + state.objects.path[ + state.objects.experiment[state.currentExperimentId].pathId + ].sectionIds; + + if (sectionIds) { + sections = sectionIds.map( + sectionId => state.objects.section[sectionId] + ); + } + } + + const newDatacenterId = getDatacenterIdOfTick(tick, sections); + if (state.currentDatacenterId !== newDatacenterId) { + dispatch(setCurrentDatacenter(newDatacenterId)); + } + + dispatch({ + type: GO_TO_TICK, + tick + }); + }; +} + +export function setLastSimulatedTick(tick) { + return { + type: SET_LAST_SIMULATED_TICK, + tick + }; +} diff --git a/frontend/src/actions/simulations.js b/frontend/src/actions/simulations.js new file mode 100644 index 00000000..6da7aa3a --- /dev/null +++ b/frontend/src/actions/simulations.js @@ -0,0 +1,52 @@ +export const SET_AUTH_VISIBILITY_FILTER = "SET_AUTH_VISIBILITY_FILTER"; +export const ADD_SIMULATION = "ADD_SIMULATION"; +export const ADD_SIMULATION_SUCCEEDED = "ADD_SIMULATION_SUCCEEDED"; +export const DELETE_SIMULATION = "DELETE_SIMULATION"; +export const DELETE_SIMULATION_SUCCEEDED = "DELETE_SIMULATION_SUCCEEDED"; +export const OPEN_SIMULATION_SUCCEEDED = "OPEN_SIMULATION_SUCCEEDED"; + +export function setAuthVisibilityFilter(filter) { + return { + type: SET_AUTH_VISIBILITY_FILTER, + filter + }; +} + +export function addSimulation(name) { + return (dispatch, getState) => { + const { auth } = getState(); + dispatch({ + type: ADD_SIMULATION, + name, + userId: auth.userId + }); + }; +} + +export function addSimulationSucceeded(authorization) { + return { + type: ADD_SIMULATION_SUCCEEDED, + authorization + }; +} + +export function deleteSimulation(id) { + return { + type: DELETE_SIMULATION, + id + }; +} + +export function deleteSimulationSucceeded(id) { + return { + type: DELETE_SIMULATION_SUCCEEDED, + id + }; +} + +export function openSimulationSucceeded(id) { + return { + type: OPEN_SIMULATION_SUCCEEDED, + id + }; +} diff --git a/frontend/src/actions/states.js b/frontend/src/actions/states.js new file mode 100644 index 00000000..b3a355a2 --- /dev/null +++ b/frontend/src/actions/states.js @@ -0,0 +1,9 @@ +export const ADD_BATCH_TO_STATES = "ADD_BATCH_TO_STATES"; + +export function addBatchToStates(objectType, objects) { + return { + type: ADD_BATCH_TO_STATES, + objectType, + objects + }; +} diff --git a/frontend/src/actions/topology/building.js b/frontend/src/actions/topology/building.js new file mode 100644 index 00000000..c6381a07 --- /dev/null +++ b/frontend/src/actions/topology/building.js @@ -0,0 +1,117 @@ +export const SET_CURRENT_DATACENTER = "SET_CURRENT_DATACENTER"; +export const RESET_CURRENT_DATACENTER = "RESET_CURRENT_DATACENTER"; +export const START_NEW_ROOM_CONSTRUCTION = "START_NEW_ROOM_CONSTRUCTION"; +export const START_NEW_ROOM_CONSTRUCTION_SUCCEEDED = + "START_NEW_ROOM_CONSTRUCTION_SUCCEEDED"; +export const FINISH_NEW_ROOM_CONSTRUCTION = "FINISH_NEW_ROOM_CONSTRUCTION"; +export const CANCEL_NEW_ROOM_CONSTRUCTION = "CANCEL_NEW_ROOM_CONSTRUCTION"; +export const CANCEL_NEW_ROOM_CONSTRUCTION_SUCCEEDED = + "CANCEL_NEW_ROOM_CONSTRUCTION_SUCCEEDED"; +export const START_ROOM_EDIT = "START_ROOM_EDIT"; +export const FINISH_ROOM_EDIT = "FINISH_ROOM_EDIT"; +export const ADD_TILE = "ADD_TILE"; +export const DELETE_TILE = "DELETE_TILE"; + +export function setCurrentDatacenter(datacenterId) { + return { + type: SET_CURRENT_DATACENTER, + datacenterId + }; +} + +export function resetCurrentDatacenter() { + return { + type: RESET_CURRENT_DATACENTER + }; +} + +export function startNewRoomConstruction() { + return { + type: START_NEW_ROOM_CONSTRUCTION + }; +} + +export function startNewRoomConstructionSucceeded(roomId) { + return { + type: START_NEW_ROOM_CONSTRUCTION_SUCCEEDED, + roomId + }; +} + +export function finishNewRoomConstruction() { + return (dispatch, getState) => { + const { objects, construction } = getState(); + if ( + objects.room[construction.currentRoomInConstruction].tileIds.length === 0 + ) { + dispatch(cancelNewRoomConstruction()); + return; + } + + dispatch({ + type: FINISH_NEW_ROOM_CONSTRUCTION + }); + }; +} + +export function cancelNewRoomConstruction() { + return { + type: CANCEL_NEW_ROOM_CONSTRUCTION + }; +} + +export function cancelNewRoomConstructionSucceeded() { + return { + type: CANCEL_NEW_ROOM_CONSTRUCTION_SUCCEEDED + }; +} + +export function startRoomEdit() { + return (dispatch, getState) => { + const { interactionLevel } = getState(); + dispatch({ + type: START_ROOM_EDIT, + roomId: interactionLevel.roomId + }); + }; +} + +export function finishRoomEdit() { + return { + type: FINISH_ROOM_EDIT + }; +} + +export function toggleTileAtLocation(positionX, positionY) { + return (dispatch, getState) => { + const { objects, construction } = getState(); + + const tileIds = + objects.room[construction.currentRoomInConstruction].tileIds; + for (let index in tileIds) { + if ( + objects.tile[tileIds[index]].positionX === positionX && + objects.tile[tileIds[index]].positionY === positionY + ) { + dispatch(deleteTile(tileIds[index])); + return; + } + } + dispatch(addTile(positionX, positionY)); + }; +} + +export function addTile(positionX, positionY) { + return { + type: ADD_TILE, + positionX, + positionY + }; +} + +export function deleteTile(tileId) { + return { + type: DELETE_TILE, + tileId + }; +} diff --git a/frontend/src/actions/topology/machine.js b/frontend/src/actions/topology/machine.js new file mode 100644 index 00000000..56968b7d --- /dev/null +++ b/frontend/src/actions/topology/machine.js @@ -0,0 +1,25 @@ +export const DELETE_MACHINE = "DELETE_MACHINE"; +export const ADD_UNIT = "ADD_UNIT"; +export const DELETE_UNIT = "DELETE_UNIT"; + +export function deleteMachine() { + return { + type: DELETE_MACHINE + }; +} + +export function addUnit(unitType, id) { + return { + type: ADD_UNIT, + unitType, + id + }; +} + +export function deleteUnit(unitType, index) { + return { + type: DELETE_UNIT, + unitType, + index + }; +} diff --git a/frontend/src/actions/topology/rack.js b/frontend/src/actions/topology/rack.js new file mode 100644 index 00000000..06988424 --- /dev/null +++ b/frontend/src/actions/topology/rack.js @@ -0,0 +1,23 @@ +export const EDIT_RACK_NAME = "EDIT_RACK_NAME"; +export const DELETE_RACK = "DELETE_RACK"; +export const ADD_MACHINE = "ADD_MACHINE"; + +export function editRackName(name) { + return { + type: EDIT_RACK_NAME, + name + }; +} + +export function deleteRack() { + return { + type: DELETE_RACK + }; +} + +export function addMachine(position) { + return { + type: ADD_MACHINE, + position + }; +} diff --git a/frontend/src/actions/topology/room.js b/frontend/src/actions/topology/room.js new file mode 100644 index 00000000..4e0fc3a2 --- /dev/null +++ b/frontend/src/actions/topology/room.js @@ -0,0 +1,48 @@ +import { findTileWithPosition } from "../../util/tile-calculations"; + +export const EDIT_ROOM_NAME = "EDIT_ROOM_NAME"; +export const DELETE_ROOM = "DELETE_ROOM"; +export const START_RACK_CONSTRUCTION = "START_RACK_CONSTRUCTION"; +export const STOP_RACK_CONSTRUCTION = "STOP_RACK_CONSTRUCTION"; +export const ADD_RACK_TO_TILE = "ADD_RACK_TO_TILE"; + +export function editRoomName(name) { + return { + type: EDIT_ROOM_NAME, + name + }; +} + +export function startRackConstruction() { + return { + type: START_RACK_CONSTRUCTION + }; +} + +export function stopRackConstruction() { + return { + type: STOP_RACK_CONSTRUCTION + }; +} + +export function addRackToTile(positionX, positionY) { + return (dispatch, getState) => { + const { objects, interactionLevel } = getState(); + const currentRoom = objects.room[interactionLevel.roomId]; + const tiles = currentRoom.tileIds.map(tileId => objects.tile[tileId]); + const tile = findTileWithPosition(tiles, positionX, positionY); + + if (tile !== null) { + dispatch({ + type: ADD_RACK_TO_TILE, + tileId: tile.id + }); + } + }; +} + +export function deleteRoom() { + return { + type: DELETE_ROOM + }; +} diff --git a/frontend/src/actions/users.js b/frontend/src/actions/users.js new file mode 100644 index 00000000..dc393df9 --- /dev/null +++ b/frontend/src/actions/users.js @@ -0,0 +1,41 @@ +export const FETCH_AUTHORIZATIONS_OF_CURRENT_USER = + "FETCH_AUTHORIZATIONS_OF_CURRENT_USER"; +export const FETCH_AUTHORIZATIONS_OF_CURRENT_USER_SUCCEEDED = + "FETCH_AUTHORIZATIONS_OF_CURRENT_USER_SUCCEEDED"; +export const DELETE_CURRENT_USER = "DELETE_CURRENT_USER"; +export const DELETE_CURRENT_USER_SUCCEEDED = "DELETE_CURRENT_USER_SUCCEEDED"; + +export function fetchAuthorizationsOfCurrentUser() { + return (dispatch, getState) => { + const { auth } = getState(); + dispatch({ + type: FETCH_AUTHORIZATIONS_OF_CURRENT_USER, + userId: auth.userId + }); + }; +} + +export function fetchAuthorizationsOfCurrentUserSucceeded( + authorizationsOfCurrentUser +) { + return { + type: FETCH_AUTHORIZATIONS_OF_CURRENT_USER_SUCCEEDED, + authorizationsOfCurrentUser + }; +} + +export function deleteCurrentUser() { + return (dispatch, getState) => { + const { auth } = getState(); + dispatch({ + type: DELETE_CURRENT_USER, + userId: auth.userId + }); + }; +} + +export function deleteCurrentUserSucceeded() { + return { + type: DELETE_CURRENT_USER_SUCCEEDED + }; +} diff --git a/frontend/src/api/index.js b/frontend/src/api/index.js new file mode 100644 index 00000000..37c288a3 --- /dev/null +++ b/frontend/src/api/index.js @@ -0,0 +1,13 @@ +import { sendSocketRequest } from "./socket"; + +export function sendRequest(request) { + return new Promise((resolve, reject) => { + sendSocketRequest(request, response => { + if (response.status.code === 200) { + resolve(response.content); + } else { + reject(response); + } + }); + }); +} diff --git a/frontend/src/api/routes/datacenters.js b/frontend/src/api/routes/datacenters.js new file mode 100644 index 00000000..20cf4935 --- /dev/null +++ b/frontend/src/api/routes/datacenters.js @@ -0,0 +1,26 @@ +import { sendRequest } from "../index"; +import { getById } from "./util"; + +export function getDatacenter(datacenterId) { + return getById("/datacenters/{datacenterId}", { datacenterId }); +} + +export function getRoomsOfDatacenter(datacenterId) { + return getById("/datacenters/{datacenterId}/rooms", { datacenterId }); +} + +export function addRoomToDatacenter(room) { + return sendRequest({ + path: "/datacenters/{datacenterId}/rooms", + method: "POST", + parameters: { + body: { + room + }, + path: { + datacenterId: room.datacenterId + }, + query: {} + } + }); +} diff --git a/frontend/src/api/routes/experiments.js b/frontend/src/api/routes/experiments.js new file mode 100644 index 00000000..f61698c5 --- /dev/null +++ b/frontend/src/api/routes/experiments.js @@ -0,0 +1,33 @@ +import { deleteById, getById } from "./util"; + +export function getExperiment(experimentId) { + return getById("/experiments/{experimentId}", { experimentId }); +} + +export function deleteExperiment(experimentId) { + return deleteById("/experiments/{experimentId}", { experimentId }); +} + +export function getLastSimulatedTick(experimentId) { + return getById("/experiments/{experimentId}/last-simulated-tick", { + experimentId + }); +} + +export function getAllMachineStates(experimentId) { + return getById("/experiments/{experimentId}/machine-states", { + experimentId + }); +} + +export function getAllRackStates(experimentId) { + return getById("/experiments/{experimentId}/rack-states", { experimentId }); +} + +export function getAllRoomStates(experimentId) { + return getById("/experiments/{experimentId}/room-states", { experimentId }); +} + +export function getAllTaskStates(experimentId) { + return getById("/experiments/{experimentId}/task-states", { experimentId }); +} diff --git a/frontend/src/api/routes/jobs.js b/frontend/src/api/routes/jobs.js new file mode 100644 index 00000000..355acc32 --- /dev/null +++ b/frontend/src/api/routes/jobs.js @@ -0,0 +1,5 @@ +import { getById } from "./util"; + +export function getTasksOfJob(jobId) { + return getById("/jobs/{jobId}/tasks", { jobId }); +} diff --git a/frontend/src/api/routes/paths.js b/frontend/src/api/routes/paths.js new file mode 100644 index 00000000..78ef7d6e --- /dev/null +++ b/frontend/src/api/routes/paths.js @@ -0,0 +1,30 @@ +import { sendRequest } from "../index"; +import { getById } from "./util"; + +export function getPath(pathId) { + return getById("/paths/{pathId}", { pathId }); +} + +export function getBranchesOfPath(pathId) { + return getById("/paths/{pathId}/branches", { pathId }); +} + +export function branchFromPath(pathId, section) { + return sendRequest({ + path: "/paths/{pathId}/branches", + method: "POST", + parameters: { + body: { + section + }, + path: { + pathId + }, + query: {} + } + }); +} + +export function getSectionsOfPath(pathId) { + return getById("/paths/{pathId}/sections", { pathId }); +} diff --git a/frontend/src/api/routes/room-types.js b/frontend/src/api/routes/room-types.js new file mode 100644 index 00000000..8a3eac58 --- /dev/null +++ b/frontend/src/api/routes/room-types.js @@ -0,0 +1,9 @@ +import { getAll, getById } from "./util"; + +export function getAvailableRoomTypes() { + return getAll("/room-types"); +} + +export function getAllowedObjectsOfRoomType(name) { + return getById("/room-types/{name}/allowed-objects", { name }); +} diff --git a/frontend/src/api/routes/rooms.js b/frontend/src/api/routes/rooms.js new file mode 100644 index 00000000..56395d7f --- /dev/null +++ b/frontend/src/api/routes/rooms.js @@ -0,0 +1,46 @@ +import { sendRequest } from "../index"; +import { deleteById, getById } from "./util"; + +export function getRoom(roomId) { + return getById("/rooms/{roomId}", { roomId }); +} + +export function updateRoom(room) { + return sendRequest({ + path: "/rooms/{roomId}", + method: "PUT", + parameters: { + body: { + room + }, + path: { + roomId: room.id + }, + query: {} + } + }); +} + +export function deleteRoom(roomId) { + return deleteById("/rooms/{roomId}", { roomId }); +} + +export function getTilesOfRoom(roomId) { + return getById("/rooms/{roomId}/tiles", { roomId }); +} + +export function addTileToRoom(tile) { + return sendRequest({ + path: "/rooms/{roomId}/tiles", + method: "POST", + parameters: { + body: { + tile + }, + path: { + roomId: tile.roomId + }, + query: {} + } + }); +} diff --git a/frontend/src/api/routes/schedulers.js b/frontend/src/api/routes/schedulers.js new file mode 100644 index 00000000..ea360967 --- /dev/null +++ b/frontend/src/api/routes/schedulers.js @@ -0,0 +1,5 @@ +import { getAll } from "./util"; + +export function getAllSchedulers() { + return getAll("/schedulers"); +} diff --git a/frontend/src/api/routes/sections.js b/frontend/src/api/routes/sections.js new file mode 100644 index 00000000..5e1a077d --- /dev/null +++ b/frontend/src/api/routes/sections.js @@ -0,0 +1,5 @@ +import { getById } from "./util"; + +export function getSection(sectionId) { + return getById("/sections/{sectionId}", { sectionId }); +} diff --git a/frontend/src/api/routes/simulations.js b/frontend/src/api/routes/simulations.js new file mode 100644 index 00000000..dcb9ac5f --- /dev/null +++ b/frontend/src/api/routes/simulations.js @@ -0,0 +1,70 @@ +import { sendRequest } from "../index"; +import { deleteById, getById } from "./util"; + +export function getSimulation(simulationId) { + return getById("/simulations/{simulationId}", { simulationId }); +} + +export function addSimulation(simulation) { + return sendRequest({ + path: "/simulations", + method: "POST", + parameters: { + body: { + simulation + }, + path: {}, + query: {} + } + }); +} + +export function updateSimulation(simulation) { + return sendRequest({ + path: "/simulations/{simulationId}", + method: "PUT", + parameters: { + body: { + simulation + }, + path: { + simulationId: simulation.id + }, + query: {} + } + }); +} + +export function deleteSimulation(simulationId) { + return deleteById("/simulations/{simulationId}", { simulationId }); +} + +export function getAuthorizationsBySimulation(simulationId) { + return getById("/simulations/{simulationId}/authorizations", { + simulationId + }); +} + +export function getPathsOfSimulation(simulationId) { + return getById("/simulations/{simulationId}/paths", { simulationId }); +} + +export function getExperimentsOfSimulation(simulationId) { + return getById("/simulations/{simulationId}/experiments", { simulationId }); +} + +export function addExperiment(simulationId, experiment) { + return sendRequest({ + path: "/simulations/{simulationId}/experiments", + method: "POST", + parameters: { + body: { + experiment + }, + path: { + simulationId + }, + query: {} + } + }); +} diff --git a/frontend/src/api/routes/specifications.js b/frontend/src/api/routes/specifications.js new file mode 100644 index 00000000..0f60b571 --- /dev/null +++ b/frontend/src/api/routes/specifications.js @@ -0,0 +1,57 @@ +import { getAll, getById } from "./util"; + +export function getAllCoolingItems() { + return getAll("/specifications/cooling-items"); +} + +export function getCoolingItem(id) { + return getById("/specifications/cooling-items/{id}", { id }); +} + +export function getAllCPUs() { + return getAll("/specifications/cpus"); +} + +export function getCPU(id) { + return getById("/specifications/cpus/{id}", { id }); +} + +export function getAllFailureModels() { + return getAll("/specifications/failure-models"); +} + +export function getFailureModel(id) { + return getById("/specifications/failure-models/{id}", { id }); +} + +export function getAllGPUs() { + return getAll("/specifications/gpus"); +} + +export function getGPU(id) { + return getById("/specifications/gpus/{id}", { id }); +} + +export function getAllMemories() { + return getAll("/specifications/memories"); +} + +export function getMemory(id) { + return getById("/specifications/memories/{id}", { id }); +} + +export function getAllPSUs() { + return getAll("/specifications/psus"); +} + +export function getPSU(id) { + return getById("/specifications/psus/{id}", { id }); +} + +export function getAllStorages() { + return getAll("/specifications/storages"); +} + +export function getStorage(id) { + return getById("/specifications/storages/{id}", { id }); +} diff --git a/frontend/src/api/routes/tiles.js b/frontend/src/api/routes/tiles.js new file mode 100644 index 00000000..08481ef4 --- /dev/null +++ b/frontend/src/api/routes/tiles.js @@ -0,0 +1,146 @@ +import { sendRequest } from "../index"; +import { deleteById, getById } from "./util"; + +export function getTile(tileId) { + return getById("/tiles/{tileId}", { tileId }); +} + +export function deleteTile(tileId) { + return deleteById("/tiles/{tileId}", { tileId }); +} + +export function getRackByTile(tileId) { + return getTileObject(tileId, "/rack"); +} + +export function addRackToTile(tileId, rack) { + return addTileObject(tileId, { rack }, "/rack"); +} + +export function updateRackOnTile(tileId, rack) { + return updateTileObject(tileId, { rack }, "/rack"); +} + +export function deleteRackFromTile(tileId) { + return deleteTileObject(tileId, "/rack"); +} + +export function getMachinesOfRackByTile(tileId) { + return getById("/tiles/{tileId}/rack/machines", { tileId }); +} + +export function addMachineToRackOnTile(tileId, machine) { + return sendRequest({ + path: "/tiles/{tileId}/rack/machines", + method: "POST", + parameters: { + body: { + machine + }, + path: { + tileId + }, + query: {} + } + }); +} + +export function updateMachineInRackOnTile(tileId, position, machine) { + return sendRequest({ + path: "/tiles/{tileId}/rack/machines/{position}", + method: "PUT", + parameters: { + body: { + machine + }, + path: { + tileId, + position + }, + query: {} + } + }); +} + +export function deleteMachineInRackOnTile(tileId, position) { + return sendRequest({ + path: "/tiles/{tileId}/rack/machines/{position}", + method: "DELETE", + parameters: { + body: {}, + path: { + tileId, + position + }, + query: {} + } + }); +} + +export function getCoolingItemByTile(tileId) { + return getTileObject(tileId, "/cooling-item"); +} + +export function addCoolingItemToTile(tileId, coolingItemId) { + return addTileObject(tileId, { coolingItemId }, "/cooling-item"); +} + +export function updateCoolingItemOnTile(tileId, coolingItemId) { + return updateTileObject(tileId, { coolingItemId }, "/cooling-item"); +} + +export function deleteCoolingItemFromTile(tileId) { + return deleteTileObject(tileId, "/cooling-item"); +} + +export function getPSUByTile(tileId) { + return getTileObject(tileId, "/psu"); +} + +export function addPSUToTile(tileId, psuId) { + return addTileObject(tileId, { psuId }, "/psu"); +} + +export function updatePSUOnTile(tileId, psuId) { + return updateTileObject(tileId, { psuId }, "/psu"); +} + +export function deletePSUFromTile(tileId) { + return deleteTileObject(tileId, "/psu"); +} + +function getTileObject(tileId, endpoint) { + return getById("/tiles/{tileId}" + endpoint, { tileId }); +} + +function addTileObject(tileId, objectBody, endpoint) { + return sendRequest({ + path: "/tiles/{tileId}" + endpoint, + method: "POST", + parameters: { + body: objectBody, + path: { + tileId + }, + query: {} + } + }); +} + +function updateTileObject(tileId, objectBody, endpoint) { + return sendRequest({ + path: "/tiles/{tileId}" + endpoint, + method: "PUT", + parameters: { + body: objectBody, + path: { + tileId + }, + query: {} + } + }); +} + +function deleteTileObject(tileId, endpoint) { + return deleteById("/tiles/{tileId}" + endpoint, { tileId }); +} diff --git a/frontend/src/api/routes/token-signin.js b/frontend/src/api/routes/token-signin.js new file mode 100644 index 00000000..26875606 --- /dev/null +++ b/frontend/src/api/routes/token-signin.js @@ -0,0 +1,7 @@ +export function performTokenSignIn(token) { + return new Promise(resolve => { + window["jQuery"].post("/tokensignin", { idtoken: token }, data => + resolve(data) + ); + }); +} diff --git a/frontend/src/api/routes/traces.js b/frontend/src/api/routes/traces.js new file mode 100644 index 00000000..a9ee4fae --- /dev/null +++ b/frontend/src/api/routes/traces.js @@ -0,0 +1,9 @@ +import { getAll, getById } from "./util"; + +export function getAllTraces() { + return getAll("/traces"); +} + +export function getJobsOfTrace(traceId) { + return getById("/traces/{traceId}/jobs", { traceId }); +} diff --git a/frontend/src/api/routes/users.js b/frontend/src/api/routes/users.js new file mode 100644 index 00000000..f8d8039c --- /dev/null +++ b/frontend/src/api/routes/users.js @@ -0,0 +1,71 @@ +import { sendRequest } from "../index"; +import { deleteById, getById } from "./util"; + +export function getUserByEmail(email) { + return sendRequest({ + path: "/users", + method: "GET", + parameters: { + body: {}, + path: {}, + query: { + email + } + } + }); +} + +export function addUser(user) { + return sendRequest({ + path: "/users", + method: "POST", + parameters: { + body: { + user: user + }, + path: {}, + query: {} + } + }); +} + +export function getUser(userId) { + return sendRequest({ + path: "/users/{userId}", + method: "GET", + parameters: { + body: {}, + path: { + userId + }, + query: {} + } + }); +} + +export function updateUser(userId, user) { + return sendRequest({ + path: "/users/{userId}", + method: "PUT", + parameters: { + body: { + user: { + givenName: user.givenName, + familyName: user.familyName + } + }, + path: { + userId + }, + query: {} + } + }); +} + +export function deleteUser(userId) { + return deleteById("/users/{userId}", { userId }); +} + +export function getAuthorizationsByUser(userId) { + return getById("/users/{userId}/authorizations", { userId }); +} diff --git a/frontend/src/api/routes/util.js b/frontend/src/api/routes/util.js new file mode 100644 index 00000000..35a40333 --- /dev/null +++ b/frontend/src/api/routes/util.js @@ -0,0 +1,37 @@ +import { sendRequest } from "../index"; + +export function getAll(path) { + return sendRequest({ + path, + method: "GET", + parameters: { + body: {}, + path: {}, + query: {} + } + }); +} + +export function getById(path, pathObject) { + return sendRequest({ + path, + method: "GET", + parameters: { + body: {}, + path: pathObject, + query: {} + } + }); +} + +export function deleteById(path, pathObject) { + return sendRequest({ + path, + method: "DELETE", + parameters: { + body: {}, + path: pathObject, + query: {} + } + }); +} diff --git a/frontend/src/api/socket.js b/frontend/src/api/socket.js new file mode 100644 index 00000000..fadb77ad --- /dev/null +++ b/frontend/src/api/socket.js @@ -0,0 +1,52 @@ +import io from "socket.io-client"; +import { getAuthToken } from "../auth/index"; + +let socket; +let requestIdCounter = 0; +const callbacks = {}; + +export function setupSocketConnection(onConnect) { + let port = window.location.port; + if (process.env.NODE_ENV !== "production") { + port = 8081; + } + socket = io.connect( + window.location.protocol + "//" + window.location.hostname + ":" + port + ); + socket.on("connect", onConnect); + socket.on("response", onSocketResponse); +} + +export function sendSocketRequest(request, callback) { + if (!socket.connected) { + console.error("Attempted to send request over unconnected socket"); + return; + } + + const newId = requestIdCounter++; + callbacks[newId] = callback; + + request.id = newId; + request.token = getAuthToken(); + + if (!request.isRootRoute) { + request.path = "/v2" + request.path; + } + + socket.emit("request", request); + + if (process.env.NODE_ENV !== "production") { + console.log("Sent socket request:", request); + } +} + +function onSocketResponse(json) { + const response = JSON.parse(json); + + if (process.env.NODE_ENV !== "production") { + console.log("Received socket response:", response); + } + + callbacks[response.id](response); + delete callbacks[response.id]; +} diff --git a/frontend/src/auth/index.js b/frontend/src/auth/index.js new file mode 100644 index 00000000..83c27b27 --- /dev/null +++ b/frontend/src/auth/index.js @@ -0,0 +1,57 @@ +import { LOG_IN_SUCCEEDED, LOG_OUT } from "../actions/auth"; +import { DELETE_CURRENT_USER_SUCCEEDED } from "../actions/users"; + +const getAuthObject = () => { + const authItem = localStorage.getItem("auth"); + if (!authItem || authItem === "{}") { + return undefined; + } + return JSON.parse(authItem); +}; + +export const userIsLoggedIn = () => { + const authObj = getAuthObject(); + + if (!authObj || !authObj.googleId) { + return false; + } + + const currentTime = new Date().getTime(); + return parseInt(authObj.expiresAt, 10) - currentTime > 0; +}; + +export const getAuthToken = () => { + const authObj = getAuthObject(); + if (!authObj) { + return undefined; + } + + return authObj.authToken; +}; + +export const saveAuthLocalStorage = payload => { + localStorage.setItem("auth", JSON.stringify(payload)); +}; + +export const clearAuthLocalStorage = () => { + localStorage.setItem("auth", ""); +}; + +export const authRedirectMiddleware = store => next => action => { + switch (action.type) { + case LOG_IN_SUCCEEDED: + saveAuthLocalStorage(action.payload); + window.location.href = "/simulations"; + break; + case LOG_OUT: + case DELETE_CURRENT_USER_SUCCEEDED: + clearAuthLocalStorage(); + window.location.href = "/"; + break; + default: + next(action); + return; + } + + next(action); +}; diff --git a/frontend/src/components/app/map/LoadingScreen.js b/frontend/src/components/app/map/LoadingScreen.js new file mode 100644 index 00000000..9f379e0b --- /dev/null +++ b/frontend/src/components/app/map/LoadingScreen.js @@ -0,0 +1,11 @@ +import React from "react"; +import FontAwesome from "react-fontawesome"; + +const LoadingScreen = () => ( + <div className="display-4"> + <FontAwesome name="refresh" className="mr-4" spin /> + Loading your datacenter... + </div> +); + +export default LoadingScreen; diff --git a/frontend/src/components/app/map/MapConstants.js b/frontend/src/components/app/map/MapConstants.js new file mode 100644 index 00000000..32438b5e --- /dev/null +++ b/frontend/src/components/app/map/MapConstants.js @@ -0,0 +1,29 @@ +export const MAP_SIZE = 50; +export const TILE_SIZE_IN_PIXELS = 100; +export const TILE_SIZE_IN_METERS = 0.5; +export const MAP_SIZE_IN_PIXELS = MAP_SIZE * TILE_SIZE_IN_PIXELS; + +export const OBJECT_MARGIN_IN_PIXELS = TILE_SIZE_IN_PIXELS / 5; +export const TILE_PLUS_MARGIN_IN_PIXELS = TILE_SIZE_IN_PIXELS / 3; +export const OBJECT_SIZE_IN_PIXELS = + TILE_SIZE_IN_PIXELS - OBJECT_MARGIN_IN_PIXELS * 2; + +export const GRID_LINE_WIDTH_IN_PIXELS = 2; +export const WALL_WIDTH_IN_PIXELS = TILE_SIZE_IN_PIXELS / 8; +export const OBJECT_BORDER_WIDTH_IN_PIXELS = TILE_SIZE_IN_PIXELS / 12; +export const TILE_PLUS_WIDTH_IN_PIXELS = TILE_SIZE_IN_PIXELS / 10; + +export const SIDEBAR_WIDTH = 350; +export const VIEWPORT_PADDING = 50; + +export const RACK_FILL_ICON_WIDTH = OBJECT_SIZE_IN_PIXELS / 3; +export const RACK_FILL_ICON_OPACITY = 0.8; + +export const MAP_MOVE_PIXELS_PER_EVENT = 20; +export const MAP_SCALE_PER_EVENT = 1.1; +export const MAP_MIN_SCALE = 0.5; +export const MAP_MAX_SCALE = 1.5; + +export const MAX_NUM_UNITS_PER_MACHINE = 4; +export const DEFAULT_RACK_SLOT_CAPACITY = 42; +export const DEFAULT_RACK_POWER_CAPACITY = 10000; diff --git a/frontend/src/components/app/map/MapStageComponent.js b/frontend/src/components/app/map/MapStageComponent.js new file mode 100644 index 00000000..67b3349c --- /dev/null +++ b/frontend/src/components/app/map/MapStageComponent.js @@ -0,0 +1,126 @@ +import React from "react"; +import { Stage } from "react-konva"; +import { Shortcuts } from "react-shortcuts"; +import MapLayer from "../../../containers/app/map/layers/MapLayer"; +import ObjectHoverLayer from "../../../containers/app/map/layers/ObjectHoverLayer"; +import RoomHoverLayer from "../../../containers/app/map/layers/RoomHoverLayer"; +import jQuery from "../../../util/jquery"; +import { NAVBAR_HEIGHT } from "../../navigation/Navbar"; +import { MAP_MOVE_PIXELS_PER_EVENT } from "./MapConstants"; +import { Provider } from "react-redux"; +import { store } from "../../../store/configure-store"; + +class MapStageComponent extends React.Component { + state = { + mouseX: 0, + mouseY: 0 + }; + + constructor(props) { + super(props); + + this.updateDimensions = this.updateDimensions.bind(this); + this.updateScale = this.updateScale.bind(this); + } + + componentWillMount() { + this.updateDimensions(); + } + + componentDidMount() { + window.addEventListener("resize", this.updateDimensions); + window.addEventListener("wheel", this.updateScale); + + window["exportCanvasToImage"] = () => { + const download = document.createElement("a"); + download.href = this.stage.getStage().toDataURL(); + download.download = "opendc-canvas-export-" + Date.now() + ".png"; + download.click(); + }; + } + + componentWillUnmount() { + window.removeEventListener("resize", this.updateDimensions); + window.removeEventListener("wheel", this.updateScale); + } + + updateDimensions() { + this.props.setMapDimensions( + jQuery(window).width(), + jQuery(window).height() - NAVBAR_HEIGHT + ); + } + + updateScale(e) { + e.preventDefault(); + this.props.zoomInOnPosition( + e.deltaY < 0, + this.state.mouseX, + this.state.mouseY + ); + } + + updateMousePosition() { + const mousePos = this.stage.getStage().getPointerPosition(); + this.setState({ mouseX: mousePos.x, mouseY: mousePos.y }); + } + + handleShortcuts(action) { + switch (action) { + case "MOVE_LEFT": + this.moveWithDelta(MAP_MOVE_PIXELS_PER_EVENT, 0); + break; + case "MOVE_RIGHT": + this.moveWithDelta(-MAP_MOVE_PIXELS_PER_EVENT, 0); + break; + case "MOVE_UP": + this.moveWithDelta(0, MAP_MOVE_PIXELS_PER_EVENT); + break; + case "MOVE_DOWN": + this.moveWithDelta(0, -MAP_MOVE_PIXELS_PER_EVENT); + break; + default: + break; + } + } + + moveWithDelta(deltaX, deltaY) { + this.props.setMapPositionWithBoundsCheck( + this.props.mapPosition.x + deltaX, + this.props.mapPosition.y + deltaY + ); + } + + render() { + return ( + <Shortcuts + name="MAP" + handler={this.handleShortcuts.bind(this)} + targetNodeSelector="body" + > + <Stage + ref={stage => { + this.stage = stage; + }} + width={this.props.mapDimensions.width} + height={this.props.mapDimensions.height} + onMouseMove={this.updateMousePosition.bind(this)} + > + <Provider store={store}> + <MapLayer /> + <RoomHoverLayer + mouseX={this.state.mouseX} + mouseY={this.state.mouseY} + /> + <ObjectHoverLayer + mouseX={this.state.mouseX} + mouseY={this.state.mouseY} + /> + </Provider> + </Stage> + </Shortcuts> + ); + } +} + +export default MapStageComponent; diff --git a/frontend/src/components/app/map/controls/ExportCanvasComponent.js b/frontend/src/components/app/map/controls/ExportCanvasComponent.js new file mode 100644 index 00000000..ee934f21 --- /dev/null +++ b/frontend/src/components/app/map/controls/ExportCanvasComponent.js @@ -0,0 +1,13 @@ +import React from "react"; + +const ExportCanvasComponent = () => ( + <button + className="btn btn-success btn-circle btn-sm" + title="Export Canvas to PNG Image" + onClick={() => window["exportCanvasToImage"]()} + > + <span className="fa fa-camera" /> + </button> +); + +export default ExportCanvasComponent; diff --git a/frontend/src/components/app/map/controls/ScaleIndicatorComponent.js b/frontend/src/components/app/map/controls/ScaleIndicatorComponent.js new file mode 100644 index 00000000..b7b5cc36 --- /dev/null +++ b/frontend/src/components/app/map/controls/ScaleIndicatorComponent.js @@ -0,0 +1,14 @@ +import React from "react"; +import { TILE_SIZE_IN_METERS, TILE_SIZE_IN_PIXELS } from "../MapConstants"; +import "./ScaleIndicatorComponent.css"; + +const ScaleIndicatorComponent = ({ scale }) => ( + <div + className="scale-indicator" + style={{ width: TILE_SIZE_IN_PIXELS * scale }} + > + {TILE_SIZE_IN_METERS}m + </div> +); + +export default ScaleIndicatorComponent; diff --git a/frontend/src/components/app/map/controls/ScaleIndicatorComponent.sass b/frontend/src/components/app/map/controls/ScaleIndicatorComponent.sass new file mode 100644 index 00000000..f2d2b55b --- /dev/null +++ b/frontend/src/components/app/map/controls/ScaleIndicatorComponent.sass @@ -0,0 +1,9 @@ +.scale-indicator + position: absolute + right: 10px + bottom: 10px + z-index: 50 + + border: solid 2px #212529 + border-top: none + border-left: none diff --git a/frontend/src/components/app/map/controls/ToolPanelComponent.js b/frontend/src/components/app/map/controls/ToolPanelComponent.js new file mode 100644 index 00000000..605e9887 --- /dev/null +++ b/frontend/src/components/app/map/controls/ToolPanelComponent.js @@ -0,0 +1,13 @@ +import React from "react"; +import ZoomControlContainer from "../../../../containers/app/map/controls/ZoomControlContainer"; +import ExportCanvasComponent from "./ExportCanvasComponent"; +import "./ToolPanelComponent.css"; + +const ToolPanelComponent = () => ( + <div className="tool-panel"> + <ZoomControlContainer /> + <ExportCanvasComponent /> + </div> +); + +export default ToolPanelComponent; diff --git a/frontend/src/components/app/map/controls/ToolPanelComponent.sass b/frontend/src/components/app/map/controls/ToolPanelComponent.sass new file mode 100644 index 00000000..996712b3 --- /dev/null +++ b/frontend/src/components/app/map/controls/ToolPanelComponent.sass @@ -0,0 +1,5 @@ +.tool-panel + position: absolute + left: 10px + bottom: 10px + z-index: 50 diff --git a/frontend/src/components/app/map/controls/ZoomControlComponent.js b/frontend/src/components/app/map/controls/ZoomControlComponent.js new file mode 100644 index 00000000..e1b7491e --- /dev/null +++ b/frontend/src/components/app/map/controls/ZoomControlComponent.js @@ -0,0 +1,24 @@ +import React from "react"; + +const ZoomControlComponent = ({ zoomInOnCenter }) => { + return ( + <span> + <button + className="btn btn-default btn-circle btn-sm mr-1" + title="Zoom in" + onClick={() => zoomInOnCenter(true)} + > + <span className="fa fa-plus" /> + </button> + <button + className="btn btn-default btn-circle btn-sm mr-1" + title="Zoom out" + onClick={() => zoomInOnCenter(false)} + > + <span className="fa fa-minus" /> + </button> + </span> + ); +}; + +export default ZoomControlComponent; diff --git a/frontend/src/components/app/map/elements/Backdrop.js b/frontend/src/components/app/map/elements/Backdrop.js new file mode 100644 index 00000000..57414463 --- /dev/null +++ b/frontend/src/components/app/map/elements/Backdrop.js @@ -0,0 +1,16 @@ +import React from "react"; +import { Rect } from "react-konva"; +import { BACKDROP_COLOR } from "../../../../util/colors"; +import { MAP_SIZE_IN_PIXELS } from "../MapConstants"; + +const Backdrop = () => ( + <Rect + x={0} + y={0} + width={MAP_SIZE_IN_PIXELS} + height={MAP_SIZE_IN_PIXELS} + fill={BACKDROP_COLOR} + /> +); + +export default Backdrop; diff --git a/frontend/src/components/app/map/elements/GrayLayer.js b/frontend/src/components/app/map/elements/GrayLayer.js new file mode 100644 index 00000000..28fadd8a --- /dev/null +++ b/frontend/src/components/app/map/elements/GrayLayer.js @@ -0,0 +1,17 @@ +import React from "react"; +import { Rect } from "react-konva"; +import { GRAYED_OUT_AREA_COLOR } from "../../../../util/colors"; +import { MAP_SIZE_IN_PIXELS } from "../MapConstants"; + +const GrayLayer = ({ onClick }) => ( + <Rect + x={0} + y={0} + width={MAP_SIZE_IN_PIXELS} + height={MAP_SIZE_IN_PIXELS} + fill={GRAYED_OUT_AREA_COLOR} + onClick={onClick} + /> +); + +export default GrayLayer; diff --git a/frontend/src/components/app/map/elements/HoverTile.js b/frontend/src/components/app/map/elements/HoverTile.js new file mode 100644 index 00000000..42e6547c --- /dev/null +++ b/frontend/src/components/app/map/elements/HoverTile.js @@ -0,0 +1,30 @@ +import PropTypes from "prop-types"; +import React from "react"; +import { Rect } from "react-konva"; +import { + ROOM_HOVER_INVALID_COLOR, + ROOM_HOVER_VALID_COLOR +} from "../../../../util/colors"; +import { TILE_SIZE_IN_PIXELS } from "../MapConstants"; + +const HoverTile = ({ pixelX, pixelY, isValid, scale, onClick }) => ( + <Rect + x={pixelX} + y={pixelY} + scaleX={scale} + scaleY={scale} + width={TILE_SIZE_IN_PIXELS} + height={TILE_SIZE_IN_PIXELS} + fill={isValid ? ROOM_HOVER_VALID_COLOR : ROOM_HOVER_INVALID_COLOR} + onClick={onClick} + /> +); + +HoverTile.propTypes = { + pixelX: PropTypes.number.isRequired, + pixelY: PropTypes.number.isRequired, + isValid: PropTypes.bool.isRequired, + onClick: PropTypes.func.isRequired +}; + +export default HoverTile; diff --git a/frontend/src/components/app/map/elements/ImageComponent.js b/frontend/src/components/app/map/elements/ImageComponent.js new file mode 100644 index 00000000..cf41ddfe --- /dev/null +++ b/frontend/src/components/app/map/elements/ImageComponent.js @@ -0,0 +1,48 @@ +import PropTypes from "prop-types"; +import React from "react"; +import { Image } from "react-konva"; + +class ImageComponent extends React.Component { + static imageCaches = {}; + static propTypes = { + src: PropTypes.string.isRequired, + x: PropTypes.number.isRequired, + y: PropTypes.number.isRequired, + width: PropTypes.number.isRequired, + height: PropTypes.number.isRequired, + opacity: PropTypes.number.isRequired + }; + + state = { + image: null + }; + + componentDidMount() { + if (ImageComponent.imageCaches[this.props.src]) { + this.setState({ image: ImageComponent.imageCaches[this.props.src] }); + return; + } + + const image = new window.Image(); + image.src = this.props.src; + image.onload = () => { + this.setState({ image }); + ImageComponent.imageCaches[this.props.src] = image; + }; + } + + render() { + return ( + <Image + image={this.state.image} + x={this.props.x} + y={this.props.y} + width={this.props.width} + height={this.props.height} + opacity={this.props.opacity} + /> + ); + } +} + +export default ImageComponent; diff --git a/frontend/src/components/app/map/elements/RackFillBar.js b/frontend/src/components/app/map/elements/RackFillBar.js new file mode 100644 index 00000000..43701d97 --- /dev/null +++ b/frontend/src/components/app/map/elements/RackFillBar.js @@ -0,0 +1,89 @@ +import PropTypes from "prop-types"; +import React from "react"; +import { Group, Rect } from "react-konva"; +import { + RACK_ENERGY_BAR_BACKGROUND_COLOR, + RACK_ENERGY_BAR_FILL_COLOR, + RACK_SPACE_BAR_BACKGROUND_COLOR, + RACK_SPACE_BAR_FILL_COLOR +} from "../../../../util/colors"; +import { + OBJECT_BORDER_WIDTH_IN_PIXELS, + OBJECT_MARGIN_IN_PIXELS, + RACK_FILL_ICON_OPACITY, + RACK_FILL_ICON_WIDTH, + TILE_SIZE_IN_PIXELS +} from "../MapConstants"; +import ImageComponent from "./ImageComponent"; + +const RackFillBar = ({ positionX, positionY, type, fillFraction }) => { + const halfOfObjectBorderWidth = OBJECT_BORDER_WIDTH_IN_PIXELS / 2; + const x = + positionX * TILE_SIZE_IN_PIXELS + + OBJECT_MARGIN_IN_PIXELS + + (type === "space" + ? halfOfObjectBorderWidth + : 0.5 * (TILE_SIZE_IN_PIXELS - 2 * OBJECT_MARGIN_IN_PIXELS)); + const startY = + positionY * TILE_SIZE_IN_PIXELS + + OBJECT_MARGIN_IN_PIXELS + + halfOfObjectBorderWidth; + const width = + 0.5 * (TILE_SIZE_IN_PIXELS - OBJECT_MARGIN_IN_PIXELS * 2) - + halfOfObjectBorderWidth; + const fullHeight = + TILE_SIZE_IN_PIXELS - + OBJECT_MARGIN_IN_PIXELS * 2 - + OBJECT_BORDER_WIDTH_IN_PIXELS; + + const fractionHeight = fillFraction * fullHeight; + const fractionY = + (positionY + 1) * TILE_SIZE_IN_PIXELS - + OBJECT_MARGIN_IN_PIXELS - + halfOfObjectBorderWidth - + fractionHeight; + + return ( + <Group> + <Rect + x={x} + y={startY} + width={width} + height={fullHeight} + fill={ + type === "space" + ? RACK_SPACE_BAR_BACKGROUND_COLOR + : RACK_ENERGY_BAR_BACKGROUND_COLOR + } + /> + <Rect + x={x} + y={fractionY} + width={width} + height={fractionHeight} + fill={ + type === "space" + ? RACK_SPACE_BAR_FILL_COLOR + : RACK_ENERGY_BAR_FILL_COLOR + } + /> + <ImageComponent + src={"/img/topology/rack-" + type + "-icon.png"} + x={x + width * 0.5 - RACK_FILL_ICON_WIDTH * 0.5} + y={startY + fullHeight * 0.5 - RACK_FILL_ICON_WIDTH * 0.5} + width={RACK_FILL_ICON_WIDTH} + height={RACK_FILL_ICON_WIDTH} + opacity={RACK_FILL_ICON_OPACITY} + /> + </Group> + ); +}; + +RackFillBar.propTypes = { + positionX: PropTypes.number.isRequired, + positionY: PropTypes.number.isRequired, + type: PropTypes.string.isRequired, + fillFraction: PropTypes.number.isRequired +}; + +export default RackFillBar; diff --git a/frontend/src/components/app/map/elements/RoomTile.js b/frontend/src/components/app/map/elements/RoomTile.js new file mode 100644 index 00000000..71c3bf15 --- /dev/null +++ b/frontend/src/components/app/map/elements/RoomTile.js @@ -0,0 +1,20 @@ +import React from "react"; +import { Rect } from "react-konva"; +import Shapes from "../../../../shapes/index"; +import { TILE_SIZE_IN_PIXELS } from "../MapConstants"; + +const RoomTile = ({ tile, color }) => ( + <Rect + x={tile.positionX * TILE_SIZE_IN_PIXELS} + y={tile.positionY * TILE_SIZE_IN_PIXELS} + width={TILE_SIZE_IN_PIXELS} + height={TILE_SIZE_IN_PIXELS} + fill={color} + /> +); + +RoomTile.propTypes = { + tile: Shapes.Tile +}; + +export default RoomTile; diff --git a/frontend/src/components/app/map/elements/TileObject.js b/frontend/src/components/app/map/elements/TileObject.js new file mode 100644 index 00000000..c1b631db --- /dev/null +++ b/frontend/src/components/app/map/elements/TileObject.js @@ -0,0 +1,29 @@ +import PropTypes from "prop-types"; +import React from "react"; +import { Rect } from "react-konva"; +import { OBJECT_BORDER_COLOR } from "../../../../util/colors"; +import { + OBJECT_BORDER_WIDTH_IN_PIXELS, + OBJECT_MARGIN_IN_PIXELS, + TILE_SIZE_IN_PIXELS +} from "../MapConstants"; + +const TileObject = ({ positionX, positionY, color }) => ( + <Rect + x={positionX * TILE_SIZE_IN_PIXELS + OBJECT_MARGIN_IN_PIXELS} + y={positionY * TILE_SIZE_IN_PIXELS + OBJECT_MARGIN_IN_PIXELS} + width={TILE_SIZE_IN_PIXELS - OBJECT_MARGIN_IN_PIXELS * 2} + height={TILE_SIZE_IN_PIXELS - OBJECT_MARGIN_IN_PIXELS * 2} + fill={color} + stroke={OBJECT_BORDER_COLOR} + strokeWidth={OBJECT_BORDER_WIDTH_IN_PIXELS} + /> +); + +TileObject.propTypes = { + positionX: PropTypes.number.isRequired, + positionY: PropTypes.number.isRequired, + color: PropTypes.string.isRequired +}; + +export default TileObject; diff --git a/frontend/src/components/app/map/elements/TilePlusIcon.js b/frontend/src/components/app/map/elements/TilePlusIcon.js new file mode 100644 index 00000000..06377152 --- /dev/null +++ b/frontend/src/components/app/map/elements/TilePlusIcon.js @@ -0,0 +1,52 @@ +import PropTypes from "prop-types"; +import React from "react"; +import { Group, Line } from "react-konva"; +import { TILE_PLUS_COLOR } from "../../../../util/colors"; +import { + TILE_PLUS_MARGIN_IN_PIXELS, + TILE_PLUS_WIDTH_IN_PIXELS, + TILE_SIZE_IN_PIXELS +} from "../MapConstants"; + +const TilePlusIcon = ({ pixelX, pixelY, mapScale }) => { + const linePoints = [ + [ + pixelX + 0.5 * TILE_SIZE_IN_PIXELS * mapScale, + pixelY + TILE_PLUS_MARGIN_IN_PIXELS * mapScale, + pixelX + 0.5 * TILE_SIZE_IN_PIXELS * mapScale, + pixelY + + TILE_SIZE_IN_PIXELS * mapScale - + TILE_PLUS_MARGIN_IN_PIXELS * mapScale + ], + [ + pixelX + TILE_PLUS_MARGIN_IN_PIXELS * mapScale, + pixelY + 0.5 * TILE_SIZE_IN_PIXELS * mapScale, + pixelX + + TILE_SIZE_IN_PIXELS * mapScale - + TILE_PLUS_MARGIN_IN_PIXELS * mapScale, + pixelY + 0.5 * TILE_SIZE_IN_PIXELS * mapScale + ] + ]; + return ( + <Group> + {linePoints.map((points, index) => ( + <Line + key={index} + points={points} + lineCap="round" + stroke={TILE_PLUS_COLOR} + strokeWidth={TILE_PLUS_WIDTH_IN_PIXELS * mapScale} + listening={false} + /> + ))} + </Group> + ); +}; + +TilePlusIcon.propTypes = { + pixelX: PropTypes.number, + pixelY: PropTypes.number, + mapScale: PropTypes.number +}; + +export default TilePlusIcon; diff --git a/frontend/src/components/app/map/elements/WallSegment.js b/frontend/src/components/app/map/elements/WallSegment.js new file mode 100644 index 00000000..c5011656 --- /dev/null +++ b/frontend/src/components/app/map/elements/WallSegment.js @@ -0,0 +1,39 @@ +import React from "react"; +import { Line } from "react-konva"; +import Shapes from "../../../../shapes/index"; +import { WALL_COLOR } from "../../../../util/colors"; +import { TILE_SIZE_IN_PIXELS, WALL_WIDTH_IN_PIXELS } from "../MapConstants"; + +const WallSegment = ({ wallSegment }) => { + let points; + if (wallSegment.isHorizontal) { + points = [ + wallSegment.startPosX * TILE_SIZE_IN_PIXELS, + wallSegment.startPosY * TILE_SIZE_IN_PIXELS, + (wallSegment.startPosX + wallSegment.length) * TILE_SIZE_IN_PIXELS, + wallSegment.startPosY * TILE_SIZE_IN_PIXELS + ]; + } else { + points = [ + wallSegment.startPosX * TILE_SIZE_IN_PIXELS, + wallSegment.startPosY * TILE_SIZE_IN_PIXELS, + wallSegment.startPosX * TILE_SIZE_IN_PIXELS, + (wallSegment.startPosY + wallSegment.length) * TILE_SIZE_IN_PIXELS + ]; + } + + return ( + <Line + points={points} + lineCap="round" + stroke={WALL_COLOR} + strokeWidth={WALL_WIDTH_IN_PIXELS} + /> + ); +}; + +WallSegment.propTypes = { + wallSegment: Shapes.WallSegment +}; + +export default WallSegment; diff --git a/frontend/src/components/app/map/groups/DatacenterGroup.js b/frontend/src/components/app/map/groups/DatacenterGroup.js new file mode 100644 index 00000000..51e32db6 --- /dev/null +++ b/frontend/src/components/app/map/groups/DatacenterGroup.js @@ -0,0 +1,40 @@ +import React from "react"; +import { Group } from "react-konva"; +import GrayContainer from "../../../../containers/app/map/GrayContainer"; +import RoomContainer from "../../../../containers/app/map/RoomContainer"; +import Shapes from "../../../../shapes/index"; + +const DatacenterGroup = ({ datacenter, interactionLevel }) => { + if (!datacenter) { + return <Group />; + } + + if (interactionLevel.mode === "BUILDING") { + return ( + <Group> + {datacenter.roomIds.map(roomId => ( + <RoomContainer key={roomId} roomId={roomId} /> + ))} + </Group> + ); + } + + return ( + <Group> + {datacenter.roomIds + .filter(roomId => roomId !== interactionLevel.roomId) + .map(roomId => <RoomContainer key={roomId} roomId={roomId} />)} + {interactionLevel.mode === "ROOM" ? <GrayContainer /> : null} + {datacenter.roomIds + .filter(roomId => roomId === interactionLevel.roomId) + .map(roomId => <RoomContainer key={roomId} roomId={roomId} />)} + </Group> + ); +}; + +DatacenterGroup.propTypes = { + datacenter: Shapes.Datacenter, + interactionLevel: Shapes.InteractionLevel +}; + +export default DatacenterGroup; diff --git a/frontend/src/components/app/map/groups/GridGroup.js b/frontend/src/components/app/map/groups/GridGroup.js new file mode 100644 index 00000000..bbb1eb68 --- /dev/null +++ b/frontend/src/components/app/map/groups/GridGroup.js @@ -0,0 +1,41 @@ +import React from "react"; +import { Group, Line } from "react-konva"; +import { GRID_COLOR } from "../../../../util/colors"; +import { + GRID_LINE_WIDTH_IN_PIXELS, + MAP_SIZE, + MAP_SIZE_IN_PIXELS, + TILE_SIZE_IN_PIXELS +} from "../MapConstants"; + +const MAP_COORDINATE_ENTRIES = Array.from(new Array(MAP_SIZE), (x, i) => i); +const HORIZONTAL_POINT_PAIRS = MAP_COORDINATE_ENTRIES.map(index => [ + 0, + index * TILE_SIZE_IN_PIXELS, + MAP_SIZE_IN_PIXELS, + index * TILE_SIZE_IN_PIXELS +]); +const VERTICAL_POINT_PAIRS = MAP_COORDINATE_ENTRIES.map(index => [ + index * TILE_SIZE_IN_PIXELS, + 0, + index * TILE_SIZE_IN_PIXELS, + MAP_SIZE_IN_PIXELS +]); + +const GridGroup = () => ( + <Group> + {HORIZONTAL_POINT_PAIRS.concat( + VERTICAL_POINT_PAIRS + ).map((points, index) => ( + <Line + key={index} + points={points} + stroke={GRID_COLOR} + strokeWidth={GRID_LINE_WIDTH_IN_PIXELS} + listening={false} + /> + ))} + </Group> +); + +export default GridGroup; diff --git a/frontend/src/components/app/map/groups/RackGroup.js b/frontend/src/components/app/map/groups/RackGroup.js new file mode 100644 index 00000000..69d6ac10 --- /dev/null +++ b/frontend/src/components/app/map/groups/RackGroup.js @@ -0,0 +1,43 @@ +import React from "react"; +import { Group } from "react-konva"; +import RackEnergyFillContainer from "../../../../containers/app/map/RackEnergyFillContainer"; +import RackSpaceFillContainer from "../../../../containers/app/map/RackSpaceFillContainer"; +import Shapes from "../../../../shapes/index"; +import { RACK_BACKGROUND_COLOR } from "../../../../util/colors"; +import { convertLoadToSimulationColor } from "../../../../util/simulation-load"; +import TileObject from "../elements/TileObject"; + +const RackGroup = ({ tile, inSimulation, rackLoad }) => { + let color = RACK_BACKGROUND_COLOR; + if (inSimulation && rackLoad >= 0) { + color = convertLoadToSimulationColor(rackLoad); + } + + return ( + <Group> + <TileObject + positionX={tile.positionX} + positionY={tile.positionY} + color={color} + /> + <Group opacity={inSimulation ? 0.3 : 1}> + <RackSpaceFillContainer + tileId={tile.id} + positionX={tile.positionX} + positionY={tile.positionY} + /> + <RackEnergyFillContainer + tileId={tile.id} + positionX={tile.positionX} + positionY={tile.positionY} + /> + </Group> + </Group> + ); +}; + +RackGroup.propTypes = { + tile: Shapes.Tile +}; + +export default RackGroup; diff --git a/frontend/src/components/app/map/groups/RoomGroup.js b/frontend/src/components/app/map/groups/RoomGroup.js new file mode 100644 index 00000000..c8f0d3db --- /dev/null +++ b/frontend/src/components/app/map/groups/RoomGroup.js @@ -0,0 +1,56 @@ +import React from "react"; +import { Group } from "react-konva"; +import GrayContainer from "../../../../containers/app/map/GrayContainer"; +import TileContainer from "../../../../containers/app/map/TileContainer"; +import WallContainer from "../../../../containers/app/map/WallContainer"; +import Shapes from "../../../../shapes/index"; + +const RoomGroup = ({ + room, + interactionLevel, + currentRoomInConstruction, + onClick +}) => { + if (currentRoomInConstruction === room.id) { + return ( + <Group onClick={onClick}> + {room.tileIds.map(tileId => ( + <TileContainer key={tileId} tileId={tileId} newTile={true} /> + ))} + </Group> + ); + } + + return ( + <Group onClick={onClick}> + {(() => { + if ( + (interactionLevel.mode === "RACK" || + interactionLevel.mode === "MACHINE") && + interactionLevel.roomId === room.id + ) { + return [ + room.tileIds + .filter(tileId => tileId !== interactionLevel.tileId) + .map(tileId => <TileContainer key={tileId} tileId={tileId} />), + <GrayContainer key={-1} />, + room.tileIds + .filter(tileId => tileId === interactionLevel.tileId) + .map(tileId => <TileContainer key={tileId} tileId={tileId} />) + ]; + } else { + return room.tileIds.map(tileId => ( + <TileContainer key={tileId} tileId={tileId} /> + )); + } + })()} + <WallContainer roomId={room.id} /> + </Group> + ); +}; + +RoomGroup.propTypes = { + room: Shapes.Room +}; + +export default RoomGroup; diff --git a/frontend/src/components/app/map/groups/TileGroup.js b/frontend/src/components/app/map/groups/TileGroup.js new file mode 100644 index 00000000..8f3953d7 --- /dev/null +++ b/frontend/src/components/app/map/groups/TileGroup.js @@ -0,0 +1,43 @@ +import PropTypes from "prop-types"; +import React from "react"; +import { Group } from "react-konva"; +import RackContainer from "../../../../containers/app/map/RackContainer"; +import Shapes from "../../../../shapes/index"; +import { + ROOM_DEFAULT_COLOR, + ROOM_IN_CONSTRUCTION_COLOR +} from "../../../../util/colors"; +import { convertLoadToSimulationColor } from "../../../../util/simulation-load"; +import RoomTile from "../elements/RoomTile"; + +const TileGroup = ({ tile, newTile, inSimulation, roomLoad, onClick }) => { + let tileObject; + switch (tile.objectType) { + case "RACK": + tileObject = <RackContainer tile={tile} />; + break; + default: + tileObject = null; + } + + let color = ROOM_DEFAULT_COLOR; + if (newTile) { + color = ROOM_IN_CONSTRUCTION_COLOR; + } else if (inSimulation && roomLoad >= 0) { + color = convertLoadToSimulationColor(roomLoad); + } + + return ( + <Group onClick={() => onClick(tile)}> + <RoomTile tile={tile} color={color} /> + {tileObject} + </Group> + ); +}; + +TileGroup.propTypes = { + tile: Shapes.Tile, + newTile: PropTypes.bool +}; + +export default TileGroup; diff --git a/frontend/src/components/app/map/groups/WallGroup.js b/frontend/src/components/app/map/groups/WallGroup.js new file mode 100644 index 00000000..43de66e8 --- /dev/null +++ b/frontend/src/components/app/map/groups/WallGroup.js @@ -0,0 +1,22 @@ +import PropTypes from "prop-types"; +import React from "react"; +import { Group } from "react-konva"; +import Shapes from "../../../../shapes/index"; +import { deriveWallLocations } from "../../../../util/tile-calculations"; +import WallSegment from "../elements/WallSegment"; + +const WallGroup = ({ tiles }) => { + return ( + <Group> + {deriveWallLocations(tiles).map((wallSegment, index) => ( + <WallSegment key={index} wallSegment={wallSegment} /> + ))} + </Group> + ); +}; + +WallGroup.propTypes = { + tiles: PropTypes.arrayOf(Shapes.Tile).isRequired +}; + +export default WallGroup; diff --git a/frontend/src/components/app/map/layers/HoverLayerComponent.js b/frontend/src/components/app/map/layers/HoverLayerComponent.js new file mode 100644 index 00000000..c39532f1 --- /dev/null +++ b/frontend/src/components/app/map/layers/HoverLayerComponent.js @@ -0,0 +1,85 @@ +import PropTypes from "prop-types"; +import React from "react"; +import { Layer } from "react-konva"; +import HoverTile from "../elements/HoverTile"; +import { TILE_SIZE_IN_PIXELS } from "../MapConstants"; + +class HoverLayerComponent extends React.Component { + static propTypes = { + mouseX: PropTypes.number.isRequired, + mouseY: PropTypes.number.isRequired, + mapPosition: PropTypes.object.isRequired, + mapScale: PropTypes.number.isRequired, + isEnabled: PropTypes.func.isRequired, + onClick: PropTypes.func.isRequired + }; + + state = { + positionX: -1, + positionY: -1, + validity: false + }; + + componentDidUpdate() { + if (!this.props.isEnabled()) { + return; + } + + const positionX = Math.floor( + (this.props.mouseX - this.props.mapPosition.x) / + (this.props.mapScale * TILE_SIZE_IN_PIXELS) + ); + const positionY = Math.floor( + (this.props.mouseY - this.props.mapPosition.y) / + (this.props.mapScale * TILE_SIZE_IN_PIXELS) + ); + + if ( + positionX !== this.state.positionX || + positionY !== this.state.positionY + ) { + this.setState({ + positionX, + positionY, + validity: this.props.isValid(positionX, positionY) + }); + } + } + + render() { + if (!this.props.isEnabled()) { + return <Layer />; + } + + const pixelX = + this.props.mapScale * this.state.positionX * TILE_SIZE_IN_PIXELS + + this.props.mapPosition.x; + const pixelY = + this.props.mapScale * this.state.positionY * TILE_SIZE_IN_PIXELS + + this.props.mapPosition.y; + + return ( + <Layer opacity={0.6}> + <HoverTile + pixelX={pixelX} + pixelY={pixelY} + scale={this.props.mapScale} + isValid={this.state.validity} + onClick={() => + this.state.validity + ? this.props.onClick(this.state.positionX, this.state.positionY) + : undefined} + /> + {this.props.children + ? React.cloneElement(this.props.children, { + pixelX, + pixelY, + scale: this.props.mapScale + }) + : undefined} + </Layer> + ); + } +} + +export default HoverLayerComponent; diff --git a/frontend/src/components/app/map/layers/MapLayerComponent.js b/frontend/src/components/app/map/layers/MapLayerComponent.js new file mode 100644 index 00000000..6ad3cb88 --- /dev/null +++ b/frontend/src/components/app/map/layers/MapLayerComponent.js @@ -0,0 +1,22 @@ +import React from "react"; +import { Group, Layer } from "react-konva"; +import DatacenterContainer from "../../../../containers/app/map/DatacenterContainer"; +import Backdrop from "../elements/Backdrop"; +import GridGroup from "../groups/GridGroup"; + +const MapLayerComponent = ({ mapPosition, mapScale }) => ( + <Layer> + <Group + x={mapPosition.x} + y={mapPosition.y} + scaleX={mapScale} + scaleY={mapScale} + > + <Backdrop /> + <DatacenterContainer /> + <GridGroup /> + </Group> + </Layer> +); + +export default MapLayerComponent; diff --git a/frontend/src/components/app/map/layers/ObjectHoverLayerComponent.js b/frontend/src/components/app/map/layers/ObjectHoverLayerComponent.js new file mode 100644 index 00000000..e7342d3c --- /dev/null +++ b/frontend/src/components/app/map/layers/ObjectHoverLayerComponent.js @@ -0,0 +1,11 @@ +import React from "react"; +import TilePlusIcon from "../elements/TilePlusIcon"; +import HoverLayerComponent from "./HoverLayerComponent"; + +const ObjectHoverLayerComponent = props => ( + <HoverLayerComponent {...props}> + <TilePlusIcon {...props} /> + </HoverLayerComponent> +); + +export default ObjectHoverLayerComponent; diff --git a/frontend/src/components/app/map/layers/RoomHoverLayerComponent.js b/frontend/src/components/app/map/layers/RoomHoverLayerComponent.js new file mode 100644 index 00000000..feea5ae5 --- /dev/null +++ b/frontend/src/components/app/map/layers/RoomHoverLayerComponent.js @@ -0,0 +1,6 @@ +import React from "react"; +import HoverLayerComponent from "./HoverLayerComponent"; + +const RoomHoverLayerComponent = props => <HoverLayerComponent {...props} />; + +export default RoomHoverLayerComponent; diff --git a/frontend/src/components/app/sidebars/Sidebar.js b/frontend/src/components/app/sidebars/Sidebar.js new file mode 100644 index 00000000..33dbe011 --- /dev/null +++ b/frontend/src/components/app/sidebars/Sidebar.js @@ -0,0 +1,50 @@ +import classNames from "classnames"; +import React from "react"; +import "./Sidebar.css"; + +class Sidebar extends React.Component { + state = { + collapsed: false + }; + + render() { + const collapseButton = ( + <div + className={classNames("sidebar-collapse-button", { + "sidebar-collapse-button-right": this.props.isRight + })} + onClick={() => this.setState({ collapsed: !this.state.collapsed })} + > + {(this.state.collapsed && this.props.isRight) || + (!this.state.collapsed && !this.props.isRight) ? ( + <span + className="fa fa-angle-left" + title={this.props.isRight ? "Expand" : "Collapse"} + /> + ) : ( + <span + className="fa fa-angle-right" + title={this.props.isRight ? "Collapse" : "Expand"} + /> + )} + </div> + ); + + if (this.state.collapsed) { + return collapseButton; + } + return ( + <div + className={classNames("sidebar p-3 h-100", { + "sidebar-right": this.props.isRight + })} + onWheel={e => e.stopPropagation()} + > + {this.props.children} + {collapseButton} + </div> + ); + } +} + +export default Sidebar; diff --git a/frontend/src/components/app/sidebars/Sidebar.sass b/frontend/src/components/app/sidebars/Sidebar.sass new file mode 100644 index 00000000..4d0e5f1e --- /dev/null +++ b/frontend/src/components/app/sidebars/Sidebar.sass @@ -0,0 +1,50 @@ +@import ../../../style-globals/_variables.sass +@import ../../../style-globals/_mixins.sass + +.sidebar-collapse-button + position: absolute + left: 5px + top: 5px + padding: 5px 7px + + background: white + border: solid 1px $gray-semi-light + z-index: 99 + + +clickable + +border-radius(5px) + +transition(background, 200ms) + + &.sidebar-collapse-button-right + left: auto + right: 5px + top: 5px + + &:hover + background: #eeeeee + +.sidebar + position: absolute + top: 0 + left: 0 + width: 350px + + z-index: 100 + background: white + + border-right: $gray-semi-dark 1px solid + + .sidebar-collapse-button + left: auto + right: -25px + +.sidebar-right + left: auto + right: 0 + + border-left: $gray-semi-dark 1px solid + border-right: none + + .sidebar-collapse-button-right + left: -25px + right: auto diff --git a/frontend/src/components/app/sidebars/elements/LoadBarComponent.js b/frontend/src/components/app/sidebars/elements/LoadBarComponent.js new file mode 100644 index 00000000..8c9b164b --- /dev/null +++ b/frontend/src/components/app/sidebars/elements/LoadBarComponent.js @@ -0,0 +1,22 @@ +import classNames from "classnames"; +import React from "react"; + +const LoadBarComponent = ({ percent, disabled }) => ( + <div className="mt-1"> + <strong>Current load</strong> + <div className={classNames("progress", { disabled })}> + <div + className="progress-bar" + role="progressbar" + aria-valuenow={percent} + aria-valuemin="0" + aria-valuemax="100" + style={{ width: percent + "%" }} + > + {percent}% + </div> + </div> + </div> +); + +export default LoadBarComponent; diff --git a/frontend/src/components/app/sidebars/elements/LoadChartComponent.js b/frontend/src/components/app/sidebars/elements/LoadChartComponent.js new file mode 100644 index 00000000..5f0d40cb --- /dev/null +++ b/frontend/src/components/app/sidebars/elements/LoadChartComponent.js @@ -0,0 +1,90 @@ +import React from "react"; +import ReactDOM from "react-dom/server"; +import SvgSaver from "svgsaver"; +import { + VictoryAxis, + VictoryChart, + VictoryLabel, + VictoryLine, + VictoryScatter +} from "victory"; +import { convertSecondsToFormattedTime } from "../../../../util/date-time"; + +const LoadChartComponent = ({ data, currentTick }) => { + const onExport = () => { + const div = document.createElement("div"); + div.innerHTML = ReactDOM.renderToString( + <VictoryChartComponent + data={data} + currentTick={currentTick} + showCurrentTick={false} + /> + ); + div.firstChild.style = + "font-family: Roboto, Arial, sans-serif; font-size: 10pt;"; + const svgSaver = new SvgSaver(); + svgSaver.asSvg( + div.firstChild, + "opendc-chart-export-" + Date.now() + ".svg" + ); + }; + + return ( + <div className="mt-1" style={{ position: "relative" }}> + <strong>Load over time</strong> + <VictoryChartComponent + data={data} + currentTick={currentTick} + showCurrentTick={true} + /> + <ExportChartComponent onExport={onExport} /> + </div> + ); +}; + +const VictoryChartComponent = ({ data, currentTick, showCurrentTick }) => ( + <VictoryChart + height={250} + padding={{ top: 10, bottom: 50, left: 50, right: 50 }} + > + <VictoryAxis + tickFormat={tick => convertSecondsToFormattedTime(tick)} + fixLabelOverlap={true} + label="Simulated Time" + /> + <VictoryAxis dependentAxis label="Load" /> + <VictoryLine data={data} /> + <VictoryScatter data={data} /> + {showCurrentTick ? ( + <VictoryLine + labelComponent={ + <VictoryLabel renderInPortal angle={90} dy={-5} dx={60} /> + } + data={[{ x: currentTick + 1, y: 0 }, { x: currentTick + 1, y: 1 }]} + labels={point => + point.y === 1 + ? "Current tick : " + convertSecondsToFormattedTime(currentTick) + : ""} + style={{ + data: { stroke: "#00A6D6", strokeWidth: 4 }, + labels: { fill: "#00A6D6" } + }} + /> + ) : ( + undefined + )} + </VictoryChart> +); + +const ExportChartComponent = ({ onExport }) => ( + <button + className="btn btn-success btn-circle btn-sm" + title="Export Chart to PNG Image" + onClick={onExport} + style={{ position: "absolute", top: 0, right: 0 }} + > + <span className="fa fa-camera" /> + </button> +); + +export default LoadChartComponent; diff --git a/frontend/src/components/app/sidebars/simulation/ExperimentMetadataComponent.js b/frontend/src/components/app/sidebars/simulation/ExperimentMetadataComponent.js new file mode 100644 index 00000000..bc563dab --- /dev/null +++ b/frontend/src/components/app/sidebars/simulation/ExperimentMetadataComponent.js @@ -0,0 +1,23 @@ +import React from "react"; + +const ExperimentMetadataComponent = ({ + experimentName, + pathName, + traceName, + schedulerName +}) => ( + <div> + <h2>{experimentName}</h2> + <p> + Path: <strong>{pathName}</strong> + </p> + <p> + Trace: <strong>{traceName}</strong> + </p> + <p> + Scheduler: <strong>{schedulerName}</strong> + </p> + </div> +); + +export default ExperimentMetadataComponent; diff --git a/frontend/src/components/app/sidebars/simulation/LoadMetricComponent.js b/frontend/src/components/app/sidebars/simulation/LoadMetricComponent.js new file mode 100644 index 00000000..3e4cf810 --- /dev/null +++ b/frontend/src/components/app/sidebars/simulation/LoadMetricComponent.js @@ -0,0 +1,40 @@ +import React from "react"; +import { + SIM_HIGH_COLOR, + SIM_LOW_COLOR, + SIM_MID_HIGH_COLOR, + SIM_MID_LOW_COLOR +} from "../../../../util/colors"; +import { LOAD_NAME_MAP } from "../../../../util/simulation-load"; + +const LoadMetricComponent = ({ loadMetric }) => ( + <div> + <div> + Colors represent <strong>{LOAD_NAME_MAP[loadMetric]}</strong> + </div> + <div className="btn-group mb-2" style={{ display: "flex" }}> + <span + className="btn btn-secondary" + style={{ backgroundColor: SIM_LOW_COLOR, flex: 1 }} + title="0-25%" + /> + <span + className="btn btn-secondary" + style={{ backgroundColor: SIM_MID_LOW_COLOR, flex: 1 }} + title="25-50%" + /> + <span + className="btn btn-secondary" + style={{ backgroundColor: SIM_MID_HIGH_COLOR, flex: 1 }} + title="50-75%" + /> + <span + className="btn btn-secondary" + style={{ backgroundColor: SIM_HIGH_COLOR, flex: 1 }} + title="75-100%" + /> + </div> + </div> +); + +export default LoadMetricComponent; diff --git a/frontend/src/components/app/sidebars/simulation/SimulationSidebarComponent.js b/frontend/src/components/app/sidebars/simulation/SimulationSidebarComponent.js new file mode 100644 index 00000000..08dbb29a --- /dev/null +++ b/frontend/src/components/app/sidebars/simulation/SimulationSidebarComponent.js @@ -0,0 +1,22 @@ +import React from "react"; +import ExperimentMetadataContainer from "../../../../containers/app/sidebars/simulation/ExperimentMetadataContainer"; +import LoadMetricContainer from "../../../../containers/app/sidebars/simulation/LoadMetricContainer"; +import TraceContainer from "../../../../containers/app/sidebars/simulation/TraceContainer"; +import Sidebar from "../Sidebar"; +import "./SimulationSidebarComponent.css"; + +const SimulationSidebarComponent = () => { + return ( + <Sidebar isRight={false}> + <div className="simulation-sidebar-container flex-column"> + <ExperimentMetadataContainer /> + <LoadMetricContainer /> + <div className="trace-container"> + <TraceContainer /> + </div> + </div> + </Sidebar> + ); +}; + +export default SimulationSidebarComponent; diff --git a/frontend/src/components/app/sidebars/simulation/SimulationSidebarComponent.sass b/frontend/src/components/app/sidebars/simulation/SimulationSidebarComponent.sass new file mode 100644 index 00000000..82af97fa --- /dev/null +++ b/frontend/src/components/app/sidebars/simulation/SimulationSidebarComponent.sass @@ -0,0 +1,8 @@ +.simulation-sidebar-container + display: flex + height: 100% + max-height: 100% + +.trace-container + flex: 1 + overflow-y: scroll diff --git a/frontend/src/components/app/sidebars/simulation/TaskComponent.js b/frontend/src/components/app/sidebars/simulation/TaskComponent.js new file mode 100644 index 00000000..bd917cc9 --- /dev/null +++ b/frontend/src/components/app/sidebars/simulation/TaskComponent.js @@ -0,0 +1,58 @@ +import approx from "approximate-number"; +import classNames from "classnames"; +import React from "react"; +import { convertSecondsToFormattedTime } from "../../../../util/date-time"; + +const TaskComponent = ({ task, flopsLeft }) => { + let icon; + let progressBarContent; + let percent; + let infoTitle; + + if (flopsLeft === task.totalFlopCount) { + icon = "hourglass-half"; + progressBarContent = ""; + percent = 0; + infoTitle = "Not submitted yet"; + } else if (flopsLeft > 0) { + icon = "refresh"; + progressBarContent = approx(task.totalFlopCount - flopsLeft) + " FLOP"; + percent = 100 * (task.totalFlopCount - flopsLeft) / task.totalFlopCount; + infoTitle = + progressBarContent + " (" + Math.round(percent * 10) / 10 + "%)"; + } else { + icon = "check"; + progressBarContent = "Completed"; + percent = 100; + infoTitle = "Completed"; + } + + return ( + <li className="list-group-item flex-column align-items-start"> + <div className="d-flex w-100 justify-content-between"> + <h5 className="mb-1">{approx(task.totalFlopCount)} FLOP</h5> + <small>Starts at {convertSecondsToFormattedTime(task.startTick)}</small> + </div> + <div title={infoTitle} style={{ display: "flex" }}> + <span + className={classNames("fa", "fa-" + icon)} + style={{ width: "20px" }} + /> + <div className="progress" style={{ flexGrow: 1 }}> + <div + className="progress-bar" + role="progressbar" + aria-valuenow={percent} + aria-valuemin="0" + aria-valuemax="100" + style={{ width: percent + "%" }} + > + {progressBarContent} + </div> + </div> + </div> + </li> + ); +}; + +export default TaskComponent; diff --git a/frontend/src/components/app/sidebars/simulation/TraceComponent.js b/frontend/src/components/app/sidebars/simulation/TraceComponent.js new file mode 100644 index 00000000..2b6559b4 --- /dev/null +++ b/frontend/src/components/app/sidebars/simulation/TraceComponent.js @@ -0,0 +1,20 @@ +import React from "react"; +import TaskContainer from "../../../../containers/app/sidebars/simulation/TaskContainer"; + +const TraceComponent = ({ jobs }) => ( + <div> + <h3>Trace</h3> + {jobs.map(job => ( + <div key={job.id}> + <h4>Job: {job.name}</h4> + <ul className="list-group"> + {job.taskIds.map(taskId => ( + <TaskContainer taskId={taskId} key={taskId} /> + ))} + </ul> + </div> + ))} + </div> +); + +export default TraceComponent; diff --git a/frontend/src/components/app/sidebars/topology/NameComponent.js b/frontend/src/components/app/sidebars/topology/NameComponent.js new file mode 100644 index 00000000..805538b3 --- /dev/null +++ b/frontend/src/components/app/sidebars/topology/NameComponent.js @@ -0,0 +1,13 @@ +import React from "react"; +import FontAwesome from "react-fontawesome"; + +const NameComponent = ({ name, onEdit }) => ( + <h2> + {name} + <button className="btn btn-outline-secondary float-right" onClick={onEdit}> + <FontAwesome name="pencil" /> + </button> + </h2> +); + +export default NameComponent; diff --git a/frontend/src/components/app/sidebars/topology/TopologySidebarComponent.js b/frontend/src/components/app/sidebars/topology/TopologySidebarComponent.js new file mode 100644 index 00000000..81e510a1 --- /dev/null +++ b/frontend/src/components/app/sidebars/topology/TopologySidebarComponent.js @@ -0,0 +1,31 @@ +import React from "react"; +import BuildingSidebarContainer from "../../../../containers/app/sidebars/topology/building/BuildingSidebarContainer"; +import MachineSidebarContainer from "../../../../containers/app/sidebars/topology/machine/MachineSidebarContainer"; +import RackSidebarContainer from "../../../../containers/app/sidebars/topology/rack/RackSidebarContainer"; +import RoomSidebarContainer from "../../../../containers/app/sidebars/topology/room/RoomSidebarContainer"; +import Sidebar from "../Sidebar"; + +const TopologySidebarComponent = ({ interactionLevel }) => { + let sidebarContent; + + switch (interactionLevel.mode) { + case "BUILDING": + sidebarContent = <BuildingSidebarContainer />; + break; + case "ROOM": + sidebarContent = <RoomSidebarContainer />; + break; + case "RACK": + sidebarContent = <RackSidebarContainer />; + break; + case "MACHINE": + sidebarContent = <MachineSidebarContainer />; + break; + default: + sidebarContent = "Missing Content"; + } + + return <Sidebar isRight={true}>{sidebarContent}</Sidebar>; +}; + +export default TopologySidebarComponent; diff --git a/frontend/src/components/app/sidebars/topology/building/BuildingSidebarComponent.js b/frontend/src/components/app/sidebars/topology/building/BuildingSidebarComponent.js new file mode 100644 index 00000000..f16c19f0 --- /dev/null +++ b/frontend/src/components/app/sidebars/topology/building/BuildingSidebarComponent.js @@ -0,0 +1,20 @@ +import React from "react"; +import NewRoomConstructionContainer from "../../../../../containers/app/sidebars/topology/building/NewRoomConstructionContainer"; + +const BuildingSidebarComponent = ({ inSimulation }) => { + return ( + <div> + <h2>Building</h2> + {inSimulation ? ( + <div className="alert alert-info"> + <span className="fa fa-info-circle mr-2" /> + <strong>Click on individual rooms</strong> to see their stats! + </div> + ) : ( + <NewRoomConstructionContainer /> + )} + </div> + ); +}; + +export default BuildingSidebarComponent; diff --git a/frontend/src/components/app/sidebars/topology/building/NewRoomConstructionComponent.js b/frontend/src/components/app/sidebars/topology/building/NewRoomConstructionComponent.js new file mode 100644 index 00000000..7b049642 --- /dev/null +++ b/frontend/src/components/app/sidebars/topology/building/NewRoomConstructionComponent.js @@ -0,0 +1,31 @@ +import React from "react"; + +const NewRoomConstructionComponent = ({ + onStart, + onFinish, + onCancel, + currentRoomInConstruction +}) => { + if (currentRoomInConstruction === -1) { + return ( + <div className="btn btn-outline-primary btn-block" onClick={onStart}> + <span className="fa fa-plus mr-2" /> + Construct a new room + </div> + ); + } + return ( + <div> + <div className="btn btn-primary btn-block" onClick={onFinish}> + <span className="fa fa-check mr-2" /> + Finalize new room + </div> + <div className="btn btn-default btn-block" onClick={onCancel}> + <span className="fa fa-times mr-2" /> + Cancel construction + </div> + </div> + ); +}; + +export default NewRoomConstructionComponent; diff --git a/frontend/src/components/app/sidebars/topology/machine/BackToRackComponent.js b/frontend/src/components/app/sidebars/topology/machine/BackToRackComponent.js new file mode 100644 index 00000000..7f56aca0 --- /dev/null +++ b/frontend/src/components/app/sidebars/topology/machine/BackToRackComponent.js @@ -0,0 +1,10 @@ +import React from "react"; + +const BackToRackComponent = ({ onClick }) => ( + <div className="btn btn-secondary btn-block" onClick={onClick}> + <span className="fa fa-angle-left mr-2" /> + Back to rack + </div> +); + +export default BackToRackComponent; diff --git a/frontend/src/components/app/sidebars/topology/machine/DeleteMachineComponent.js b/frontend/src/components/app/sidebars/topology/machine/DeleteMachineComponent.js new file mode 100644 index 00000000..d8774bf9 --- /dev/null +++ b/frontend/src/components/app/sidebars/topology/machine/DeleteMachineComponent.js @@ -0,0 +1,10 @@ +import React from "react"; + +const DeleteMachineComponent = ({ onClick }) => ( + <div className="btn btn-outline-danger btn-block" onClick={onClick}> + <span className="fa fa-trash mr-2" /> + Delete this machine + </div> +); + +export default DeleteMachineComponent; diff --git a/frontend/src/components/app/sidebars/topology/machine/MachineNameComponent.js b/frontend/src/components/app/sidebars/topology/machine/MachineNameComponent.js new file mode 100644 index 00000000..0ad8b79c --- /dev/null +++ b/frontend/src/components/app/sidebars/topology/machine/MachineNameComponent.js @@ -0,0 +1,7 @@ +import React from "react"; + +const MachineNameComponent = ({ position }) => ( + <h2>Machine at slot {position}</h2> +); + +export default MachineNameComponent; diff --git a/frontend/src/components/app/sidebars/topology/machine/MachineSidebarComponent.js b/frontend/src/components/app/sidebars/topology/machine/MachineSidebarComponent.js new file mode 100644 index 00000000..5ccaf25c --- /dev/null +++ b/frontend/src/components/app/sidebars/topology/machine/MachineSidebarComponent.js @@ -0,0 +1,27 @@ +import React from "react"; +import LoadBarContainer from "../../../../../containers/app/sidebars/elements/LoadBarContainer"; +import LoadChartContainer from "../../../../../containers/app/sidebars/elements/LoadChartContainer"; +import BackToRackContainer from "../../../../../containers/app/sidebars/topology/machine/BackToRackContainer"; +import DeleteMachineContainer from "../../../../../containers/app/sidebars/topology/machine/DeleteMachineContainer"; +import MachineNameContainer from "../../../../../containers/app/sidebars/topology/machine/MachineNameContainer"; +import UnitTabsContainer from "../../../../../containers/app/sidebars/topology/machine/UnitTabsContainer"; + +const MachineSidebarComponent = ({ inSimulation, machineId }) => { + return ( + <div> + <MachineNameContainer /> + <BackToRackContainer /> + {inSimulation ? ( + <div> + <LoadBarContainer objectType="machine" objectId={machineId} /> + <LoadChartContainer objectType="machine" objectId={machineId} /> + </div> + ) : ( + <DeleteMachineContainer /> + )} + <UnitTabsContainer /> + </div> + ); +}; + +export default MachineSidebarComponent; diff --git a/frontend/src/components/app/sidebars/topology/machine/UnitAddComponent.js b/frontend/src/components/app/sidebars/topology/machine/UnitAddComponent.js new file mode 100644 index 00000000..0c903228 --- /dev/null +++ b/frontend/src/components/app/sidebars/topology/machine/UnitAddComponent.js @@ -0,0 +1,46 @@ +import PropTypes from "prop-types"; +import React from "react"; + +class UnitAddComponent extends React.Component { + static propTypes = { + units: PropTypes.array.isRequired, + onAdd: PropTypes.func.isRequired + }; + + render() { + return ( + <div className="form-inline"> + <div className="form-group w-100"> + <select + className="form-control w-75 mr-1" + ref={unitSelect => (this.unitSelect = unitSelect)} + > + {this.props.units.map(unit => ( + <option value={unit.id} key={unit.id}> + {unit.manufacturer + + " " + + unit.family + + " " + + unit.model + + " " + + unit.generation} + </option> + ))} + </select> + <button + type="submit" + className="btn btn-outline-primary" + onClick={() => + this.props.onAdd(parseInt(this.unitSelect.value, 10)) + } + > + <span className="fa fa-plus mr-2" /> + Add + </button> + </div> + </div> + ); + } +} + +export default UnitAddComponent; diff --git a/frontend/src/components/app/sidebars/topology/machine/UnitComponent.js b/frontend/src/components/app/sidebars/topology/machine/UnitComponent.js new file mode 100644 index 00000000..7c27043d --- /dev/null +++ b/frontend/src/components/app/sidebars/topology/machine/UnitComponent.js @@ -0,0 +1,78 @@ +import React from "react"; +import jQuery from "../../../../../util/jquery"; + +class UnitComponent extends React.Component { + componentDidMount() { + jQuery(".unit-info-popover").popover({ + trigger: "focus" + }); + } + + render() { + let unitInfo; + if (this.props.unitType === "cpu" || this.props.unitType === "gpu") { + unitInfo = + "<strong>Clockrate:</strong> <code>" + + this.props.unit.clockRateMhz + + " MHz</code><br/>" + + "<strong>Num. Cores:</strong> <code>" + + this.props.unit.numberOfCores + + "</code><br/>" + + "<strong>Energy Cons.:</strong> <code>" + + this.props.unit.energyConsumptionW + + " W</code>"; + } else if ( + this.props.unitType === "memory" || + this.props.unitType === "storage" + ) { + unitInfo = + "<strong>Speed:</strong> <code>" + + this.props.unit.speedMbPerS + + " Mb/s</code><br/>" + + "<strong>Size:</strong> <code>" + + this.props.unit.sizeMb + + " MB</code><br/>" + + "<strong>Energy Cons.:</strong> <code> " + + this.props.unit.energyConsumptionW + + " W</code>"; + } + + return ( + <li className="d-flex list-group-item justify-content-between align-items-center"> + <span style={{ maxWidth: "60%" }}> + {this.props.unit.manufacturer + + " " + + this.props.unit.family + + " " + + this.props.unit.model + + " " + + this.props.unit.generation} + </span> + <span> + <span + tabIndex="0" + className="unit-info-popover btn btn-outline-info mr-1 fa fa-info-circle" + role="button" + data-toggle="popover" + data-trigger="focus" + title="Unit information" + data-content={unitInfo} + data-html="true" + /> + {this.props.inSimulation ? ( + undefined + ) : ( + <span + className="btn btn-outline-danger" + onClick={this.props.onDelete} + > + <span className="fa fa-trash" /> + </span> + )} + </span> + </li> + ); + } +} + +export default UnitComponent; diff --git a/frontend/src/components/app/sidebars/topology/machine/UnitListComponent.js b/frontend/src/components/app/sidebars/topology/machine/UnitListComponent.js new file mode 100644 index 00000000..38df806b --- /dev/null +++ b/frontend/src/components/app/sidebars/topology/machine/UnitListComponent.js @@ -0,0 +1,29 @@ +import React from "react"; +import UnitContainer from "../../../../../containers/app/sidebars/topology/machine/UnitContainer"; + +const UnitListComponent = ({ unitType, unitIds, inSimulation }) => ( + <ul className="list-group mt-1"> + {unitIds.length !== 0 ? ( + unitIds.map((unitId, index) => ( + <UnitContainer + unitType={unitType} + unitId={unitId} + index={index} + key={index} + /> + )) + ) : ( + <div className="alert alert-info"> + {inSimulation ? ( + <strong>No units of this type in this machine</strong> + ) : ( + <span> + <strong>No units...</strong> Add some with the menu above! + </span> + )} + </div> + )} + </ul> +); + +export default UnitListComponent; diff --git a/frontend/src/components/app/sidebars/topology/machine/UnitTabsComponent.js b/frontend/src/components/app/sidebars/topology/machine/UnitTabsComponent.js new file mode 100644 index 00000000..0683c796 --- /dev/null +++ b/frontend/src/components/app/sidebars/topology/machine/UnitTabsComponent.js @@ -0,0 +1,65 @@ +import React from "react"; +import UnitAddContainer from "../../../../../containers/app/sidebars/topology/machine/UnitAddContainer"; +import UnitListContainer from "../../../../../containers/app/sidebars/topology/machine/UnitListContainer"; + +const UnitTabsComponent = ({ inSimulation }) => ( + <div> + <ul className="nav nav-tabs mt-2 mb-1" role="tablist"> + <li className="nav-item"> + <a + className="nav-link active" + data-toggle="tab" + href="#cpu-units" + role="tab" + > + CPU + </a> + </li> + <li className="nav-item"> + <a className="nav-link" data-toggle="tab" href="#gpu-units" role="tab"> + GPU + </a> + </li> + <li className="nav-item"> + <a + className="nav-link" + data-toggle="tab" + href="#memory-units" + role="tab" + > + Memory + </a> + </li> + <li className="nav-item"> + <a + className="nav-link" + data-toggle="tab" + href="#storage-units" + role="tab" + > + Storage + </a> + </li> + </ul> + <div className="tab-content"> + <div className="tab-pane active" id="cpu-units" role="tabpanel"> + {inSimulation ? undefined : <UnitAddContainer unitType="cpu" />} + <UnitListContainer unitType="cpu" /> + </div> + <div className="tab-pane" id="gpu-units" role="tabpanel"> + {inSimulation ? undefined : <UnitAddContainer unitType="gpu" />} + <UnitListContainer unitType="gpu" /> + </div> + <div className="tab-pane" id="memory-units" role="tabpanel"> + {inSimulation ? undefined : <UnitAddContainer unitType="memory" />} + <UnitListContainer unitType="memory" /> + </div> + <div className="tab-pane" id="storage-units" role="tabpanel"> + {inSimulation ? undefined : <UnitAddContainer unitType="storage" />} + <UnitListContainer unitType="storage" /> + </div> + </div> + </div> +); + +export default UnitTabsComponent; diff --git a/frontend/src/components/app/sidebars/topology/rack/BackToRoomComponent.js b/frontend/src/components/app/sidebars/topology/rack/BackToRoomComponent.js new file mode 100644 index 00000000..6bcf4088 --- /dev/null +++ b/frontend/src/components/app/sidebars/topology/rack/BackToRoomComponent.js @@ -0,0 +1,10 @@ +import React from "react"; + +const BackToRoomComponent = ({ onClick }) => ( + <div className="btn btn-secondary btn-block mb-2" onClick={onClick}> + <span className="fa fa-angle-left mr-2" /> + Back to room + </div> +); + +export default BackToRoomComponent; diff --git a/frontend/src/components/app/sidebars/topology/rack/DeleteRackComponent.js b/frontend/src/components/app/sidebars/topology/rack/DeleteRackComponent.js new file mode 100644 index 00000000..d8aa7634 --- /dev/null +++ b/frontend/src/components/app/sidebars/topology/rack/DeleteRackComponent.js @@ -0,0 +1,10 @@ +import React from "react"; + +const DeleteRackComponent = ({ onClick }) => ( + <div className="btn btn-outline-danger btn-block" onClick={onClick}> + <span className="fa fa-trash mr-2" /> + Delete this rack + </div> +); + +export default DeleteRackComponent; diff --git a/frontend/src/components/app/sidebars/topology/rack/EmptySlotComponent.js b/frontend/src/components/app/sidebars/topology/rack/EmptySlotComponent.js new file mode 100644 index 00000000..d86f9fee --- /dev/null +++ b/frontend/src/components/app/sidebars/topology/rack/EmptySlotComponent.js @@ -0,0 +1,19 @@ +import React from "react"; + +const EmptySlotComponent = ({ position, onAdd, inSimulation }) => ( + <li className="list-group-item d-flex justify-content-between align-items-center"> + <span className="badge badge-default badge-info mr-1 disabled"> + {position} + </span> + {inSimulation ? ( + <span className="badge badge-default badge-success">Empty Slot</span> + ) : ( + <button className="btn btn-outline-primary" onClick={onAdd}> + <span className="fa fa-plus mr-2" /> + Add machine + </button> + )} + </li> +); + +export default EmptySlotComponent; diff --git a/frontend/src/components/app/sidebars/topology/rack/MachineComponent.js b/frontend/src/components/app/sidebars/topology/rack/MachineComponent.js new file mode 100644 index 00000000..2521f4a2 --- /dev/null +++ b/frontend/src/components/app/sidebars/topology/rack/MachineComponent.js @@ -0,0 +1,78 @@ +import React from "react"; +import Shapes from "../../../../../shapes"; +import { convertLoadToSimulationColor } from "../../../../../util/simulation-load"; + +const UnitIcon = ({ id, type }) => ( + <div> + <img + src={"/img/topology/" + id + "-icon.png"} + alt={"Machine contains " + type + " units"} + className="img-fluid ml-1" + style={{ maxHeight: "35px" }} + /> + </div> +); + +const MachineComponent = ({ + position, + machine, + inSimulation, + machineLoad, + onClick +}) => { + let color = "white"; + if (inSimulation && machineLoad >= 0) { + color = convertLoadToSimulationColor(machineLoad); + } + const hasNoUnits = + machine.cpuIds.length + + machine.gpuIds.length + + machine.memoryIds.length + + machine.storageIds.length === + 0; + + return ( + <li + className="d-flex list-group-item list-group-item-action justify-content-between align-items-center" + onClick={onClick} + style={{ backgroundColor: color }} + > + <span className="badge badge-default badge-info mr-1">{position}</span> + <div className="d-inline-flex"> + {machine.cpuIds.length > 0 ? ( + <UnitIcon id="cpu" type="CPU" /> + ) : ( + undefined + )} + {machine.gpuIds.length > 0 ? ( + <UnitIcon id="gpu" type="GPU" /> + ) : ( + undefined + )} + {machine.memoryIds.length > 0 ? ( + <UnitIcon id="memory" type="memory" /> + ) : ( + undefined + )} + {machine.storageIds.length > 0 ? ( + <UnitIcon id="storage" type="storage" /> + ) : ( + undefined + )} + {hasNoUnits ? ( + <span className="badge badge-default badge-warning"> + Machine with no units + </span> + ) : ( + undefined + )} + </div> + </li> + ); +}; + +MachineComponent.propTypes = { + machine: Shapes.Machine +}; + +export default MachineComponent; diff --git a/frontend/src/components/app/sidebars/topology/rack/MachineListComponent.js b/frontend/src/components/app/sidebars/topology/rack/MachineListComponent.js new file mode 100644 index 00000000..d5521557 --- /dev/null +++ b/frontend/src/components/app/sidebars/topology/rack/MachineListComponent.js @@ -0,0 +1,26 @@ +import React from "react"; +import EmptySlotContainer from "../../../../../containers/app/sidebars/topology/rack/EmptySlotContainer"; +import MachineContainer from "../../../../../containers/app/sidebars/topology/rack/MachineContainer"; +import "./MachineListComponent.css"; + +const MachineListComponent = ({ machineIds }) => { + return ( + <ul className="list-group machine-list"> + {machineIds.map((machineId, index) => { + if (machineId === null) { + return <EmptySlotContainer key={index} position={index + 1} />; + } else { + return ( + <MachineContainer + key={index} + position={index + 1} + machineId={machineId} + /> + ); + } + })} + </ul> + ); +}; + +export default MachineListComponent; diff --git a/frontend/src/components/app/sidebars/topology/rack/MachineListComponent.sass b/frontend/src/components/app/sidebars/topology/rack/MachineListComponent.sass new file mode 100644 index 00000000..bbcfe696 --- /dev/null +++ b/frontend/src/components/app/sidebars/topology/rack/MachineListComponent.sass @@ -0,0 +1,2 @@ +.machine-list li + min-height: 64px diff --git a/frontend/src/components/app/sidebars/topology/rack/RackNameComponent.js b/frontend/src/components/app/sidebars/topology/rack/RackNameComponent.js new file mode 100644 index 00000000..5e095823 --- /dev/null +++ b/frontend/src/components/app/sidebars/topology/rack/RackNameComponent.js @@ -0,0 +1,8 @@ +import React from "react"; +import NameComponent from "../NameComponent"; + +const RackNameComponent = ({ rackName, onEdit }) => ( + <NameComponent name={rackName} onEdit={onEdit} /> +); + +export default RackNameComponent; diff --git a/frontend/src/components/app/sidebars/topology/rack/RackSidebarComponent.js b/frontend/src/components/app/sidebars/topology/rack/RackSidebarComponent.js new file mode 100644 index 00000000..f832b9b9 --- /dev/null +++ b/frontend/src/components/app/sidebars/topology/rack/RackSidebarComponent.js @@ -0,0 +1,34 @@ +import React from "react"; +import LoadBarContainer from "../../../../../containers/app/sidebars/elements/LoadBarContainer"; +import LoadChartContainer from "../../../../../containers/app/sidebars/elements/LoadChartContainer"; +import BackToRoomContainer from "../../../../../containers/app/sidebars/topology/rack/BackToRoomContainer"; +import DeleteRackContainer from "../../../../../containers/app/sidebars/topology/rack/DeleteRackContainer"; +import MachineListContainer from "../../../../../containers/app/sidebars/topology/rack/MachineListContainer"; +import RackNameContainer from "../../../../../containers/app/sidebars/topology/rack/RackNameContainer"; +import "./RackSidebarComponent.css"; + +const RackSidebarComponent = ({ inSimulation, rackId }) => { + return ( + <div className="rack-sidebar-container flex-column"> + <div className="rack-sidebar-header-container"> + <RackNameContainer /> + <BackToRoomContainer /> + {inSimulation ? ( + <div> + <LoadBarContainer objectType="rack" objectId={rackId} /> + <LoadChartContainer objectType="rack" objectId={rackId} /> + </div> + ) : ( + <div> + <DeleteRackContainer /> + </div> + )} + </div> + <div className="machine-list-container mt-2"> + <MachineListContainer /> + </div> + </div> + ); +}; + +export default RackSidebarComponent; diff --git a/frontend/src/components/app/sidebars/topology/rack/RackSidebarComponent.sass b/frontend/src/components/app/sidebars/topology/rack/RackSidebarComponent.sass new file mode 100644 index 00000000..822804bc --- /dev/null +++ b/frontend/src/components/app/sidebars/topology/rack/RackSidebarComponent.sass @@ -0,0 +1,11 @@ +.rack-sidebar-container + display: flex + height: 100% + max-height: 100% + +.rack-sidebar-header-container + flex: 0 + +.machine-list-container + flex: 1 + overflow-y: scroll diff --git a/frontend/src/components/app/sidebars/topology/room/BackToBuildingComponent.js b/frontend/src/components/app/sidebars/topology/room/BackToBuildingComponent.js new file mode 100644 index 00000000..0409dbdd --- /dev/null +++ b/frontend/src/components/app/sidebars/topology/room/BackToBuildingComponent.js @@ -0,0 +1,10 @@ +import React from "react"; + +const BackToBuildingComponent = ({ onClick }) => ( + <div className="btn btn-secondary btn-block mb-2" onClick={onClick}> + <span className="fa fa-angle-left mr-2" /> + Back to building + </div> +); + +export default BackToBuildingComponent; diff --git a/frontend/src/components/app/sidebars/topology/room/DeleteRoomComponent.js b/frontend/src/components/app/sidebars/topology/room/DeleteRoomComponent.js new file mode 100644 index 00000000..3e3b3b36 --- /dev/null +++ b/frontend/src/components/app/sidebars/topology/room/DeleteRoomComponent.js @@ -0,0 +1,10 @@ +import React from "react"; + +const DeleteRoomComponent = ({ onClick }) => ( + <div className="btn btn-outline-danger btn-block" onClick={onClick}> + <span className="fa fa-trash mr-2" /> + Delete this room + </div> +); + +export default DeleteRoomComponent; diff --git a/frontend/src/components/app/sidebars/topology/room/EditRoomComponent.js b/frontend/src/components/app/sidebars/topology/room/EditRoomComponent.js new file mode 100644 index 00000000..c3b9f0ad --- /dev/null +++ b/frontend/src/components/app/sidebars/topology/room/EditRoomComponent.js @@ -0,0 +1,27 @@ +import classNames from "classnames"; +import React from "react"; + +const EditRoomComponent = ({ + onEdit, + onFinish, + isEditing, + isInRackConstructionMode +}) => + isEditing ? ( + <div className="btn btn-info btn-block" onClick={onFinish}> + <span className="fa fa-check mr-2" /> + Finish editing room + </div> + ) : ( + <div + className={classNames("btn btn-outline-info btn-block", { + disabled: isInRackConstructionMode + })} + onClick={() => (isInRackConstructionMode ? undefined : onEdit())} + > + <span className="fa fa-pencil mr-2" /> + Edit the tiles of this room + </div> + ); + +export default EditRoomComponent; diff --git a/frontend/src/components/app/sidebars/topology/room/RackConstructionComponent.js b/frontend/src/components/app/sidebars/topology/room/RackConstructionComponent.js new file mode 100644 index 00000000..06b8a2aa --- /dev/null +++ b/frontend/src/components/app/sidebars/topology/room/RackConstructionComponent.js @@ -0,0 +1,32 @@ +import classNames from "classnames"; +import React from "react"; + +const RackConstructionComponent = ({ + onStart, + onStop, + inRackConstructionMode, + isEditingRoom +}) => { + if (inRackConstructionMode) { + return ( + <div className="btn btn-primary btn-block" onClick={onStop}> + <span className="fa fa-times mr-2" /> + Stop rack construction + </div> + ); + } + + return ( + <div + className={classNames("btn btn-outline-primary btn-block", { + disabled: isEditingRoom + })} + onClick={() => (isEditingRoom ? undefined : onStart())} + > + <span className="fa fa-plus mr-2" /> + Start rack construction + </div> + ); +}; + +export default RackConstructionComponent; diff --git a/frontend/src/components/app/sidebars/topology/room/RoomNameComponent.js b/frontend/src/components/app/sidebars/topology/room/RoomNameComponent.js new file mode 100644 index 00000000..11b88edd --- /dev/null +++ b/frontend/src/components/app/sidebars/topology/room/RoomNameComponent.js @@ -0,0 +1,8 @@ +import React from "react"; +import NameComponent from "../NameComponent"; + +const RoomNameComponent = ({ roomName, onEdit }) => ( + <NameComponent name={roomName} onEdit={onEdit} /> +); + +export default RoomNameComponent; diff --git a/frontend/src/components/app/sidebars/topology/room/RoomSidebarComponent.js b/frontend/src/components/app/sidebars/topology/room/RoomSidebarComponent.js new file mode 100644 index 00000000..275f9624 --- /dev/null +++ b/frontend/src/components/app/sidebars/topology/room/RoomSidebarComponent.js @@ -0,0 +1,38 @@ +import React from "react"; +import LoadBarContainer from "../../../../../containers/app/sidebars/elements/LoadBarContainer"; +import LoadChartContainer from "../../../../../containers/app/sidebars/elements/LoadChartContainer"; +import BackToBuildingContainer from "../../../../../containers/app/sidebars/topology/room/BackToBuildingContainer"; +import DeleteRoomContainer from "../../../../../containers/app/sidebars/topology/room/DeleteRoomContainer"; +import EditRoomContainer from "../../../../../containers/app/sidebars/topology/room/EditRoomContainer"; +import RackConstructionContainer from "../../../../../containers/app/sidebars/topology/room/RackConstructionContainer"; +import RoomNameContainer from "../../../../../containers/app/sidebars/topology/room/RoomNameContainer"; +import RoomTypeContainer from "../../../../../containers/app/sidebars/topology/room/RoomTypeContainer"; + +const RoomSidebarComponent = ({ roomId, roomType, inSimulation }) => { + let allowedObjects; + if (!inSimulation && roomType === "SERVER") { + allowedObjects = <RackConstructionContainer />; + } + + return ( + <div> + <RoomNameContainer /> + <RoomTypeContainer /> + <BackToBuildingContainer /> + {inSimulation ? ( + <div> + <LoadBarContainer objectType="room" objectId={roomId} /> + <LoadChartContainer objectType="room" objectId={roomId} /> + </div> + ) : ( + <div> + {allowedObjects} + <EditRoomContainer /> + <DeleteRoomContainer /> + </div> + )} + </div> + ); +}; + +export default RoomSidebarComponent; diff --git a/frontend/src/components/app/sidebars/topology/room/RoomTypeComponent.js b/frontend/src/components/app/sidebars/topology/room/RoomTypeComponent.js new file mode 100644 index 00000000..46d91c2c --- /dev/null +++ b/frontend/src/components/app/sidebars/topology/room/RoomTypeComponent.js @@ -0,0 +1,8 @@ +import React from "react"; +import { ROOM_TYPE_TO_NAME_MAP } from "../../../../../util/room-types"; + +const RoomTypeComponent = ({ roomType }) => ( + <p className="lead">{ROOM_TYPE_TO_NAME_MAP[roomType]}</p> +); + +export default RoomTypeComponent; diff --git a/frontend/src/components/app/timeline/PlayButtonComponent.js b/frontend/src/components/app/timeline/PlayButtonComponent.js new file mode 100644 index 00000000..1a9b0ced --- /dev/null +++ b/frontend/src/components/app/timeline/PlayButtonComponent.js @@ -0,0 +1,30 @@ +import React from "react"; + +const PlayButtonComponent = ({ + isPlaying, + currentTick, + lastSimulatedTick, + onPlay, + onPause +}) => ( + <div + className="play-btn" + onClick={() => { + if (isPlaying) { + onPause(); + } else { + if (currentTick !== lastSimulatedTick) { + onPlay(); + } + } + }} + > + {isPlaying ? ( + <span className="fa fa-pause" /> + ) : ( + <span className="fa fa-play" /> + )} + </div> +); + +export default PlayButtonComponent; diff --git a/frontend/src/components/app/timeline/Timeline.sass b/frontend/src/components/app/timeline/Timeline.sass new file mode 100644 index 00000000..4c99a218 --- /dev/null +++ b/frontend/src/components/app/timeline/Timeline.sass @@ -0,0 +1,116 @@ +@import ../../../style-globals/_variables.sass +@import ../../../style-globals/_mixins.sass + +$container-size: 500px +$play-btn-size: 40px +$border-width: 1px +$timeline-border: $border-width solid $gray-semi-dark + +.timeline-bar + display: block + position: absolute + left: 0 + bottom: 20px + width: 100% + text-align: center + z-index: 2000 + + pointer-events: none + +.timeline-container + display: inline-block + margin: 0 auto + text-align: left + + width: $container-size + +.timeline-labels + display: block + height: 25px + line-height: 25px + + div + display: inline-block + + .start-time-label + margin-left: $play-btn-size - $border-width + padding-left: 4px + + .end-time-label + padding-right: 4px + float: right + +.timeline-controls + display: flex + border: $timeline-border + overflow: hidden + + pointer-events: all + + +border-radius($standard-border-radius) + + .play-btn + width: $play-btn-size + height: $play-btn-size + $border-width + line-height: $play-btn-size + $border-width + text-align: center + float: left + margin-top: -$border-width + + font-size: 16pt + background: #333 + color: #eee + + +transition(background, $transition-length) + +user-select + +clickable + + .play-btn:hover + background: #656565 + + .play-btn:active + background: #000 + + .timeline + position: relative + flex: 1 + height: $play-btn-size + line-height: $play-btn-size + float: right + + background: $blue-light + + z-index: 500 + + div + +transition(all, $transition-length) + + .time-marker + position: absolute + top: 0 + left: 0 + + width: 6px + height: 100% + + background: $blue-very-dark + + +border-radius(2px) + + z-index: 503 + + pointer-events: none + + .section-marker + position: absolute + top: 0 + left: 0 + + width: 3px + height: 100% + + background: #222222 + + z-index: 504 + + pointer-events: none diff --git a/frontend/src/components/app/timeline/TimelineComponent.js b/frontend/src/components/app/timeline/TimelineComponent.js new file mode 100644 index 00000000..0f88b8f4 --- /dev/null +++ b/frontend/src/components/app/timeline/TimelineComponent.js @@ -0,0 +1,37 @@ +import React from "react"; +import TimelineControlsContainer from "../../../containers/app/timeline/TimelineControlsContainer"; +import TimelineLabelsContainer from "../../../containers/app/timeline/TimelineLabelsContainer"; +import "./Timeline.css"; + +class TimelineComponent extends React.Component { + componentDidMount() { + this.interval = setInterval(() => { + if (!this.props.isPlaying) { + return; + } + + if (this.props.currentTick < this.props.lastSimulatedTick) { + this.props.incrementTick(); + } else { + this.props.pauseSimulation(); + } + }, 1000); + } + + componentWillUnmount() { + clearInterval(this.interval); + } + + render() { + return ( + <div className="timeline-bar"> + <div className="timeline-container"> + <TimelineLabelsContainer /> + <TimelineControlsContainer /> + </div> + </div> + ); + } +} + +export default TimelineComponent; diff --git a/frontend/src/components/app/timeline/TimelineControlsComponent.js b/frontend/src/components/app/timeline/TimelineControlsComponent.js new file mode 100644 index 00000000..f3d55154 --- /dev/null +++ b/frontend/src/components/app/timeline/TimelineControlsComponent.js @@ -0,0 +1,49 @@ +import React from "react"; +import PlayButtonContainer from "../../../containers/app/timeline/PlayButtonContainer"; +import { convertTickToPercentage } from "../../../util/timeline"; + +class TimelineControlsComponent extends React.Component { + onTimelineClick(e) { + const percentage = e.nativeEvent.offsetX / this.timeline.clientWidth; + const tick = Math.floor(percentage * (this.props.lastSimulatedTick + 1)); + this.props.goToTick(tick); + } + + render() { + return ( + <div className="timeline-controls"> + <PlayButtonContainer /> + <div + className="timeline" + ref={timeline => (this.timeline = timeline)} + onClick={this.onTimelineClick.bind(this)} + > + <div + className="time-marker" + style={{ + left: convertTickToPercentage( + this.props.currentTick, + this.props.lastSimulatedTick + ) + }} + /> + {this.props.sectionTicks.map(sectionTick => ( + <div + key={sectionTick} + className="section-marker" + style={{ + left: convertTickToPercentage( + sectionTick, + this.props.lastSimulatedTick + ) + }} + title="Topology changes at this tick" + /> + ))} + </div> + </div> + ); + } +} + +export default TimelineControlsComponent; diff --git a/frontend/src/components/app/timeline/TimelineLabelsComponent.js b/frontend/src/components/app/timeline/TimelineLabelsComponent.js new file mode 100644 index 00000000..6943a86f --- /dev/null +++ b/frontend/src/components/app/timeline/TimelineLabelsComponent.js @@ -0,0 +1,15 @@ +import React from "react"; +import { convertSecondsToFormattedTime } from "../../../util/date-time"; + +const TimelineLabelsComponent = ({ currentTick, lastSimulatedTick }) => ( + <div className="timeline-labels"> + <div className="start-time-label"> + {convertSecondsToFormattedTime(currentTick)} + </div> + <div className="end-time-label"> + {convertSecondsToFormattedTime(lastSimulatedTick)} + </div> + </div> +); + +export default TimelineLabelsComponent; diff --git a/frontend/src/components/experiments/ExperimentListComponent.js b/frontend/src/components/experiments/ExperimentListComponent.js new file mode 100644 index 00000000..2f7106e5 --- /dev/null +++ b/frontend/src/components/experiments/ExperimentListComponent.js @@ -0,0 +1,59 @@ +import PropTypes from "prop-types"; +import React from "react"; +import ExperimentRowContainer from "../../containers/experiments/ExperimentRowContainer"; + +const ExperimentListComponent = ({ experimentIds, loading }) => { + let alert; + + if (loading) { + alert = ( + <div className="alert alert-success"> + <span className="fa fa-refresh fa-spin mr-2" /> + <strong>Loading Experiments...</strong> + </div> + ); + } else if (experimentIds.length === 0 && !loading) { + alert = ( + <div className="alert alert-info"> + <span className="fa fa-question-circle mr-2" /> + <strong>No experiments here yet...</strong> Add some with the button + below! + </div> + ); + } + + return ( + <div className="vertically-expanding-container"> + {alert ? ( + alert + ) : ( + <table className="table table-striped"> + <thead> + <tr> + <th>Name</th> + <th>Path</th> + <th>Trace</th> + <th>Scheduler</th> + <th /> + </tr> + </thead> + <tbody> + {experimentIds.map(experimentId => ( + <ExperimentRowContainer + experimentId={experimentId} + key={experimentId} + /> + ))} + </tbody> + </table> + )} + </div> + ); +}; + +ExperimentListComponent.propTypes = { + experimentIds: PropTypes.arrayOf(PropTypes.number).isRequired, + loading: PropTypes.bool +}; + +export default ExperimentListComponent; diff --git a/frontend/src/components/experiments/ExperimentRowComponent.js b/frontend/src/components/experiments/ExperimentRowComponent.js new file mode 100644 index 00000000..e71c6a00 --- /dev/null +++ b/frontend/src/components/experiments/ExperimentRowComponent.js @@ -0,0 +1,40 @@ +import PropTypes from "prop-types"; +import React from "react"; +import { Link } from "react-router-dom"; +import Shapes from "../../shapes/index"; + +const ExperimentRowComponent = ({ experiment, simulationId, onDelete }) => ( + <tr> + <td className="pt-3">{experiment.name}</td> + <td className="pt-3"> + {experiment.path.name + ? experiment.path.name + : "Path " + experiment.path.id} + </td> + <td className="pt-3">{experiment.trace.name}</td> + <td className="pt-3">{experiment.scheduler.name}</td> + <td className="text-right"> + <Link + to={"/simulations/" + simulationId + "/experiments/" + experiment.id} + className="btn btn-outline-primary btn-sm mr-2" + title="Open this experiment" + > + <span className="fa fa-play" /> + </Link> + <div + className="btn btn-outline-danger btn-sm" + title="Delete this experiment" + onClick={() => onDelete(experiment.id)} + > + <span className="fa fa-trash" /> + </div> + </td> + </tr> +); + +ExperimentRowComponent.propTypes = { + experiment: Shapes.Experiment.isRequired, + simulationId: PropTypes.number.isRequired +}; + +export default ExperimentRowComponent; diff --git a/frontend/src/components/experiments/NewExperimentButtonComponent.js b/frontend/src/components/experiments/NewExperimentButtonComponent.js new file mode 100644 index 00000000..651172e3 --- /dev/null +++ b/frontend/src/components/experiments/NewExperimentButtonComponent.js @@ -0,0 +1,17 @@ +import PropTypes from "prop-types"; +import React from "react"; + +const NewExperimentButtonComponent = ({ onClick }) => ( + <div className="bottom-btn-container"> + <div className="btn btn-primary float-right" onClick={onClick}> + <span className="fa fa-plus mr-2" /> + New Experiment + </div> + </div> +); + +NewExperimentButtonComponent.propTypes = { + onClick: PropTypes.func.isRequired +}; + +export default NewExperimentButtonComponent; diff --git a/frontend/src/components/home/ContactSection.js b/frontend/src/components/home/ContactSection.js new file mode 100644 index 00000000..f6c9c5d8 --- /dev/null +++ b/frontend/src/components/home/ContactSection.js @@ -0,0 +1,64 @@ +import React from "react"; +import FontAwesome from "react-fontawesome"; +import "./ContactSection.css"; +import ContentSection from "./ContentSection"; + +const ContactSection = () => ( + <ContentSection name="contact" title="Contact"> + <div className="row justify-content-center"> + <div className="col-4"> + <a href="https://github.com/atlarge-research/opendc"> + <FontAwesome name="github" size="3x" className="mb-2" /> + <div className="w-100" /> + atlarge-research/opendc + </a> + </div> + <div className="col-4"> + <a href="mailto:opendc@atlarge-research.com"> + <FontAwesome name="envelope" size="3x" className="mb-2" /> + <div className="w-100" /> + opendc@atlarge-research.com + </a> + </div> + </div> + <div className="row"> + <div className="col text-center"> + <img + src="img/tudelft-icon.png" + className="img-fluid tudelft-icon" + alt="TU Delft" + /> + </div> + </div> + <div className="row"> + <div className="col text-center"> + A project by the + <a + href="http://atlarge.science" + target="_blank" + rel="noopener noreferrer" + > + <strong>@Large Research Group</strong> + </a>. + </div> + </div> + <div className="row"> + <div className="col text-center disclaimer mt-5 small"> + <FontAwesome name="exclamation-triangle" size="2x" className="mr-2" /> + <br /> + OpenDC is an experimental tool. Your data may get lost, overwritten, or + otherwise become unavailable. + <br /> + The OpenDC authors should in no way be liable in the event this happens + (see our{" "} + <strong> + <a href="https://github.com/atlarge-research/opendc/blob/master/LICENSE.txt"> + license + </a> + </strong>). Sorry for the inconvenience. + </div> + </div> + </ContentSection> +); + +export default ContactSection; diff --git a/frontend/src/components/home/ContactSection.sass b/frontend/src/components/home/ContactSection.sass new file mode 100644 index 00000000..2cde7391 --- /dev/null +++ b/frontend/src/components/home/ContactSection.sass @@ -0,0 +1,15 @@ +.contact-section + background-color: #444 + color: #ddd + + a + color: #ddd + + a:hover + color: #fff + + .tudelft-icon + height: 100px + + .disclaimer + color: #cccccc diff --git a/frontend/src/components/home/ContentSection.js b/frontend/src/components/home/ContentSection.js new file mode 100644 index 00000000..2e24ee10 --- /dev/null +++ b/frontend/src/components/home/ContentSection.js @@ -0,0 +1,19 @@ +import classNames from "classnames"; +import PropTypes from "prop-types"; +import React from "react"; +import "./ContentSection.css"; + +const ContentSection = ({ name, title, children }) => ( + <div id={name} className={classNames(name + "-section", "content-section")}> + <div className="container"> + <h1>{title}</h1> + {children} + </div> + </div> +); + +ContentSection.propTypes = { + name: PropTypes.string.isRequired +}; + +export default ContentSection; diff --git a/frontend/src/components/home/ContentSection.sass b/frontend/src/components/home/ContentSection.sass new file mode 100644 index 00000000..67541179 --- /dev/null +++ b/frontend/src/components/home/ContentSection.sass @@ -0,0 +1,9 @@ +@import ../../style-globals/_variables.sass + +.content-section + padding-top: 50px + padding-bottom: 100px + text-align: center + + h1 + margin-bottom: 30px diff --git a/frontend/src/components/home/IntroSection.js b/frontend/src/components/home/IntroSection.js new file mode 100644 index 00000000..59f5face --- /dev/null +++ b/frontend/src/components/home/IntroSection.js @@ -0,0 +1,40 @@ +import React from "react"; + +const IntroSection = () => ( + <section id="intro" className="intro-section"> + <div className="container pt-5 pb-3"> + <div className="row justify-content-center"> + <div className="col-xl-4 col-lg-4 col-md-4 col-sm-8 col-8"> + <h4>The datacenter (DC) industry...</h4> + <ul> + <li>Is worth over $15 bn, and growing</li> + <li>Has many hard-to-grasp concepts</li> + <li>Needs to become accessible to many</li> + </ul> + </div> + <div className="col-xl-4 col-lg-4 col-md-4 col-sm-8 col-8"> + <img + src="img/datacenter-drawing.png" + className="col-12 img-fluid" + alt="Schematic top-down view of a datacenter" + /> + <p className="col-12 figure-caption text-center"> + <a href="http://www.dolphinhosts.co.uk/wp-content/uploads/2013/07/data-centers.gif"> + Image source + </a> + </p> + </div> + <div className="col-xl-4 col-lg-4 col-md-4 col-sm-8 col-8"> + <h4>OpenDC provides...</h4> + <ul> + <li>Collaborative online DC modeling</li> + <li>Diverse and effective DC simulation</li> + <li>Exploratory DC performance feedback</li> + </ul> + </div> + </div> + </div> + </section> +); + +export default IntroSection; diff --git a/frontend/src/components/home/JumbotronHeader.js b/frontend/src/components/home/JumbotronHeader.js new file mode 100644 index 00000000..8a5312b3 --- /dev/null +++ b/frontend/src/components/home/JumbotronHeader.js @@ -0,0 +1,20 @@ +import React from "react"; +import "./JumbotronHeader.css"; + +const JumbotronHeader = () => ( + <section className="jumbotron-header"> + <div className="container"> + <div className="jumbotron text-center"> + <h1> + Open<span className="dc">DC</span> + </h1> + <p className="lead"> + Collaborative Datacenter Simulation and Exploration for Everybody + </p> + <img src="img/logo.png" className="img-responsive mt-3" alt="OpenDC" /> + </div> + </div> + </section> +); + +export default JumbotronHeader; diff --git a/frontend/src/components/home/JumbotronHeader.sass b/frontend/src/components/home/JumbotronHeader.sass new file mode 100644 index 00000000..b88b79f7 --- /dev/null +++ b/frontend/src/components/home/JumbotronHeader.sass @@ -0,0 +1,24 @@ +.jumbotron-header + background: #00A6D6 + +.jumbotron + background-color: inherit + margin-bottom: 0 + + padding-top: 120px + padding-bottom: 120px + + img + max-width: 110px + + h1 + color: #fff + font-size: 4.5em + + .dc + color: #fff + font-weight: bold + + .lead + color: #fff + font-size: 1.4em diff --git a/frontend/src/components/home/ModelingSection.js b/frontend/src/components/home/ModelingSection.js new file mode 100644 index 00000000..17834b0b --- /dev/null +++ b/frontend/src/components/home/ModelingSection.js @@ -0,0 +1,24 @@ +import React from "react"; +import ScreenshotSection from "./ScreenshotSection"; + +const ModelingSection = () => ( + <ScreenshotSection + name="modeling" + title="Datacenter Modeling" + imageUrl="https://github.com/atlarge-research/opendc/raw/master/images/opendc-frontend-construction.PNG" + caption="Building a datacenter in OpenDC" + imageIsRight={true} + > + <h3>Collaboratively...</h3> + <ul> + <li>Model DC layout, and room locations and types</li> + <li>Place racks in rooms and nodes in racks</li> + <li> + Add real-world CPU, GPU, memory, storage and network units to each node + </li> + <li>Select from diverse scheduling policies</li> + </ul> + </ScreenshotSection> +); + +export default ModelingSection; diff --git a/frontend/src/components/home/ScreenshotSection.js b/frontend/src/components/home/ScreenshotSection.js new file mode 100644 index 00000000..42b8ac77 --- /dev/null +++ b/frontend/src/components/home/ScreenshotSection.js @@ -0,0 +1,32 @@ +import classNames from "classnames"; +import React from "react"; +import ContentSection from "./ContentSection"; +import "./ScreenshotSection.css"; + +const ScreenshotSection = ({ + name, + title, + imageUrl, + caption, + imageIsRight, + children +}) => ( + <ContentSection name={name} title={title}> + <div className="row"> + <div + className={classNames( + "col-xl-5 col-lg-5 col-md-5 col-sm-12 col-12 text-left", + { "order-1": !imageIsRight } + )} + > + {children} + </div> + <div className="col-xl-7 col-lg-7 col-md-7 col-sm-12 col-12"> + <img src={imageUrl} className="col-12 screenshot" alt={caption} /> + <div className="row text-muted justify-content-center">{caption}</div> + </div> + </div> + </ContentSection> +); + +export default ScreenshotSection; diff --git a/frontend/src/components/home/ScreenshotSection.sass b/frontend/src/components/home/ScreenshotSection.sass new file mode 100644 index 00000000..a349ad48 --- /dev/null +++ b/frontend/src/components/home/ScreenshotSection.sass @@ -0,0 +1,5 @@ +.screenshot + outline: 2px black solid + padding-left: 0 + padding-right: 0 + margin-bottom: 5px diff --git a/frontend/src/components/home/SimulationSection.js b/frontend/src/components/home/SimulationSection.js new file mode 100644 index 00000000..3961e549 --- /dev/null +++ b/frontend/src/components/home/SimulationSection.js @@ -0,0 +1,25 @@ +import React from "react"; +import ScreenshotSection from "./ScreenshotSection"; + +const ModelingSection = () => ( + <ScreenshotSection + name="simulation" + title="Datacenter Simulation" + imageUrl="https://github.com/atlarge-research/opendc/raw/master/images/opendc-frontend-simulation-zoom.PNG" + caption="Running an experiment in OpenDC" + imageIsRight={false} + > + <h3>Working with OpenDC:</h3> + <ul> + <li>Seamlessly switch between construction and simulation modes</li> + <li> + Choose one of several predefined workloads (Big Data, Bag of Tasks, + Hadoop, etc.) + </li> + <li>Play, pause, and skip around the informative simulation timeline</li> + <li>Visualize and demo live</li> + </ul> + </ScreenshotSection> +); + +export default ModelingSection; diff --git a/frontend/src/components/home/StakeholderSection.js b/frontend/src/components/home/StakeholderSection.js new file mode 100644 index 00000000..6d25fd86 --- /dev/null +++ b/frontend/src/components/home/StakeholderSection.js @@ -0,0 +1,42 @@ +import React from "react"; +import ContentSection from "./ContentSection"; + +const Stakeholder = ({ name, title, subtitle }) => ( + <div className="col-xl-4 col-lg-4 col-md-4 col-sm-6 col-6"> + <img + src={"img/stakeholders/" + name + ".png"} + className="col-xl-3 col-lg-4 col-md-4 col-sm-4 col-4 img-fluid" + alt={title} + /> + <div className="text-center mt-2"> + <h4>{title}</h4> + <p>{subtitle}</p> + </div> + </div> +); + +const StakeholderSection = () => ( + <ContentSection name="stakeholders" title="Stakeholders"> + <div className="row justify-content-center"> + <Stakeholder + name="Manager" + title="Managers" + subtitle="Seeing is deciding" + /> + <Stakeholder name="Sales" title="Sales" subtitle="Demo concepts" /> + <Stakeholder name="Developer" title="DevOps" subtitle="Develop & tune" /> + <Stakeholder + name="Researcher" + title="Researchers" + subtitle="Understand & design" + /> + <Stakeholder + name="Student" + title="Students" + subtitle="Grasp complex concepts" + /> + </div> + </ContentSection> +); + +export default StakeholderSection; diff --git a/frontend/src/components/home/TeamSection.js b/frontend/src/components/home/TeamSection.js new file mode 100644 index 00000000..b86655b4 --- /dev/null +++ b/frontend/src/components/home/TeamSection.js @@ -0,0 +1,56 @@ +import React from "react"; +import ContentSection from "./ContentSection"; + +const TeamMember = ({ photoId, name, description }) => ( + <div className="col-xl-3 col-lg-3 col-md-5 col-sm-6 col-12 justify-content-center"> + <img + src={"img/portraits/" + photoId + ".png"} + className="col-xl-10 col-lg-10 col-md-10 col-sm-8 col-5 mb-2 mt-2" + alt={name} + /> + <div className="col-12"> + <h4>{name}</h4> + <div className="team-member-description">{description}</div> + </div> + </div> +); + +const TeamSection = () => ( + <ContentSection name="team" title="Core Team"> + <div className="row justify-content-center"> + <TeamMember + photoId="aiosup" + name="Prof. dr. ir. Alexandru Iosup" + description="Project Lead" + /> + <TeamMember + photoId="gandreadis" + name="Georgios Andreadis" + description="Technology Lead and Software Engineer responsible for the frontend web application" + /> + <TeamMember + photoId="fmastenbroek" + name="Fabian Mastenbroek" + description="Software Engineer responsible for the datacenter simulator" + /> + <TeamMember + photoId="loverweel" + name="Leon Overweel" + description="Software Engineer responsible for the web server, database, and API specification" + /> + </div> + <div className="text-center lead mt-3"> + See{" "} + <a + target="_blank" + href="http://atlarge.science/opendc#team" + rel="noopener noreferrer" + > + atlarge.science/opendc + </a>{" "} + for the full team! + </div> + </ContentSection> +); + +export default TeamSection; diff --git a/frontend/src/components/home/TechnologiesSection.js b/frontend/src/components/home/TechnologiesSection.js new file mode 100644 index 00000000..fdcfc522 --- /dev/null +++ b/frontend/src/components/home/TechnologiesSection.js @@ -0,0 +1,42 @@ +import React from "react"; +import FontAwesome from "react-fontawesome"; +import ContentSection from "./ContentSection"; + +const TechnologiesSection = () => ( + <ContentSection name="technologies" title="Technologies"> + <ul className="list-group text-left"> + <li className="d-flex list-group-item justify-content-between align-items-center list-group-item-primary"> + <span style={{ minWidth: 100 }}> + <FontAwesome name="window-maximize" className="mr-2" /> + <strong className="">Browser</strong> + </span> + <span className="text-right">JavaScript, React, Redux, Konva</span> + </li> + <li className="d-flex list-group-item justify-content-between align-items-center list-group-item-warning"> + <span style={{ minWidth: 100 }}> + <FontAwesome name="television" className="mr-2" /> + <strong>Server</strong> + </span> + <span className="text-right"> + Python, Flask, FlaskSocketIO, OpenAPI + </span> + </li> + <li className="d-flex list-group-item justify-content-between align-items-center list-group-item-success"> + <span style={{ minWidth: 100 }}> + <FontAwesome name="database" className="mr-2" /> + <strong>Database</strong> + </span> + <span className="text-right">MariaDB</span> + </li> + <li className="d-flex list-group-item justify-content-between align-items-center list-group-item-danger"> + <span style={{ minWidth: 100 }}> + <FontAwesome name="cogs" className="mr-2" /> + <strong>Simulator</strong> + </span> + <span className="text-right">Kotlin</span> + </li> + </ul> + </ContentSection> +); + +export default TechnologiesSection; diff --git a/frontend/src/components/modals/ConfirmationModal.js b/frontend/src/components/modals/ConfirmationModal.js new file mode 100644 index 00000000..abdce5ac --- /dev/null +++ b/frontend/src/components/modals/ConfirmationModal.js @@ -0,0 +1,37 @@ +import PropTypes from "prop-types"; +import React from "react"; +import Modal from "./Modal"; + +class ConfirmationModal extends React.Component { + static propTypes = { + title: PropTypes.string.isRequired, + message: PropTypes.string.isRequired, + show: PropTypes.bool.isRequired, + callback: PropTypes.func.isRequired + }; + + onConfirm() { + this.props.callback(true); + } + + onCancel() { + this.props.callback(false); + } + + render() { + return ( + <Modal + title={this.props.title} + show={this.props.show} + onSubmit={this.onConfirm.bind(this)} + onCancel={this.onCancel.bind(this)} + submitButtonType="danger" + submitButtonText="Confirm" + > + {this.props.message} + </Modal> + ); + } +} + +export default ConfirmationModal; diff --git a/frontend/src/components/modals/Modal.js b/frontend/src/components/modals/Modal.js new file mode 100644 index 00000000..19337db8 --- /dev/null +++ b/frontend/src/components/modals/Modal.js @@ -0,0 +1,132 @@ +import classNames from "classnames"; +import PropTypes from "prop-types"; +import React from "react"; +import jQuery from "../../util/jquery"; + +class Modal extends React.Component { + static propTypes = { + title: PropTypes.string.isRequired, + show: PropTypes.bool.isRequired, + onSubmit: PropTypes.func.isRequired, + onCancel: PropTypes.func.isRequired, + submitButtonType: PropTypes.string, + submitButtonText: PropTypes.string + }; + static defaultProps = { + submitButtonType: "primary", + submitButtonText: "Save" + }; + static idCounter = 0; + + // Local, up-to-date copy of modal visibility for time between close event and a props update (to prevent duplicate + // 'close' triggers) + visible = false; + + constructor(props) { + super(props); + this.id = "modal-" + Modal.idCounter++; + } + + componentDidMount() { + this.visible = this.props.show; + this.openOrCloseModal(); + + // Trigger auto-focus + jQuery("#" + this.id) + .on("shown.bs.modal", function() { + jQuery(this) + .find("input") + .first() + .focus(); + }) + .on("hide.bs.modal", () => { + if (this.visible) { + this.props.onCancel(); + } + }) + .on("keydown", e => { + e.stopPropagation(); + }); + } + + componentDidUpdate() { + this.visible = this.props.show; + this.openOrCloseModal(); + } + + onSubmit() { + if (this.visible) { + this.props.onSubmit(); + this.visible = false; + this.closeModal(); + } + } + + onCancel() { + if (this.visible) { + this.props.onCancel(); + this.visible = false; + this.closeModal(); + } + } + + openModal() { + jQuery("#" + this.id).modal("show"); + } + + closeModal() { + jQuery("#" + this.id).modal("hide"); + } + + openOrCloseModal() { + if (this.visible) { + this.openModal(); + } else { + this.closeModal(); + } + } + + render() { + return ( + <div className="modal fade" id={this.id} role="dialog"> + <div className="modal-dialog" role="document"> + <div className="modal-content"> + <div className="modal-header"> + <h5 className="modal-title">{this.props.title}</h5> + <button + type="button" + className="close" + onClick={this.onCancel.bind(this)} + aria-label="Close" + > + <span>×</span> + </button> + </div> + <div className="modal-body">{this.props.children}</div> + <div className="modal-footer"> + <button + type="button" + className="btn btn-secondary" + onClick={this.onCancel.bind(this)} + > + Close + </button> + <button + type="button" + className={classNames( + "btn", + "btn-" + this.props.submitButtonType + )} + onClick={this.onSubmit.bind(this)} + > + {this.props.submitButtonText} + </button> + </div> + </div> + </div> + </div> + ); + } +} + +export default Modal; diff --git a/frontend/src/components/modals/TextInputModal.js b/frontend/src/components/modals/TextInputModal.js new file mode 100644 index 00000000..cc16f8e1 --- /dev/null +++ b/frontend/src/components/modals/TextInputModal.js @@ -0,0 +1,58 @@ +import PropTypes from "prop-types"; +import React from "react"; +import Modal from "./Modal"; + +class TextInputModal extends React.Component { + static propTypes = { + title: PropTypes.string.isRequired, + label: PropTypes.string.isRequired, + show: PropTypes.bool.isRequired, + callback: PropTypes.func.isRequired, + initialValue: PropTypes.string + }; + + componentDidUpdate() { + if (this.props.initialValue) { + this.textInput.value = this.props.initialValue; + } + } + + onSubmit() { + this.props.callback(this.textInput.value); + this.textInput.value = ""; + } + + onCancel() { + this.props.callback(undefined); + this.textInput.value = ""; + } + + render() { + return ( + <Modal + title={this.props.title} + show={this.props.show} + onSubmit={this.onSubmit.bind(this)} + onCancel={this.onCancel.bind(this)} + > + <form + onSubmit={e => { + e.preventDefault(); + this.onSubmit(); + }} + > + <div className="form-group"> + <label className="form-control-label">{this.props.label}</label> + <input + type="text" + className="form-control" + ref={textInput => (this.textInput = textInput)} + /> + </div> + </form> + </Modal> + ); + } +} + +export default TextInputModal; diff --git a/frontend/src/components/modals/custom-components/NewExperimentModalComponent.js b/frontend/src/components/modals/custom-components/NewExperimentModalComponent.js new file mode 100644 index 00000000..e356fe96 --- /dev/null +++ b/frontend/src/components/modals/custom-components/NewExperimentModalComponent.js @@ -0,0 +1,104 @@ +import PropTypes from "prop-types"; +import React from "react"; +import Shapes from "../../../shapes"; +import Modal from "../Modal"; + +class NewExperimentModalComponent extends React.Component { + static propTypes = { + show: PropTypes.bool.isRequired, + paths: PropTypes.arrayOf(Shapes.Path), + schedulers: PropTypes.arrayOf(Shapes.Scheduler), + traces: PropTypes.arrayOf(Shapes.Trace), + callback: PropTypes.func.isRequired + }; + + reset() { + this.textInput.value = ""; + this.pathSelect.selectedIndex = 0; + this.traceSelect.selectedIndex = 0; + this.schedulerSelect.selectedIndex = 0; + } + + onSubmit() { + this.props.callback( + this.textInput.value, + parseInt(this.pathSelect.value, 10), + parseInt(this.traceSelect.value, 10), + this.schedulerSelect.value + ); + this.reset(); + } + + onCancel() { + this.props.callback(undefined); + this.reset(); + } + + render() { + return ( + <Modal + title="New Experiment" + show={this.props.show} + onSubmit={this.onSubmit.bind(this)} + onCancel={this.onCancel.bind(this)} + > + <form + onSubmit={e => { + e.preventDefault(); + this.onSubmit(); + }} + > + <div className="form-group"> + <label className="form-control-label">Name</label> + <input + type="text" + className="form-control" + ref={textInput => (this.textInput = textInput)} + /> + </div> + <div className="form-group"> + <label className="form-control-label">Path</label> + <select + className="form-control" + ref={pathSelect => (this.pathSelect = pathSelect)} + > + {this.props.paths.map(path => ( + <option value={path.id} key={path.id}> + {path.name ? path.name : "Path " + path.id} + </option> + ))} + </select> + </div> + <div className="form-group"> + <label className="form-control-label">Trace</label> + <select + className="form-control" + ref={traceSelect => (this.traceSelect = traceSelect)} + > + {this.props.traces.map(trace => ( + <option value={trace.id} key={trace.id}> + {trace.name} + </option> + ))} + </select> + </div> + <div className="form-group"> + <label className="form-control-label">Scheduler</label> + <select + className="form-control" + ref={schedulerSelect => (this.schedulerSelect = schedulerSelect)} + > + {this.props.schedulers.map(scheduler => ( + <option value={scheduler.name} key={scheduler.name}> + {scheduler.name} + </option> + ))} + </select> + </div> + </form> + </Modal> + ); + } +} + +export default NewExperimentModalComponent; diff --git a/frontend/src/components/navigation/AppNavbar.js b/frontend/src/components/navigation/AppNavbar.js new file mode 100644 index 00000000..1a35f85d --- /dev/null +++ b/frontend/src/components/navigation/AppNavbar.js @@ -0,0 +1,56 @@ +import React from "react"; +import FontAwesome from "react-fontawesome"; +import { Link } from "react-router-dom"; +import Navbar, { NavItem } from "./Navbar"; +import "./Navbar.css"; + +const AppNavbar = ({ simulationId, inSimulation, fullWidth }) => ( + <Navbar fullWidth={fullWidth}> + {inSimulation ? ( + <NavItem route={"/simulations/" + simulationId}> + <Link + className="nav-link" + title="Construction" + to={"/simulations/" + simulationId} + > + <FontAwesome name="industry" className="mr-2" /> + Construction + </Link> + </NavItem> + ) : ( + undefined + )} + {inSimulation ? ( + <NavItem route={"/simulations/" + simulationId + "/experiments"}> + <Link + className="nav-link" + title="Experiments" + to={"/simulations/" + simulationId + "/experiments"} + > + <FontAwesome name="play" className="mr-2" /> + Experiments + </Link> + </NavItem> + ) : ( + undefined + )} + <NavItem route="/simulations"> + <Link className="nav-link" title="My Simulations" to="/simulations"> + <FontAwesome name="list" className="mr-2" /> + My Simulations + </Link> + </NavItem> + <NavItem route="email"> + <a + className="nav-link" + title="Support" + href="mailto:opendc@atlarge-research.com" + > + <FontAwesome name="envelope" className="mr-2" /> + Support + </a> + </NavItem> + </Navbar> +); + +export default AppNavbar; diff --git a/frontend/src/components/navigation/HomeNavbar.js b/frontend/src/components/navigation/HomeNavbar.js new file mode 100644 index 00000000..5d08bf3c --- /dev/null +++ b/frontend/src/components/navigation/HomeNavbar.js @@ -0,0 +1,24 @@ +import React from "react"; +import Navbar from "./Navbar"; +import "./Navbar.css"; + +const ScrollNavItem = ({ id, name }) => ( + <li className="nav-item"> + <a className="nav-link" href={id}> + {name} + </a> + </li> +); + +const HomeNavbar = () => ( + <Navbar fullWidth={false}> + <ScrollNavItem id="#stakeholders" name="Stakeholders" /> + <ScrollNavItem id="#modeling" name="Modeling" /> + <ScrollNavItem id="#simulation" name="Simulation" /> + <ScrollNavItem id="#technologies" name="Technologies" /> + <ScrollNavItem id="#team" name="Team" /> + <ScrollNavItem id="#contact" name="Contact" /> + </Navbar> +); + +export default HomeNavbar; diff --git a/frontend/src/components/navigation/LogoutButton.js b/frontend/src/components/navigation/LogoutButton.js new file mode 100644 index 00000000..800a3da8 --- /dev/null +++ b/frontend/src/components/navigation/LogoutButton.js @@ -0,0 +1,16 @@ +import PropTypes from "prop-types"; +import React from "react"; +import FontAwesome from "react-fontawesome"; +import { Link } from "react-router-dom"; + +const LogoutButton = ({ onLogout }) => ( + <Link className="logout nav-link" title="Sign out" to="#" onClick={onLogout}> + <FontAwesome name="power-off" size="lg" /> + </Link> +); + +LogoutButton.propTypes = { + onLogout: PropTypes.func.isRequired +}; + +export default LogoutButton; diff --git a/frontend/src/components/navigation/Navbar.js b/frontend/src/components/navigation/Navbar.js new file mode 100644 index 00000000..44458949 --- /dev/null +++ b/frontend/src/components/navigation/Navbar.js @@ -0,0 +1,102 @@ +import classNames from "classnames"; +import React from "react"; +import { Link, withRouter } from "react-router-dom"; +import { userIsLoggedIn } from "../../auth/index"; +import Login from "../../containers/auth/Login"; +import Logout from "../../containers/auth/Logout"; +import ProfileName from "../../containers/auth/ProfileName"; +import "./Navbar.css"; + +export const NAVBAR_HEIGHT = 60; + +export const NavItem = withRouter(props => <NavItemWithoutRoute {...props} />); +export const LoggedInSection = withRouter(props => ( + <LoggedInSectionWithoutRoute {...props} /> +)); + +const GitHubLink = () => ( + <a + href="https://github.com/atlarge-research/opendc" + className="ml-2 mr-3 text-dark" + style={{ position: "relative", top: 7 }} + > + <span className="fa fa-github fa-2x" /> + </a> +); + +const NavItemWithoutRoute = ({ route, location, children }) => ( + <li + className={classNames( + "nav-item", + location.pathname === route ? "active" : undefined + )} + > + {children} + </li> +); + +const LoggedInSectionWithoutRoute = ({ location }) => ( + <ul className="navbar-nav auth-links"> + {userIsLoggedIn() ? ( + [ + location.pathname === "/" ? ( + <NavItem route="/simulations" key="simulations"> + <Link className="nav-link" title="My Simulations" to="/simulations"> + My Simulations + </Link> + </NavItem> + ) : ( + <NavItem route="/profile" key="profile"> + <Link className="nav-link" title="My Profile" to="/profile"> + <ProfileName /> + </Link> + </NavItem> + ), + <NavItem route="logout" key="logout"> + <Logout /> + </NavItem> + ] + ) : ( + <NavItem route="login"> + <GitHubLink /> + <Login visible={true} /> + </NavItem> + )} + </ul> +); + +const Navbar = ({ fullWidth, children }) => ( + <nav + className="navbar fixed-top navbar-expand-lg navbar-light bg-faded" + id="navbar" + > + <div className={fullWidth ? "container-fluid" : "container"}> + <button + className="navbar-toggler navbar-toggler-right" + type="button" + data-toggle="collapse" + data-target="#navbarSupportedContent" + aria-controls="navbarSupportedContent" + aria-expanded="false" + aria-label="Toggle navigation" + > + <span className="navbar-toggler-icon" /> + </button> + <Link + className="navbar-brand opendc-brand" + to="/" + title="OpenDC" + onClick={() => window.scrollTo(0, 0)} + > + <img src="/img/logo.png" alt="OpenDC" /> + </Link> + + <div className="collapse navbar-collapse" id="navbarSupportedContent"> + <ul className="navbar-nav mr-auto">{children}</ul> + <LoggedInSection /> + </div> + </div> + </nav> +); + +export default Navbar; diff --git a/frontend/src/components/navigation/Navbar.sass b/frontend/src/components/navigation/Navbar.sass new file mode 100644 index 00000000..94c52936 --- /dev/null +++ b/frontend/src/components/navigation/Navbar.sass @@ -0,0 +1,29 @@ +@import ../../style-globals/_mixins.sass +@import ../../style-globals/_variables.sass + +.navbar + border-top: $blue 3px solid + border-bottom: $gray-semi-dark 1px solid + color: $gray-very-dark + background: #fafafb + +.opendc-brand + display: inline-block + color: $gray-very-dark + + +transition(background, $transition-length) + + img + position: relative + bottom: 3px + display: inline-block + width: 30px + +.login + height: 40px + background: $blue + border: none + +clickable + + &:hover + background: $blue-dark diff --git a/frontend/src/components/not-found/BlinkingCursor.js b/frontend/src/components/not-found/BlinkingCursor.js new file mode 100644 index 00000000..eea89e7b --- /dev/null +++ b/frontend/src/components/not-found/BlinkingCursor.js @@ -0,0 +1,6 @@ +import React from "react"; +import "./BlinkingCursor.css"; + +const BlinkingCursor = () => <span className="blinking-cursor">_</span>; + +export default BlinkingCursor; diff --git a/frontend/src/components/not-found/BlinkingCursor.sass b/frontend/src/components/not-found/BlinkingCursor.sass new file mode 100644 index 00000000..6be1476d --- /dev/null +++ b/frontend/src/components/not-found/BlinkingCursor.sass @@ -0,0 +1,35 @@ +.blinking-cursor + -webkit-animation: 1s blink step-end infinite + -moz-animation: 1s blink step-end infinite + -o-animation: 1s blink step-end infinite + animation: 1s blink step-end infinite + +@keyframes blink + from, to + color: #eeeeee + 50% + color: #333333 + +@-moz-keyframes blink + from, to + color: #eeeeee + 50% + color: #333333 + +@-webkit-keyframes blink + from, to + color: #eeeeee + 50% + color: #333333 + +@-ms-keyframes blink + from, to + color: #eeeeee + 50% + color: #333333 + +@-o-keyframes blink + from, to + color: #eeeeee + 50% + color: #333333 diff --git a/frontend/src/components/not-found/CodeBlock.js b/frontend/src/components/not-found/CodeBlock.js new file mode 100644 index 00000000..46dc4402 --- /dev/null +++ b/frontend/src/components/not-found/CodeBlock.js @@ -0,0 +1,34 @@ +import React from "react"; +import "./CodeBlock.css"; + +const CodeBlock = () => { + const textBlock = + " oo oooo oo <br/>" + + " oo oo oo oo <br/>" + + " oo oo oo oo <br/>" + + " oooooo oo oo oooooo <br/>" + + " oo oo oo oo <br/>" + + " oo oooo oo <br/>"; + const charList = textBlock.split(""); + + // Binary representation of the string "OpenDC!" ;) + const binaryString = + "01001111011100000110010101101110010001000100001100100001"; + + let binaryIndex = 0; + for (let i = 0; i < charList.length; i++) { + if (charList[i] === "o") { + charList[i] = binaryString[binaryIndex]; + binaryIndex++; + } + } + + return ( + <div + className="code-block" + dangerouslySetInnerHTML={{ __html: textBlock }} + /> + ); +}; + +export default CodeBlock; diff --git a/frontend/src/components/not-found/CodeBlock.sass b/frontend/src/components/not-found/CodeBlock.sass new file mode 100644 index 00000000..51a3d3d0 --- /dev/null +++ b/frontend/src/components/not-found/CodeBlock.sass @@ -0,0 +1,3 @@ +.code-block + white-space: pre-wrap + margin-top: 60px diff --git a/frontend/src/components/not-found/TerminalWindow.js b/frontend/src/components/not-found/TerminalWindow.js new file mode 100644 index 00000000..c6b8b78b --- /dev/null +++ b/frontend/src/components/not-found/TerminalWindow.js @@ -0,0 +1,29 @@ +import React from "react"; +import { Link } from "react-router-dom"; +import BlinkingCursor from "./BlinkingCursor"; +import CodeBlock from "./CodeBlock"; +import "./TerminalWindow.css"; + +const TerminalWindow = () => ( + <div className="terminal-window"> + <div className="terminal-header">Terminal -- bash</div> + <div className="terminal-body"> + <div className="segfault"> + $ status<br /> + opendc[4264]: segfault at 0000051497be459d1 err 12 in libopendc.9.0.4<br + /> + opendc[4269]: segfault at 000004234855fc2db err 3 in libopendc.9.0.4<br /> + opendc[4270]: STDERR Page does not exist<br /> + </div> + <CodeBlock /> + <div className="sub-title"> + Got lost?<BlinkingCursor /> + </div> + <Link to="/" className="home-btn"> + <span className="fa fa-home" /> GET ME BACK TO OPENDC + </Link> + </div> + </div> +); + +export default TerminalWindow; diff --git a/frontend/src/components/not-found/TerminalWindow.sass b/frontend/src/components/not-found/TerminalWindow.sass new file mode 100644 index 00000000..4f51a77f --- /dev/null +++ b/frontend/src/components/not-found/TerminalWindow.sass @@ -0,0 +1,70 @@ +.terminal-window + width: 600px + height: 400px + display: block + + position: absolute + top: 0 + bottom: 0 + left: 0 + right: 0 + + margin: auto + + -webkit-user-select: none + -moz-user-select: none + -ms-user-select: none + user-select: none + cursor: default + + overflow: hidden + + box-shadow: 5px 5px 20px #444444 + +.terminal-header + font-family: monospace + background: #cccccc + color: #444444 + height: 30px + line-height: 30px + padding-left: 10px + + border-top-left-radius: 7px + border-top-right-radius: 7px + +.terminal-body + font-family: monospace + text-align: center + background-color: #333333 + color: #eeeeee + padding: 10px + + height: 100% + +.segfault + text-align: left + +.sub-title + margin-top: 20px + +.home-btn + margin-top: 10px + padding: 5px + display: inline-block + border: 1px solid #eeeeee + color: #eeeeee + text-decoration: none + cursor: pointer + + -webkit-transition: all 200ms + -moz-transition: all 200ms + -o-transition: all 200ms + transition: all 200ms + +.home-btn:hover + background: #eeeeee + color: #333333 + +.home-btn:active + background: #333333 + color: #eeeeee diff --git a/frontend/src/components/simulations/FilterButton.js b/frontend/src/components/simulations/FilterButton.js new file mode 100644 index 00000000..aa41f180 --- /dev/null +++ b/frontend/src/components/simulations/FilterButton.js @@ -0,0 +1,24 @@ +import classNames from "classnames"; +import PropTypes from "prop-types"; +import React from "react"; + +const FilterButton = ({ active, children, onClick }) => ( + <button + className={classNames("btn btn-secondary", { active: active })} + onClick={() => { + if (!active) { + onClick(); + } + }} + > + {children} + </button> +); + +FilterButton.propTypes = { + active: PropTypes.bool.isRequired, + children: PropTypes.node.isRequired, + onClick: PropTypes.func.isRequired +}; + +export default FilterButton; diff --git a/frontend/src/components/simulations/FilterPanel.js b/frontend/src/components/simulations/FilterPanel.js new file mode 100644 index 00000000..836c0842 --- /dev/null +++ b/frontend/src/components/simulations/FilterPanel.js @@ -0,0 +1,13 @@ +import React from "react"; +import FilterLink from "../../containers/simulations/FilterLink"; +import "./FilterPanel.css"; + +const FilterPanel = () => ( + <div className="btn-group filter-panel mb-2"> + <FilterLink filter="SHOW_ALL">All Simulations</FilterLink> + <FilterLink filter="SHOW_OWN">My Simulations</FilterLink> + <FilterLink filter="SHOW_SHARED">Shared with me</FilterLink> + </div> +); + +export default FilterPanel; diff --git a/frontend/src/components/simulations/FilterPanel.sass b/frontend/src/components/simulations/FilterPanel.sass new file mode 100644 index 00000000..e10e4746 --- /dev/null +++ b/frontend/src/components/simulations/FilterPanel.sass @@ -0,0 +1,5 @@ +.filter-panel + display: flex + + button + flex: 1 !important diff --git a/frontend/src/components/simulations/NewSimulationButtonComponent.js b/frontend/src/components/simulations/NewSimulationButtonComponent.js new file mode 100644 index 00000000..7e12d30f --- /dev/null +++ b/frontend/src/components/simulations/NewSimulationButtonComponent.js @@ -0,0 +1,17 @@ +import PropTypes from "prop-types"; +import React from "react"; + +const NewSimulationButtonComponent = ({ onClick }) => ( + <div className="bottom-btn-container"> + <div className="btn btn-primary float-right" onClick={onClick}> + <span className="fa fa-plus mr-2" /> + New Simulation + </div> + </div> +); + +NewSimulationButtonComponent.propTypes = { + onClick: PropTypes.func.isRequired +}; + +export default NewSimulationButtonComponent; diff --git a/frontend/src/components/simulations/SimulationActionButtons.js b/frontend/src/components/simulations/SimulationActionButtons.js new file mode 100644 index 00000000..46f4f159 --- /dev/null +++ b/frontend/src/components/simulations/SimulationActionButtons.js @@ -0,0 +1,37 @@ +import PropTypes from "prop-types"; +import React from "react"; +import { Link } from "react-router-dom"; + +const SimulationActionButtons = ({ simulationId, onViewUsers, onDelete }) => ( + <td className="text-right"> + <Link + to={"/simulations/" + simulationId} + className="btn btn-outline-primary btn-sm mr-2" + title="Open this simulation" + > + <span className="fa fa-play" /> + </Link> + <div + className="btn btn-outline-success btn-sm disabled mr-2" + title="View and edit collaborators (not supported yet)" + onClick={() => onViewUsers(simulationId)} + > + <span className="fa fa-users" /> + </div> + <div + className="btn btn-outline-danger btn-sm" + title="Delete this simulation" + onClick={() => onDelete(simulationId)} + > + <span className="fa fa-trash" /> + </div> + </td> +); + +SimulationActionButtons.propTypes = { + simulationId: PropTypes.number.isRequired, + onViewUsers: PropTypes.func, + onDelete: PropTypes.func +}; + +export default SimulationActionButtons; diff --git a/frontend/src/components/simulations/SimulationAuthList.js b/frontend/src/components/simulations/SimulationAuthList.js new file mode 100644 index 00000000..f29dc96d --- /dev/null +++ b/frontend/src/components/simulations/SimulationAuthList.js @@ -0,0 +1,43 @@ +import PropTypes from "prop-types"; +import React from "react"; +import Shapes from "../../shapes/index"; +import SimulationAuthRow from "./SimulationAuthRow"; + +const SimulationAuthList = ({ authorizations }) => { + return ( + <div className="vertically-expanding-container"> + {authorizations.length === 0 ? ( + <div className="alert alert-info"> + <span className="info-icon fa fa-question-circle mr-2" /> + <strong>No simulations here yet...</strong> Add some with the 'New + Simulation' button! + </div> + ) : ( + <table className="table table-striped"> + <thead> + <tr> + <th>Simulation name</th> + <th>Last edited</th> + <th>Access rights</th> + <th /> + </tr> + </thead> + <tbody> + {authorizations.map(authorization => ( + <SimulationAuthRow + simulationAuth={authorization} + key={authorization.simulation.id} + /> + ))} + </tbody> + </table> + )} + </div> + ); +}; + +SimulationAuthList.propTypes = { + authorizations: PropTypes.arrayOf(Shapes.Authorization).isRequired +}; + +export default SimulationAuthList; diff --git a/frontend/src/components/simulations/SimulationAuthRow.js b/frontend/src/components/simulations/SimulationAuthRow.js new file mode 100644 index 00000000..b638fbce --- /dev/null +++ b/frontend/src/components/simulations/SimulationAuthRow.js @@ -0,0 +1,32 @@ +import classNames from "classnames"; +import React from "react"; +import SimulationActions from "../../containers/simulations/SimulationActions"; +import Shapes from "../../shapes/index"; +import { AUTH_DESCRIPTION_MAP, AUTH_ICON_MAP } from "../../util/authorizations"; +import { parseAndFormatDateTime } from "../../util/date-time"; + +const SimulationAuthRow = ({ simulationAuth }) => ( + <tr> + <td className="pt-3">{simulationAuth.simulation.name}</td> + <td className="pt-3"> + {parseAndFormatDateTime(simulationAuth.simulation.datetimeLastEdited)} + </td> + <td className="pt-3"> + <span + className={classNames( + "fa", + "fa-" + AUTH_ICON_MAP[simulationAuth.authorizationLevel], + "mr-2" + )} + /> + {AUTH_DESCRIPTION_MAP[simulationAuth.authorizationLevel]} + </td> + <SimulationActions simulationId={simulationAuth.simulation.id} /> + </tr> +); + +SimulationAuthRow.propTypes = { + simulationAuth: Shapes.Authorization.isRequired +}; + +export default SimulationAuthRow; diff --git a/frontend/src/containers/app/map/DatacenterContainer.js b/frontend/src/containers/app/map/DatacenterContainer.js new file mode 100644 index 00000000..125739f3 --- /dev/null +++ b/frontend/src/containers/app/map/DatacenterContainer.js @@ -0,0 +1,17 @@ +import { connect } from "react-redux"; +import DatacenterGroup from "../../../components/app/map/groups/DatacenterGroup"; + +const mapStateToProps = state => { + if (state.currentDatacenterId === -1) { + return {}; + } + + return { + datacenter: state.objects.datacenter[state.currentDatacenterId], + interactionLevel: state.interactionLevel + }; +}; + +const DatacenterContainer = connect(mapStateToProps)(DatacenterGroup); + +export default DatacenterContainer; diff --git a/frontend/src/containers/app/map/GrayContainer.js b/frontend/src/containers/app/map/GrayContainer.js new file mode 100644 index 00000000..d215bf6c --- /dev/null +++ b/frontend/src/containers/app/map/GrayContainer.js @@ -0,0 +1,13 @@ +import { connect } from "react-redux"; +import { goDownOneInteractionLevel } from "../../../actions/interaction-level"; +import GrayLayer from "../../../components/app/map/elements/GrayLayer"; + +const mapDispatchToProps = dispatch => { + return { + onClick: () => dispatch(goDownOneInteractionLevel()) + }; +}; + +const GrayContainer = connect(undefined, mapDispatchToProps)(GrayLayer); + +export default GrayContainer; diff --git a/frontend/src/containers/app/map/MapStage.js b/frontend/src/containers/app/map/MapStage.js new file mode 100644 index 00000000..a8467171 --- /dev/null +++ b/frontend/src/containers/app/map/MapStage.js @@ -0,0 +1,31 @@ +import { connect } from "react-redux"; +import { + setMapDimensions, + setMapPositionWithBoundsCheck, + zoomInOnPosition +} from "../../../actions/map"; +import MapStageComponent from "../../../components/app/map/MapStageComponent"; + +const mapStateToProps = state => { + return { + mapPosition: state.map.position, + mapDimensions: state.map.dimensions + }; +}; + +const mapDispatchToProps = dispatch => { + return { + zoomInOnPosition: (zoomIn, x, y) => + dispatch(zoomInOnPosition(zoomIn, x, y)), + setMapPositionWithBoundsCheck: (x, y) => + dispatch(setMapPositionWithBoundsCheck(x, y)), + setMapDimensions: (width, height) => + dispatch(setMapDimensions(width, height)) + }; +}; + +const MapStage = connect(mapStateToProps, mapDispatchToProps)( + MapStageComponent +); + +export default MapStage; diff --git a/frontend/src/containers/app/map/RackContainer.js b/frontend/src/containers/app/map/RackContainer.js new file mode 100644 index 00000000..365bb062 --- /dev/null +++ b/frontend/src/containers/app/map/RackContainer.js @@ -0,0 +1,30 @@ +import { connect } from "react-redux"; +import RackGroup from "../../../components/app/map/groups/RackGroup"; +import { getStateLoad } from "../../../util/simulation-load"; + +const mapStateToProps = (state, ownProps) => { + const inSimulation = state.currentExperimentId !== -1; + + let rackLoad = undefined; + if (inSimulation) { + if ( + state.states.rack[state.currentTick] && + state.states.rack[state.currentTick][ownProps.tile.objectId] + ) { + rackLoad = getStateLoad( + state.loadMetric, + state.states.rack[state.currentTick][ownProps.tile.objectId] + ); + } + } + + return { + interactionLevel: state.interactionLevel, + inSimulation, + rackLoad + }; +}; + +const RackContainer = connect(mapStateToProps)(RackGroup); + +export default RackContainer; diff --git a/frontend/src/containers/app/map/RackEnergyFillContainer.js b/frontend/src/containers/app/map/RackEnergyFillContainer.js new file mode 100644 index 00000000..0b7921d9 --- /dev/null +++ b/frontend/src/containers/app/map/RackEnergyFillContainer.js @@ -0,0 +1,40 @@ +import { connect } from "react-redux"; +import RackFillBar from "../../../components/app/map/elements/RackFillBar"; + +const mapStateToProps = (state, ownProps) => { + let energyConsumptionTotal = 0; + const rack = state.objects.rack[state.objects.tile[ownProps.tileId].objectId]; + const machineIds = rack.machineIds; + machineIds.forEach(machineId => { + if (machineId !== null) { + const machine = state.objects.machine[machineId]; + machine.cpuIds.forEach( + id => + (energyConsumptionTotal += state.objects.cpu[id].energyConsumptionW) + ); + machine.gpuIds.forEach( + id => + (energyConsumptionTotal += state.objects.gpu[id].energyConsumptionW) + ); + machine.memoryIds.forEach( + id => + (energyConsumptionTotal += + state.objects.memory[id].energyConsumptionW) + ); + machine.storageIds.forEach( + id => + (energyConsumptionTotal += + state.objects.storage[id].energyConsumptionW) + ); + } + }); + + return { + type: "energy", + fillFraction: Math.min(1, energyConsumptionTotal / rack.powerCapacityW) + }; +}; + +const RackSpaceFillContainer = connect(mapStateToProps)(RackFillBar); + +export default RackSpaceFillContainer; diff --git a/frontend/src/containers/app/map/RackSpaceFillContainer.js b/frontend/src/containers/app/map/RackSpaceFillContainer.js new file mode 100644 index 00000000..cc4d1251 --- /dev/null +++ b/frontend/src/containers/app/map/RackSpaceFillContainer.js @@ -0,0 +1,16 @@ +import { connect } from "react-redux"; +import RackFillBar from "../../../components/app/map/elements/RackFillBar"; + +const mapStateToProps = (state, ownProps) => { + const machineIds = + state.objects.rack[state.objects.tile[ownProps.tileId].objectId].machineIds; + return { + type: "space", + fillFraction: + machineIds.filter(id => id !== null).length / machineIds.length + }; +}; + +const RackSpaceFillContainer = connect(mapStateToProps)(RackFillBar); + +export default RackSpaceFillContainer; diff --git a/frontend/src/containers/app/map/RoomContainer.js b/frontend/src/containers/app/map/RoomContainer.js new file mode 100644 index 00000000..b83c7fa0 --- /dev/null +++ b/frontend/src/containers/app/map/RoomContainer.js @@ -0,0 +1,21 @@ +import { connect } from "react-redux"; +import { goFromBuildingToRoom } from "../../../actions/interaction-level"; +import RoomGroup from "../../../components/app/map/groups/RoomGroup"; + +const mapStateToProps = (state, ownProps) => { + return { + interactionLevel: state.interactionLevel, + currentRoomInConstruction: state.construction.currentRoomInConstruction, + room: state.objects.room[ownProps.roomId] + }; +}; + +const mapDispatchToProps = (dispatch, ownProps) => { + return { + onClick: () => dispatch(goFromBuildingToRoom(ownProps.roomId)) + }; +}; + +const RoomContainer = connect(mapStateToProps, mapDispatchToProps)(RoomGroup); + +export default RoomContainer; diff --git a/frontend/src/containers/app/map/TileContainer.js b/frontend/src/containers/app/map/TileContainer.js new file mode 100644 index 00000000..9e179924 --- /dev/null +++ b/frontend/src/containers/app/map/TileContainer.js @@ -0,0 +1,43 @@ +import { connect } from "react-redux"; +import { goFromRoomToRack } from "../../../actions/interaction-level"; +import TileGroup from "../../../components/app/map/groups/TileGroup"; +import { getStateLoad } from "../../../util/simulation-load"; + +const mapStateToProps = (state, ownProps) => { + const tile = state.objects.tile[ownProps.tileId]; + const inSimulation = state.currentExperimentId !== -1; + + let roomLoad = undefined; + if (inSimulation) { + if ( + state.states.room[state.currentTick] && + state.states.room[state.currentTick][tile.roomId] + ) { + roomLoad = getStateLoad( + state.loadMetric, + state.states.room[state.currentTick][tile.roomId] + ); + } + } + + return { + interactionLevel: state.interactionLevel, + tile, + inSimulation, + roomLoad + }; +}; + +const mapDispatchToProps = dispatch => { + return { + onClick: tile => { + if (tile.objectType) { + dispatch(goFromRoomToRack(tile.id)); + } + } + }; +}; + +const TileContainer = connect(mapStateToProps, mapDispatchToProps)(TileGroup); + +export default TileContainer; diff --git a/frontend/src/containers/app/map/WallContainer.js b/frontend/src/containers/app/map/WallContainer.js new file mode 100644 index 00000000..38192b05 --- /dev/null +++ b/frontend/src/containers/app/map/WallContainer.js @@ -0,0 +1,14 @@ +import { connect } from "react-redux"; +import WallGroup from "../../../components/app/map/groups/WallGroup"; + +const mapStateToProps = (state, ownProps) => { + return { + tiles: state.objects.room[ownProps.roomId].tileIds.map( + tileId => state.objects.tile[tileId] + ) + }; +}; + +const WallContainer = connect(mapStateToProps)(WallGroup); + +export default WallContainer; diff --git a/frontend/src/containers/app/map/controls/ScaleIndicatorContainer.js b/frontend/src/containers/app/map/controls/ScaleIndicatorContainer.js new file mode 100644 index 00000000..f075cde5 --- /dev/null +++ b/frontend/src/containers/app/map/controls/ScaleIndicatorContainer.js @@ -0,0 +1,14 @@ +import { connect } from "react-redux"; +import ScaleIndicatorComponent from "../../../../components/app/map/controls/ScaleIndicatorComponent"; + +const mapStateToProps = state => { + return { + scale: state.map.scale + }; +}; + +const ScaleIndicatorContainer = connect(mapStateToProps)( + ScaleIndicatorComponent +); + +export default ScaleIndicatorContainer; diff --git a/frontend/src/containers/app/map/controls/ZoomControlContainer.js b/frontend/src/containers/app/map/controls/ZoomControlContainer.js new file mode 100644 index 00000000..50910bd6 --- /dev/null +++ b/frontend/src/containers/app/map/controls/ZoomControlContainer.js @@ -0,0 +1,21 @@ +import { connect } from "react-redux"; +import { zoomInOnCenter } from "../../../../actions/map"; +import ZoomControlComponent from "../../../../components/app/map/controls/ZoomControlComponent"; + +const mapStateToProps = state => { + return { + mapScale: state.map.scale + }; +}; + +const mapDispatchToProps = dispatch => { + return { + zoomInOnCenter: zoomIn => dispatch(zoomInOnCenter(zoomIn)) + }; +}; + +const ZoomControlContainer = connect(mapStateToProps, mapDispatchToProps)( + ZoomControlComponent +); + +export default ZoomControlContainer; diff --git a/frontend/src/containers/app/map/layers/MapLayer.js b/frontend/src/containers/app/map/layers/MapLayer.js new file mode 100644 index 00000000..cf971350 --- /dev/null +++ b/frontend/src/containers/app/map/layers/MapLayer.js @@ -0,0 +1,13 @@ +import { connect } from "react-redux"; +import MapLayerComponent from "../../../../components/app/map/layers/MapLayerComponent"; + +const mapStateToProps = state => { + return { + mapPosition: state.map.position, + mapScale: state.map.scale + }; +}; + +const MapLayer = connect(mapStateToProps)(MapLayerComponent); + +export default MapLayer; diff --git a/frontend/src/containers/app/map/layers/ObjectHoverLayer.js b/frontend/src/containers/app/map/layers/ObjectHoverLayer.js new file mode 100644 index 00000000..9b28575e --- /dev/null +++ b/frontend/src/containers/app/map/layers/ObjectHoverLayer.js @@ -0,0 +1,37 @@ +import { connect } from "react-redux"; +import { addRackToTile } from "../../../../actions/topology/room"; +import ObjectHoverLayerComponent from "../../../../components/app/map/layers/ObjectHoverLayerComponent"; +import { findTileWithPosition } from "../../../../util/tile-calculations"; + +const mapStateToProps = state => { + return { + mapPosition: state.map.position, + mapScale: state.map.scale, + isEnabled: () => state.construction.inRackConstructionMode, + isValid: (x, y) => { + if (state.interactionLevel.mode !== "ROOM") { + return false; + } + + const currentRoom = state.objects.room[state.interactionLevel.roomId]; + const tiles = currentRoom.tileIds.map( + tileId => state.objects.tile[tileId] + ); + const tile = findTileWithPosition(tiles, x, y); + + return !(tile === null || tile.objectType); + } + }; +}; + +const mapDispatchToProps = dispatch => { + return { + onClick: (x, y) => dispatch(addRackToTile(x, y)) + }; +}; + +const ObjectHoverLayer = connect(mapStateToProps, mapDispatchToProps)( + ObjectHoverLayerComponent +); + +export default ObjectHoverLayer; diff --git a/frontend/src/containers/app/map/layers/RoomHoverLayer.js b/frontend/src/containers/app/map/layers/RoomHoverLayer.js new file mode 100644 index 00000000..020102bf --- /dev/null +++ b/frontend/src/containers/app/map/layers/RoomHoverLayer.js @@ -0,0 +1,55 @@ +import { connect } from "react-redux"; +import { toggleTileAtLocation } from "../../../../actions/topology/building"; +import RoomHoverLayerComponent from "../../../../components/app/map/layers/RoomHoverLayerComponent"; +import { + deriveValidNextTilePositions, + findPositionInPositions, + findPositionInRooms +} from "../../../../util/tile-calculations"; + +const mapStateToProps = state => { + return { + mapPosition: state.map.position, + mapScale: state.map.scale, + isEnabled: () => state.construction.currentRoomInConstruction !== -1, + isValid: (x, y) => { + const newRoom = Object.assign( + {}, + state.objects.room[state.construction.currentRoomInConstruction] + ); + const oldRooms = Object.keys(state.objects.room) + .map(id => Object.assign({}, state.objects.room[id])) + .filter( + room => + state.objects.datacenter[state.currentDatacenterId].roomIds.indexOf( + room.id + ) !== -1 && room.id !== state.construction.currentRoomInConstruction + ); + + [...oldRooms, newRoom].forEach(room => { + room.tiles = room.tileIds.map(tileId => state.objects.tile[tileId]); + }); + if (newRoom.tileIds.length === 0) { + return findPositionInRooms(oldRooms, x, y) === -1; + } + + const validNextPositions = deriveValidNextTilePositions( + oldRooms, + newRoom.tiles + ); + return findPositionInPositions(validNextPositions, x, y) !== -1; + } + }; +}; + +const mapDispatchToProps = dispatch => { + return { + onClick: (x, y) => dispatch(toggleTileAtLocation(x, y)) + }; +}; + +const RoomHoverLayer = connect(mapStateToProps, mapDispatchToProps)( + RoomHoverLayerComponent +); + +export default RoomHoverLayer; diff --git a/frontend/src/containers/app/sidebars/elements/LoadBarContainer.js b/frontend/src/containers/app/sidebars/elements/LoadBarContainer.js new file mode 100644 index 00000000..2e637f9a --- /dev/null +++ b/frontend/src/containers/app/sidebars/elements/LoadBarContainer.js @@ -0,0 +1,32 @@ +import { connect } from "react-redux"; +import LoadBarComponent from "../../../../components/app/sidebars/elements/LoadBarComponent"; +import { getStateLoad } from "../../../../util/simulation-load"; + +const mapStateToProps = (state, ownProps) => { + let percent = 0; + let enabled = false; + + const objectStates = state.states[ownProps.objectType]; + if ( + objectStates[state.currentTick] && + objectStates[state.currentTick][ownProps.objectId] + ) { + percent = Math.floor( + 100 * + getStateLoad( + state.loadMetric, + objectStates[state.currentTick][ownProps.objectId] + ) + ); + enabled = true; + } + + return { + percent, + enabled + }; +}; + +const LoadBarContainer = connect(mapStateToProps)(LoadBarComponent); + +export default LoadBarContainer; diff --git a/frontend/src/containers/app/sidebars/elements/LoadChartContainer.js b/frontend/src/containers/app/sidebars/elements/LoadChartContainer.js new file mode 100644 index 00000000..57bfec38 --- /dev/null +++ b/frontend/src/containers/app/sidebars/elements/LoadChartContainer.js @@ -0,0 +1,31 @@ +import { connect } from "react-redux"; +import LoadChartComponent from "../../../../components/app/sidebars/elements/LoadChartComponent"; +import { getStateLoad } from "../../../../util/simulation-load"; + +const mapStateToProps = (state, ownProps) => { + const data = []; + + if (state.lastSimulatedTick !== -1) { + const objectStates = state.states[ownProps.objectType]; + Object.keys(objectStates).forEach(tick => { + if (objectStates[tick][ownProps.objectId]) { + data.push({ + x: tick, + y: getStateLoad( + state.loadMetric, + objectStates[tick][ownProps.objectId] + ) + }); + } + }); + } + + return { + data, + currentTick: state.currentTick + }; +}; + +const LoadChartContainer = connect(mapStateToProps)(LoadChartComponent); + +export default LoadChartContainer; diff --git a/frontend/src/containers/app/sidebars/simulation/ExperimentMetadataContainer.js b/frontend/src/containers/app/sidebars/simulation/ExperimentMetadataContainer.js new file mode 100644 index 00000000..25a0d9e9 --- /dev/null +++ b/frontend/src/containers/app/sidebars/simulation/ExperimentMetadataContainer.js @@ -0,0 +1,38 @@ +import { connect } from "react-redux"; +import ExperimentMetadataComponent from "../../../../components/app/sidebars/simulation/ExperimentMetadataComponent"; + +const mapStateToProps = state => { + if (!state.objects.experiment[state.currentExperimentId]) { + return { + experimentName: "Loading experiment", + pathName: "", + traceName: "", + schedulerName: "" + }; + } + + const path = + state.objects.path[ + state.objects.experiment[state.currentExperimentId].pathId + ]; + const pathName = path.name ? path.name : "Path " + path.id; + + return { + experimentName: state.objects.experiment[state.currentExperimentId].name, + pathName, + traceName: + state.objects.trace[ + state.objects.experiment[state.currentExperimentId].traceId + ].name, + schedulerName: + state.objects.scheduler[ + state.objects.experiment[state.currentExperimentId].schedulerName + ].name + }; +}; + +const ExperimentMetadataContainer = connect(mapStateToProps)( + ExperimentMetadataComponent +); + +export default ExperimentMetadataContainer; diff --git a/frontend/src/containers/app/sidebars/simulation/LoadMetricContainer.js b/frontend/src/containers/app/sidebars/simulation/LoadMetricContainer.js new file mode 100644 index 00000000..0c66b582 --- /dev/null +++ b/frontend/src/containers/app/sidebars/simulation/LoadMetricContainer.js @@ -0,0 +1,12 @@ +import { connect } from "react-redux"; +import LoadMetricComponent from "../../../../components/app/sidebars/simulation/LoadMetricComponent"; + +const mapStateToProps = state => { + return { + loadMetric: state.loadMetric + }; +}; + +const LoadMetricContainer = connect(mapStateToProps)(LoadMetricComponent); + +export default LoadMetricContainer; diff --git a/frontend/src/containers/app/sidebars/simulation/TaskContainer.js b/frontend/src/containers/app/sidebars/simulation/TaskContainer.js new file mode 100644 index 00000000..093d4266 --- /dev/null +++ b/frontend/src/containers/app/sidebars/simulation/TaskContainer.js @@ -0,0 +1,26 @@ +import { connect } from "react-redux"; +import TaskComponent from "../../../../components/app/sidebars/simulation/TaskComponent"; + +const mapStateToProps = (state, ownProps) => { + let flopsLeft = state.objects.task[ownProps.taskId].totalFlopCount; + + if ( + state.states.task[state.currentTick] && + state.states.task[state.currentTick][ownProps.taskId] + ) { + flopsLeft = state.states.task[state.currentTick][ownProps.taskId].flopsLeft; + } else if ( + state.objects.task[ownProps.taskId].startTick < state.currentTick + ) { + flopsLeft = 0; + } + + return { + task: state.objects.task[ownProps.taskId], + flopsLeft + }; +}; + +const TaskContainer = connect(mapStateToProps)(TaskComponent); + +export default TaskContainer; diff --git a/frontend/src/containers/app/sidebars/simulation/TraceContainer.js b/frontend/src/containers/app/sidebars/simulation/TraceContainer.js new file mode 100644 index 00000000..682b6cc9 --- /dev/null +++ b/frontend/src/containers/app/sidebars/simulation/TraceContainer.js @@ -0,0 +1,25 @@ +import { connect } from "react-redux"; +import TraceComponent from "../../../../components/app/sidebars/simulation/TraceComponent"; + +const mapStateToProps = state => { + if ( + !state.objects.experiment[state.currentExperimentId] || + !state.objects.trace[ + state.objects.experiment[state.currentExperimentId].traceId + ].jobIds + ) { + return { + jobs: [] + }; + } + + return { + jobs: state.objects.trace[ + state.objects.experiment[state.currentExperimentId].traceId + ].jobIds.map(id => state.objects.job[id]) + }; +}; + +const TraceContainer = connect(mapStateToProps)(TraceComponent); + +export default TraceContainer; diff --git a/frontend/src/containers/app/sidebars/topology/TopologySidebar.js b/frontend/src/containers/app/sidebars/topology/TopologySidebar.js new file mode 100644 index 00000000..31c902fc --- /dev/null +++ b/frontend/src/containers/app/sidebars/topology/TopologySidebar.js @@ -0,0 +1,12 @@ +import { connect } from "react-redux"; +import TopologySidebarComponent from "../../../../components/app/sidebars/topology/TopologySidebarComponent"; + +const mapStateToProps = state => { + return { + interactionLevel: state.interactionLevel + }; +}; + +const TopologySidebar = connect(mapStateToProps)(TopologySidebarComponent); + +export default TopologySidebar; diff --git a/frontend/src/containers/app/sidebars/topology/building/BuildingSidebarContainer.js b/frontend/src/containers/app/sidebars/topology/building/BuildingSidebarContainer.js new file mode 100644 index 00000000..da24b8f0 --- /dev/null +++ b/frontend/src/containers/app/sidebars/topology/building/BuildingSidebarContainer.js @@ -0,0 +1,14 @@ +import { connect } from "react-redux"; +import BuildingSidebarComponent from "../../../../../components/app/sidebars/topology/building/BuildingSidebarComponent"; + +const mapStateToProps = state => { + return { + inSimulation: state.currentExperimentId !== -1 + }; +}; + +const BuildingSidebarContainer = connect(mapStateToProps)( + BuildingSidebarComponent +); + +export default BuildingSidebarContainer; diff --git a/frontend/src/containers/app/sidebars/topology/building/NewRoomConstructionContainer.js b/frontend/src/containers/app/sidebars/topology/building/NewRoomConstructionContainer.js new file mode 100644 index 00000000..bb64cbb4 --- /dev/null +++ b/frontend/src/containers/app/sidebars/topology/building/NewRoomConstructionContainer.js @@ -0,0 +1,27 @@ +import { connect } from "react-redux"; +import { + cancelNewRoomConstruction, + finishNewRoomConstruction, + startNewRoomConstruction +} from "../../../../../actions/topology/building"; +import StartNewRoomConstructionComponent from "../../../../../components/app/sidebars/topology/building/NewRoomConstructionComponent"; + +const mapStateToProps = state => { + return { + currentRoomInConstruction: state.construction.currentRoomInConstruction + }; +}; + +const mapDispatchToProps = dispatch => { + return { + onStart: () => dispatch(startNewRoomConstruction()), + onFinish: () => dispatch(finishNewRoomConstruction()), + onCancel: () => dispatch(cancelNewRoomConstruction()) + }; +}; + +const NewRoomConstructionButton = connect(mapStateToProps, mapDispatchToProps)( + StartNewRoomConstructionComponent +); + +export default NewRoomConstructionButton; diff --git a/frontend/src/containers/app/sidebars/topology/machine/BackToRackContainer.js b/frontend/src/containers/app/sidebars/topology/machine/BackToRackContainer.js new file mode 100644 index 00000000..885c533d --- /dev/null +++ b/frontend/src/containers/app/sidebars/topology/machine/BackToRackContainer.js @@ -0,0 +1,15 @@ +import { connect } from "react-redux"; +import { goDownOneInteractionLevel } from "../../../../../actions/interaction-level"; +import BackToRackComponent from "../../../../../components/app/sidebars/topology/machine/BackToRackComponent"; + +const mapDispatchToProps = dispatch => { + return { + onClick: () => dispatch(goDownOneInteractionLevel()) + }; +}; + +const BackToRackContainer = connect(undefined, mapDispatchToProps)( + BackToRackComponent +); + +export default BackToRackContainer; diff --git a/frontend/src/containers/app/sidebars/topology/machine/DeleteMachineContainer.js b/frontend/src/containers/app/sidebars/topology/machine/DeleteMachineContainer.js new file mode 100644 index 00000000..f42c8ba7 --- /dev/null +++ b/frontend/src/containers/app/sidebars/topology/machine/DeleteMachineContainer.js @@ -0,0 +1,15 @@ +import { connect } from "react-redux"; +import { openDeleteMachineModal } from "../../../../../actions/modals/topology"; +import DeleteMachineComponent from "../../../../../components/app/sidebars/topology/machine/DeleteMachineComponent"; + +const mapDispatchToProps = dispatch => { + return { + onClick: () => dispatch(openDeleteMachineModal()) + }; +}; + +const DeleteMachineContainer = connect(undefined, mapDispatchToProps)( + DeleteMachineComponent +); + +export default DeleteMachineContainer; diff --git a/frontend/src/containers/app/sidebars/topology/machine/MachineNameContainer.js b/frontend/src/containers/app/sidebars/topology/machine/MachineNameContainer.js new file mode 100644 index 00000000..05d2bf80 --- /dev/null +++ b/frontend/src/containers/app/sidebars/topology/machine/MachineNameContainer.js @@ -0,0 +1,12 @@ +import { connect } from "react-redux"; +import MachineNameComponent from "../../../../../components/app/sidebars/topology/machine/MachineNameComponent"; + +const mapStateToProps = state => { + return { + position: state.interactionLevel.position + }; +}; + +const MachineNameContainer = connect(mapStateToProps)(MachineNameComponent); + +export default MachineNameContainer; diff --git a/frontend/src/containers/app/sidebars/topology/machine/MachineSidebarContainer.js b/frontend/src/containers/app/sidebars/topology/machine/MachineSidebarContainer.js new file mode 100644 index 00000000..7729385e --- /dev/null +++ b/frontend/src/containers/app/sidebars/topology/machine/MachineSidebarContainer.js @@ -0,0 +1,18 @@ +import { connect } from "react-redux"; +import MachineSidebarComponent from "../../../../../components/app/sidebars/topology/machine/MachineSidebarComponent"; + +const mapStateToProps = state => { + return { + inSimulation: state.currentExperimentId !== -1, + machineId: + state.objects.rack[ + state.objects.tile[state.interactionLevel.tileId].objectId + ].machineIds[state.interactionLevel.position - 1] + }; +}; + +const MachineSidebarContainer = connect(mapStateToProps)( + MachineSidebarComponent +); + +export default MachineSidebarContainer; diff --git a/frontend/src/containers/app/sidebars/topology/machine/UnitAddContainer.js b/frontend/src/containers/app/sidebars/topology/machine/UnitAddContainer.js new file mode 100644 index 00000000..0e5a6073 --- /dev/null +++ b/frontend/src/containers/app/sidebars/topology/machine/UnitAddContainer.js @@ -0,0 +1,21 @@ +import { connect } from "react-redux"; +import { addUnit } from "../../../../../actions/topology/machine"; +import UnitAddComponent from "../../../../../components/app/sidebars/topology/machine/UnitAddComponent"; + +const mapStateToProps = (state, ownProps) => { + return { + units: Object.values(state.objects[ownProps.unitType]) + }; +}; + +const mapDispatchToProps = (dispatch, ownProps) => { + return { + onAdd: id => dispatch(addUnit(ownProps.unitType, id)) + }; +}; + +const UnitAddContainer = connect(mapStateToProps, mapDispatchToProps)( + UnitAddComponent +); + +export default UnitAddContainer; diff --git a/frontend/src/containers/app/sidebars/topology/machine/UnitContainer.js b/frontend/src/containers/app/sidebars/topology/machine/UnitContainer.js new file mode 100644 index 00000000..a919e8d3 --- /dev/null +++ b/frontend/src/containers/app/sidebars/topology/machine/UnitContainer.js @@ -0,0 +1,22 @@ +import { connect } from "react-redux"; +import { deleteUnit } from "../../../../../actions/topology/machine"; +import UnitComponent from "../../../../../components/app/sidebars/topology/machine/UnitComponent"; + +const mapStateToProps = (state, ownProps) => { + return { + unit: state.objects[ownProps.unitType][ownProps.unitId], + inSimulation: state.currentExperimentId !== -1 + }; +}; + +const mapDispatchToProps = (dispatch, ownProps) => { + return { + onDelete: () => dispatch(deleteUnit(ownProps.unitType, ownProps.index)) + }; +}; + +const UnitContainer = connect(mapStateToProps, mapDispatchToProps)( + UnitComponent +); + +export default UnitContainer; diff --git a/frontend/src/containers/app/sidebars/topology/machine/UnitListContainer.js b/frontend/src/containers/app/sidebars/topology/machine/UnitListContainer.js new file mode 100644 index 00000000..6554b8f8 --- /dev/null +++ b/frontend/src/containers/app/sidebars/topology/machine/UnitListContainer.js @@ -0,0 +1,18 @@ +import { connect } from "react-redux"; +import UnitListComponent from "../../../../../components/app/sidebars/topology/machine/UnitListComponent"; + +const mapStateToProps = (state, ownProps) => { + return { + unitIds: + state.objects.machine[ + state.objects.rack[ + state.objects.tile[state.interactionLevel.tileId].objectId + ].machineIds[state.interactionLevel.position - 1] + ][ownProps.unitType + "Ids"], + inSimulation: state.currentExperimentId !== -1 + }; +}; + +const UnitListContainer = connect(mapStateToProps)(UnitListComponent); + +export default UnitListContainer; diff --git a/frontend/src/containers/app/sidebars/topology/machine/UnitTabsContainer.js b/frontend/src/containers/app/sidebars/topology/machine/UnitTabsContainer.js new file mode 100644 index 00000000..85d83877 --- /dev/null +++ b/frontend/src/containers/app/sidebars/topology/machine/UnitTabsContainer.js @@ -0,0 +1,12 @@ +import { connect } from "react-redux"; +import UnitTabsComponent from "../../../../../components/app/sidebars/topology/machine/UnitTabsComponent"; + +const mapStateToProps = state => { + return { + inSimulation: state.currentExperimentId !== -1 + }; +}; + +const UnitTabsContainer = connect(mapStateToProps)(UnitTabsComponent); + +export default UnitTabsContainer; diff --git a/frontend/src/containers/app/sidebars/topology/rack/BackToRoomContainer.js b/frontend/src/containers/app/sidebars/topology/rack/BackToRoomContainer.js new file mode 100644 index 00000000..1b1bb2b0 --- /dev/null +++ b/frontend/src/containers/app/sidebars/topology/rack/BackToRoomContainer.js @@ -0,0 +1,15 @@ +import { connect } from "react-redux"; +import { goDownOneInteractionLevel } from "../../../../../actions/interaction-level"; +import BackToRoomComponent from "../../../../../components/app/sidebars/topology/rack/BackToRoomComponent"; + +const mapDispatchToProps = dispatch => { + return { + onClick: () => dispatch(goDownOneInteractionLevel()) + }; +}; + +const BackToRoomContainer = connect(undefined, mapDispatchToProps)( + BackToRoomComponent +); + +export default BackToRoomContainer; diff --git a/frontend/src/containers/app/sidebars/topology/rack/DeleteRackContainer.js b/frontend/src/containers/app/sidebars/topology/rack/DeleteRackContainer.js new file mode 100644 index 00000000..a54ceb23 --- /dev/null +++ b/frontend/src/containers/app/sidebars/topology/rack/DeleteRackContainer.js @@ -0,0 +1,15 @@ +import { connect } from "react-redux"; +import { openDeleteRackModal } from "../../../../../actions/modals/topology"; +import DeleteRackComponent from "../../../../../components/app/sidebars/topology/rack/DeleteRackComponent"; + +const mapDispatchToProps = dispatch => { + return { + onClick: () => dispatch(openDeleteRackModal()) + }; +}; + +const DeleteRackContainer = connect(undefined, mapDispatchToProps)( + DeleteRackComponent +); + +export default DeleteRackContainer; diff --git a/frontend/src/containers/app/sidebars/topology/rack/EmptySlotContainer.js b/frontend/src/containers/app/sidebars/topology/rack/EmptySlotContainer.js new file mode 100644 index 00000000..527805a2 --- /dev/null +++ b/frontend/src/containers/app/sidebars/topology/rack/EmptySlotContainer.js @@ -0,0 +1,21 @@ +import { connect } from "react-redux"; +import { addMachine } from "../../../../../actions/topology/rack"; +import EmptySlotComponent from "../../../../../components/app/sidebars/topology/rack/EmptySlotComponent"; + +const mapStateToProps = state => { + return { + inSimulation: state.currentExperimentId !== -1 + }; +}; + +const mapDispatchToProps = (dispatch, ownProps) => { + return { + onAdd: () => dispatch(addMachine(ownProps.position)) + }; +}; + +const EmptySlotContainer = connect(mapStateToProps, mapDispatchToProps)( + EmptySlotComponent +); + +export default EmptySlotContainer; diff --git a/frontend/src/containers/app/sidebars/topology/rack/MachineContainer.js b/frontend/src/containers/app/sidebars/topology/rack/MachineContainer.js new file mode 100644 index 00000000..8cd177e7 --- /dev/null +++ b/frontend/src/containers/app/sidebars/topology/rack/MachineContainer.js @@ -0,0 +1,40 @@ +import { connect } from "react-redux"; +import { goFromRackToMachine } from "../../../../../actions/interaction-level"; +import MachineComponent from "../../../../../components/app/sidebars/topology/rack/MachineComponent"; +import { getStateLoad } from "../../../../../util/simulation-load"; + +const mapStateToProps = (state, ownProps) => { + const machine = state.objects.machine[ownProps.machineId]; + const inSimulation = state.currentExperimentId !== -1; + + let machineLoad = undefined; + if (inSimulation) { + if ( + state.states.machine[state.currentTick] && + state.states.machine[state.currentTick][machine.id] + ) { + machineLoad = getStateLoad( + state.loadMetric, + state.states.machine[state.currentTick][machine.id] + ); + } + } + + return { + machine, + inSimulation, + machineLoad + }; +}; + +const mapDispatchToProps = (dispatch, ownProps) => { + return { + onClick: () => dispatch(goFromRackToMachine(ownProps.position)) + }; +}; + +const MachineContainer = connect(mapStateToProps, mapDispatchToProps)( + MachineComponent +); + +export default MachineContainer; diff --git a/frontend/src/containers/app/sidebars/topology/rack/MachineListContainer.js b/frontend/src/containers/app/sidebars/topology/rack/MachineListContainer.js new file mode 100644 index 00000000..b19a50ae --- /dev/null +++ b/frontend/src/containers/app/sidebars/topology/rack/MachineListContainer.js @@ -0,0 +1,15 @@ +import { connect } from "react-redux"; +import MachineListComponent from "../../../../../components/app/sidebars/topology/rack/MachineListComponent"; + +const mapStateToProps = state => { + return { + machineIds: + state.objects.rack[ + state.objects.tile[state.interactionLevel.tileId].objectId + ].machineIds + }; +}; + +const MachineListContainer = connect(mapStateToProps)(MachineListComponent); + +export default MachineListContainer; diff --git a/frontend/src/containers/app/sidebars/topology/rack/RackNameContainer.js b/frontend/src/containers/app/sidebars/topology/rack/RackNameContainer.js new file mode 100644 index 00000000..8f364ca0 --- /dev/null +++ b/frontend/src/containers/app/sidebars/topology/rack/RackNameContainer.js @@ -0,0 +1,24 @@ +import { connect } from "react-redux"; +import { openEditRackNameModal } from "../../../../../actions/modals/topology"; +import RackNameComponent from "../../../../../components/app/sidebars/topology/rack/RackNameComponent"; + +const mapStateToProps = state => { + return { + rackName: + state.objects.rack[ + state.objects.tile[state.interactionLevel.tileId].objectId + ].name + }; +}; + +const mapDispatchToProps = dispatch => { + return { + onEdit: () => dispatch(openEditRackNameModal()) + }; +}; + +const RackNameContainer = connect(mapStateToProps, mapDispatchToProps)( + RackNameComponent +); + +export default RackNameContainer; diff --git a/frontend/src/containers/app/sidebars/topology/rack/RackSidebarContainer.js b/frontend/src/containers/app/sidebars/topology/rack/RackSidebarContainer.js new file mode 100644 index 00000000..0a2bfdcc --- /dev/null +++ b/frontend/src/containers/app/sidebars/topology/rack/RackSidebarContainer.js @@ -0,0 +1,13 @@ +import { connect } from "react-redux"; +import RackSidebarComponent from "../../../../../components/app/sidebars/topology/rack/RackSidebarComponent"; + +const mapStateToProps = state => { + return { + rackId: state.objects.tile[state.interactionLevel.tileId].objectId, + inSimulation: state.currentExperimentId !== -1 + }; +}; + +const RackSidebarContainer = connect(mapStateToProps)(RackSidebarComponent); + +export default RackSidebarContainer; diff --git a/frontend/src/containers/app/sidebars/topology/room/BackToBuildingContainer.js b/frontend/src/containers/app/sidebars/topology/room/BackToBuildingContainer.js new file mode 100644 index 00000000..02288b7b --- /dev/null +++ b/frontend/src/containers/app/sidebars/topology/room/BackToBuildingContainer.js @@ -0,0 +1,15 @@ +import { connect } from "react-redux"; +import { goDownOneInteractionLevel } from "../../../../../actions/interaction-level"; +import BackToBuildingComponent from "../../../../../components/app/sidebars/topology/room/BackToBuildingComponent"; + +const mapDispatchToProps = dispatch => { + return { + onClick: () => dispatch(goDownOneInteractionLevel()) + }; +}; + +const BackToBuildingContainer = connect(undefined, mapDispatchToProps)( + BackToBuildingComponent +); + +export default BackToBuildingContainer; diff --git a/frontend/src/containers/app/sidebars/topology/room/DeleteRoomContainer.js b/frontend/src/containers/app/sidebars/topology/room/DeleteRoomContainer.js new file mode 100644 index 00000000..5223061d --- /dev/null +++ b/frontend/src/containers/app/sidebars/topology/room/DeleteRoomContainer.js @@ -0,0 +1,15 @@ +import { connect } from "react-redux"; +import { openDeleteRoomModal } from "../../../../../actions/modals/topology"; +import DeleteRoomComponent from "../../../../../components/app/sidebars/topology/room/DeleteRoomComponent"; + +const mapDispatchToProps = dispatch => { + return { + onClick: () => dispatch(openDeleteRoomModal()) + }; +}; + +const DeleteRoomContainer = connect(undefined, mapDispatchToProps)( + DeleteRoomComponent +); + +export default DeleteRoomContainer; diff --git a/frontend/src/containers/app/sidebars/topology/room/EditRoomContainer.js b/frontend/src/containers/app/sidebars/topology/room/EditRoomContainer.js new file mode 100644 index 00000000..81052f54 --- /dev/null +++ b/frontend/src/containers/app/sidebars/topology/room/EditRoomContainer.js @@ -0,0 +1,26 @@ +import { connect } from "react-redux"; +import { + finishRoomEdit, + startRoomEdit +} from "../../../../../actions/topology/building"; +import EditRoomComponent from "../../../../../components/app/sidebars/topology/room/EditRoomComponent"; + +const mapStateToProps = state => { + return { + isEditing: state.construction.currentRoomInConstruction !== -1, + isInRackConstructionMode: state.construction.inRackConstructionMode + }; +}; + +const mapDispatchToProps = dispatch => { + return { + onEdit: () => dispatch(startRoomEdit()), + onFinish: () => dispatch(finishRoomEdit()) + }; +}; + +const EditRoomContainer = connect(mapStateToProps, mapDispatchToProps)( + EditRoomComponent +); + +export default EditRoomContainer; diff --git a/frontend/src/containers/app/sidebars/topology/room/RackConstructionContainer.js b/frontend/src/containers/app/sidebars/topology/room/RackConstructionContainer.js new file mode 100644 index 00000000..c784d3ae --- /dev/null +++ b/frontend/src/containers/app/sidebars/topology/room/RackConstructionContainer.js @@ -0,0 +1,26 @@ +import { connect } from "react-redux"; +import { + startRackConstruction, + stopRackConstruction +} from "../../../../../actions/topology/room"; +import RackConstructionComponent from "../../../../../components/app/sidebars/topology/room/RackConstructionComponent"; + +const mapStateToProps = state => { + return { + inRackConstructionMode: state.construction.inRackConstructionMode, + isEditingRoom: state.construction.currentRoomInConstruction !== -1 + }; +}; + +const mapDispatchToProps = dispatch => { + return { + onStart: () => dispatch(startRackConstruction()), + onStop: () => dispatch(stopRackConstruction()) + }; +}; + +const RackConstructionContainer = connect(mapStateToProps, mapDispatchToProps)( + RackConstructionComponent +); + +export default RackConstructionContainer; diff --git a/frontend/src/containers/app/sidebars/topology/room/RoomNameContainer.js b/frontend/src/containers/app/sidebars/topology/room/RoomNameContainer.js new file mode 100644 index 00000000..36125521 --- /dev/null +++ b/frontend/src/containers/app/sidebars/topology/room/RoomNameContainer.js @@ -0,0 +1,21 @@ +import { connect } from "react-redux"; +import { openEditRoomNameModal } from "../../../../../actions/modals/topology"; +import RoomNameComponent from "../../../../../components/app/sidebars/topology/room/RoomNameComponent"; + +const mapStateToProps = state => { + return { + roomName: state.objects.room[state.interactionLevel.roomId].name + }; +}; + +const mapDispatchToProps = dispatch => { + return { + onEdit: () => dispatch(openEditRoomNameModal()) + }; +}; + +const RoomNameContainer = connect(mapStateToProps, mapDispatchToProps)( + RoomNameComponent +); + +export default RoomNameContainer; diff --git a/frontend/src/containers/app/sidebars/topology/room/RoomSidebarContainer.js b/frontend/src/containers/app/sidebars/topology/room/RoomSidebarContainer.js new file mode 100644 index 00000000..38d5fb80 --- /dev/null +++ b/frontend/src/containers/app/sidebars/topology/room/RoomSidebarContainer.js @@ -0,0 +1,14 @@ +import { connect } from "react-redux"; +import RoomSidebarComponent from "../../../../../components/app/sidebars/topology/room/RoomSidebarComponent"; + +const mapStateToProps = state => { + return { + roomId: state.interactionLevel.roomId, + roomType: state.objects.room[state.interactionLevel.roomId].roomType, + inSimulation: state.currentExperimentId !== -1 + }; +}; + +const RoomSidebarContainer = connect(mapStateToProps)(RoomSidebarComponent); + +export default RoomSidebarContainer; diff --git a/frontend/src/containers/app/sidebars/topology/room/RoomTypeContainer.js b/frontend/src/containers/app/sidebars/topology/room/RoomTypeContainer.js new file mode 100644 index 00000000..414852f1 --- /dev/null +++ b/frontend/src/containers/app/sidebars/topology/room/RoomTypeContainer.js @@ -0,0 +1,12 @@ +import { connect } from "react-redux"; +import RoomTypeComponent from "../../../../../components/app/sidebars/topology/room/RoomTypeComponent"; + +const mapStateToProps = state => { + return { + roomType: state.objects.room[state.interactionLevel.roomId].roomType + }; +}; + +const RoomNameContainer = connect(mapStateToProps)(RoomTypeComponent); + +export default RoomNameContainer; diff --git a/frontend/src/containers/app/timeline/PlayButtonContainer.js b/frontend/src/containers/app/timeline/PlayButtonContainer.js new file mode 100644 index 00000000..4e3c3d81 --- /dev/null +++ b/frontend/src/containers/app/timeline/PlayButtonContainer.js @@ -0,0 +1,27 @@ +import { connect } from "react-redux"; +import { + pauseSimulation, + playSimulation +} from "../../../actions/simulation/playback"; +import PlayButtonComponent from "../../../components/app/timeline/PlayButtonComponent"; + +const mapStateToProps = state => { + return { + isPlaying: state.isPlaying, + currentTick: state.currentTick, + lastSimulatedTick: state.lastSimulatedTick + }; +}; + +const mapDispatchToProps = dispatch => { + return { + onPlay: () => dispatch(playSimulation()), + onPause: () => dispatch(pauseSimulation()) + }; +}; + +const PlayButtonContainer = connect(mapStateToProps, mapDispatchToProps)( + PlayButtonComponent +); + +export default PlayButtonContainer; diff --git a/frontend/src/containers/app/timeline/TimelineContainer.js b/frontend/src/containers/app/timeline/TimelineContainer.js new file mode 100644 index 00000000..74d37d58 --- /dev/null +++ b/frontend/src/containers/app/timeline/TimelineContainer.js @@ -0,0 +1,41 @@ +import { connect } from "react-redux"; +import { pauseSimulation } from "../../../actions/simulation/playback"; +import { incrementTick } from "../../../actions/simulation/tick"; +import { setCurrentDatacenter } from "../../../actions/topology/building"; +import TimelineComponent from "../../../components/app/timeline/TimelineComponent"; + +const mapStateToProps = state => { + let sections = []; + if (state.currentExperimentId !== -1) { + const sectionIds = + state.objects.path[ + state.objects.experiment[state.currentExperimentId].pathId + ].sectionIds; + + if (sectionIds) { + sections = sectionIds.map(sectionId => state.objects.section[sectionId]); + } + } + + return { + isPlaying: state.isPlaying, + currentTick: state.currentTick, + lastSimulatedTick: state.lastSimulatedTick, + currentDatacenterId: state.currentDatacenterId, + sections + }; +}; + +const mapDispatchToProps = dispatch => { + return { + incrementTick: () => dispatch(incrementTick()), + pauseSimulation: () => dispatch(pauseSimulation()), + setCurrentDatacenter: id => dispatch(setCurrentDatacenter(id)) + }; +}; + +const TimelineContainer = connect(mapStateToProps, mapDispatchToProps)( + TimelineComponent +); + +export default TimelineContainer; diff --git a/frontend/src/containers/app/timeline/TimelineControlsContainer.js b/frontend/src/containers/app/timeline/TimelineControlsContainer.js new file mode 100644 index 00000000..ac851b2e --- /dev/null +++ b/frontend/src/containers/app/timeline/TimelineControlsContainer.js @@ -0,0 +1,36 @@ +import { connect } from "react-redux"; +import { goToTick } from "../../../actions/simulation/tick"; +import TimelineControlsComponent from "../../../components/app/timeline/TimelineControlsComponent"; + +const mapStateToProps = state => { + let sectionTicks = []; + if (state.currentExperimentId !== -1) { + const sectionIds = + state.objects.path[ + state.objects.experiment[state.currentExperimentId].pathId + ].sectionIds; + if (sectionIds) { + sectionTicks = sectionIds + .filter(sectionId => state.objects.section[sectionId].startTick !== 0) + .map(sectionId => state.objects.section[sectionId].startTick); + } + } + + return { + currentTick: state.currentTick, + lastSimulatedTick: state.lastSimulatedTick, + sectionTicks + }; +}; + +const mapDispatchToProps = dispatch => { + return { + goToTick: tick => dispatch(goToTick(tick)) + }; +}; + +const TimelineControlsContainer = connect(mapStateToProps, mapDispatchToProps)( + TimelineControlsComponent +); + +export default TimelineControlsContainer; diff --git a/frontend/src/containers/app/timeline/TimelineLabelsContainer.js b/frontend/src/containers/app/timeline/TimelineLabelsContainer.js new file mode 100644 index 00000000..9d7f268d --- /dev/null +++ b/frontend/src/containers/app/timeline/TimelineLabelsContainer.js @@ -0,0 +1,15 @@ +import { connect } from "react-redux"; +import TimelineLabelsComponent from "../../../components/app/timeline/TimelineLabelsComponent"; + +const mapStateToProps = state => { + return { + currentTick: state.currentTick, + lastSimulatedTick: state.lastSimulatedTick + }; +}; + +const TimelineLabelsContainer = connect(mapStateToProps)( + TimelineLabelsComponent +); + +export default TimelineLabelsContainer; diff --git a/frontend/src/containers/auth/Login.js b/frontend/src/containers/auth/Login.js new file mode 100644 index 00000000..15af8e62 --- /dev/null +++ b/frontend/src/containers/auth/Login.js @@ -0,0 +1,65 @@ +import PropTypes from "prop-types"; +import React from "react"; +import GoogleLogin from "react-google-login"; +import { connect } from "react-redux"; +import { logIn } from "../../actions/auth"; + +class LoginContainer extends React.Component { + static propTypes = { + visible: PropTypes.bool.isRequired, + onLogin: PropTypes.func.isRequired + }; + + onAuthResponse(response) { + this.props.onLogin({ + email: response.getBasicProfile().getEmail(), + givenName: response.getBasicProfile().getGivenName(), + familyName: response.getBasicProfile().getFamilyName(), + googleId: response.googleId, + authToken: response.getAuthResponse().id_token, + expiresAt: response.getAuthResponse().expires_at + }); + } + + onAuthFailure(error) { + console.error(error); + } + + render() { + if (!this.props.visible) { + return <span />; + } + + return ( + <GoogleLogin + clientId={process.env.REACT_APP_OAUTH_CLIENT_ID} + onSuccess={this.onAuthResponse.bind(this)} + onFailure={this.onAuthFailure.bind(this)} + render={renderProps => ( + <span onClick={renderProps.onClick} className="login btn btn-primary"> + <span className="fa fa-google" /> Login with Google + </span> + )} + /> + ); + } +} + +const mapStateToProps = (state, ownProps) => { + return { + visible: ownProps.visible + }; +}; + +const mapDispatchToProps = dispatch => { + return { + onLogin: payload => dispatch(logIn(payload)) + }; +}; + +const Login = connect( + mapStateToProps, + mapDispatchToProps +)(LoginContainer); + +export default Login; diff --git a/frontend/src/containers/auth/Logout.js b/frontend/src/containers/auth/Logout.js new file mode 100644 index 00000000..918932f6 --- /dev/null +++ b/frontend/src/containers/auth/Logout.js @@ -0,0 +1,13 @@ +import { connect } from "react-redux"; +import { logOut } from "../../actions/auth"; +import LogoutButton from "../../components/navigation/LogoutButton"; + +const mapDispatchToProps = dispatch => { + return { + onLogout: () => dispatch(logOut()) + }; +}; + +const Logout = connect(undefined, mapDispatchToProps)(LogoutButton); + +export default Logout; diff --git a/frontend/src/containers/auth/ProfileName.js b/frontend/src/containers/auth/ProfileName.js new file mode 100644 index 00000000..21941bd2 --- /dev/null +++ b/frontend/src/containers/auth/ProfileName.js @@ -0,0 +1,14 @@ +import React from "react"; +import { connect } from "react-redux"; + +const mapStateToProps = state => { + return { + text: state.auth.givenName + " " + state.auth.familyName + }; +}; + +const SpanElement = ({ text }) => <span>{text}</span>; + +const ProfileName = connect(mapStateToProps)(SpanElement); + +export default ProfileName; diff --git a/frontend/src/containers/experiments/ExperimentListContainer.js b/frontend/src/containers/experiments/ExperimentListContainer.js new file mode 100644 index 00000000..53bb1dad --- /dev/null +++ b/frontend/src/containers/experiments/ExperimentListContainer.js @@ -0,0 +1,28 @@ +import { connect } from "react-redux"; +import ExperimentListComponent from "../../components/experiments/ExperimentListComponent"; + +const mapStateToProps = state => { + if ( + state.currentSimulationId === -1 || + !("experimentIds" in state.objects.simulation[state.currentSimulationId]) + ) { + return { + loading: true, + experimentIds: [] + }; + } + + const experimentIds = + state.objects.simulation[state.currentSimulationId].experimentIds; + if (experimentIds) { + return { + experimentIds + }; + } +}; + +const ExperimentListContainer = connect(mapStateToProps)( + ExperimentListComponent +); + +export default ExperimentListContainer; diff --git a/frontend/src/containers/experiments/ExperimentRowContainer.js b/frontend/src/containers/experiments/ExperimentRowContainer.js new file mode 100644 index 00000000..96ebc3db --- /dev/null +++ b/frontend/src/containers/experiments/ExperimentRowContainer.js @@ -0,0 +1,30 @@ +import { connect } from "react-redux"; +import { deleteExperiment } from "../../actions/experiments"; +import ExperimentRowComponent from "../../components/experiments/ExperimentRowComponent"; + +const mapStateToProps = (state, ownProps) => { + const experiment = Object.assign( + {}, + state.objects.experiment[ownProps.experimentId] + ); + experiment.trace = state.objects.trace[experiment.traceId]; + experiment.scheduler = state.objects.scheduler[experiment.schedulerName]; + experiment.path = state.objects.path[experiment.pathId]; + + return { + experiment, + simulationId: state.currentSimulationId + }; +}; + +const mapDispatchToProps = dispatch => { + return { + onDelete: id => dispatch(deleteExperiment(id)) + }; +}; + +const ExperimentRowContainer = connect(mapStateToProps, mapDispatchToProps)( + ExperimentRowComponent +); + +export default ExperimentRowContainer; diff --git a/frontend/src/containers/experiments/NewExperimentButtonContainer.js b/frontend/src/containers/experiments/NewExperimentButtonContainer.js new file mode 100644 index 00000000..60eb92a6 --- /dev/null +++ b/frontend/src/containers/experiments/NewExperimentButtonContainer.js @@ -0,0 +1,15 @@ +import { connect } from "react-redux"; +import { openNewExperimentModal } from "../../actions/modals/experiments"; +import NewExperimentButtonComponent from "../../components/experiments/NewExperimentButtonComponent"; + +const mapDispatchToProps = dispatch => { + return { + onClick: () => dispatch(openNewExperimentModal()) + }; +}; + +const NewExperimentButtonContainer = connect(undefined, mapDispatchToProps)( + NewExperimentButtonComponent +); + +export default NewExperimentButtonContainer; diff --git a/frontend/src/containers/modals/DeleteMachineModal.js b/frontend/src/containers/modals/DeleteMachineModal.js new file mode 100644 index 00000000..eba37833 --- /dev/null +++ b/frontend/src/containers/modals/DeleteMachineModal.js @@ -0,0 +1,37 @@ +import React from "react"; +import { connect } from "react-redux"; +import { closeDeleteMachineModal } from "../../actions/modals/topology"; +import { deleteMachine } from "../../actions/topology/machine"; +import ConfirmationModal from "../../components/modals/ConfirmationModal"; + +const DeleteMachineModalComponent = ({ visible, callback }) => ( + <ConfirmationModal + title="Delete this machine" + message="Are you sure you want to delete this machine?" + show={visible} + callback={callback} + /> +); + +const mapStateToProps = state => { + return { + visible: state.modals.deleteMachineModalVisible + }; +}; + +const mapDispatchToProps = dispatch => { + return { + callback: isConfirmed => { + if (isConfirmed) { + dispatch(deleteMachine()); + } + dispatch(closeDeleteMachineModal()); + } + }; +}; + +const DeleteMachineModal = connect(mapStateToProps, mapDispatchToProps)( + DeleteMachineModalComponent +); + +export default DeleteMachineModal; diff --git a/frontend/src/containers/modals/DeleteProfileModal.js b/frontend/src/containers/modals/DeleteProfileModal.js new file mode 100644 index 00000000..674e9408 --- /dev/null +++ b/frontend/src/containers/modals/DeleteProfileModal.js @@ -0,0 +1,37 @@ +import React from "react"; +import { connect } from "react-redux"; +import { closeDeleteProfileModal } from "../../actions/modals/profile"; +import { deleteCurrentUser } from "../../actions/users"; +import ConfirmationModal from "../../components/modals/ConfirmationModal"; + +const DeleteProfileModalComponent = ({ visible, callback }) => ( + <ConfirmationModal + title="Delete my account" + message="Are you sure you want to delete your OpenDC account?" + show={visible} + callback={callback} + /> +); + +const mapStateToProps = state => { + return { + visible: state.modals.deleteProfileModalVisible + }; +}; + +const mapDispatchToProps = dispatch => { + return { + callback: isConfirmed => { + if (isConfirmed) { + dispatch(deleteCurrentUser()); + } + dispatch(closeDeleteProfileModal()); + } + }; +}; + +const DeleteProfileModal = connect(mapStateToProps, mapDispatchToProps)( + DeleteProfileModalComponent +); + +export default DeleteProfileModal; diff --git a/frontend/src/containers/modals/DeleteRackModal.js b/frontend/src/containers/modals/DeleteRackModal.js new file mode 100644 index 00000000..41bacb37 --- /dev/null +++ b/frontend/src/containers/modals/DeleteRackModal.js @@ -0,0 +1,37 @@ +import React from "react"; +import { connect } from "react-redux"; +import { closeDeleteRackModal } from "../../actions/modals/topology"; +import { deleteRack } from "../../actions/topology/rack"; +import ConfirmationModal from "../../components/modals/ConfirmationModal"; + +const DeleteRackModalComponent = ({ visible, callback }) => ( + <ConfirmationModal + title="Delete this rack" + message="Are you sure you want to delete this rack?" + show={visible} + callback={callback} + /> +); + +const mapStateToProps = state => { + return { + visible: state.modals.deleteRackModalVisible + }; +}; + +const mapDispatchToProps = dispatch => { + return { + callback: isConfirmed => { + if (isConfirmed) { + dispatch(deleteRack()); + } + dispatch(closeDeleteRackModal()); + } + }; +}; + +const DeleteRackModal = connect(mapStateToProps, mapDispatchToProps)( + DeleteRackModalComponent +); + +export default DeleteRackModal; diff --git a/frontend/src/containers/modals/DeleteRoomModal.js b/frontend/src/containers/modals/DeleteRoomModal.js new file mode 100644 index 00000000..339ff22c --- /dev/null +++ b/frontend/src/containers/modals/DeleteRoomModal.js @@ -0,0 +1,37 @@ +import React from "react"; +import { connect } from "react-redux"; +import { closeDeleteRoomModal } from "../../actions/modals/topology"; +import { deleteRoom } from "../../actions/topology/room"; +import ConfirmationModal from "../../components/modals/ConfirmationModal"; + +const DeleteRoomModalComponent = ({ visible, callback }) => ( + <ConfirmationModal + title="Delete this room" + message="Are you sure you want to delete this room?" + show={visible} + callback={callback} + /> +); + +const mapStateToProps = state => { + return { + visible: state.modals.deleteRoomModalVisible + }; +}; + +const mapDispatchToProps = dispatch => { + return { + callback: isConfirmed => { + if (isConfirmed) { + dispatch(deleteRoom()); + } + dispatch(closeDeleteRoomModal()); + } + }; +}; + +const DeleteRoomModal = connect(mapStateToProps, mapDispatchToProps)( + DeleteRoomModalComponent +); + +export default DeleteRoomModal; diff --git a/frontend/src/containers/modals/EditRackNameModal.js b/frontend/src/containers/modals/EditRackNameModal.js new file mode 100644 index 00000000..748e847b --- /dev/null +++ b/frontend/src/containers/modals/EditRackNameModal.js @@ -0,0 +1,44 @@ +import React from "react"; +import { connect } from "react-redux"; +import { closeEditRackNameModal } from "../../actions/modals/topology"; +import { editRackName } from "../../actions/topology/rack"; +import TextInputModal from "../../components/modals/TextInputModal"; + +const EditRackNameModalComponent = ({ visible, previousName, callback }) => ( + <TextInputModal + title="Edit rack name" + label="Rack name" + show={visible} + initialValue={previousName} + callback={callback} + /> +); + +const mapStateToProps = state => { + return { + visible: state.modals.editRackNameModalVisible, + previousName: + state.interactionLevel.mode === "RACK" + ? state.objects.rack[ + state.objects.tile[state.interactionLevel.tileId].objectId + ].name + : "" + }; +}; + +const mapDispatchToProps = dispatch => { + return { + callback: name => { + if (name) { + dispatch(editRackName(name)); + } + dispatch(closeEditRackNameModal()); + } + }; +}; + +const EditRackNameModal = connect(mapStateToProps, mapDispatchToProps)( + EditRackNameModalComponent +); + +export default EditRackNameModal; diff --git a/frontend/src/containers/modals/EditRoomNameModal.js b/frontend/src/containers/modals/EditRoomNameModal.js new file mode 100644 index 00000000..be6c547c --- /dev/null +++ b/frontend/src/containers/modals/EditRoomNameModal.js @@ -0,0 +1,42 @@ +import React from "react"; +import { connect } from "react-redux"; +import { closeEditRoomNameModal } from "../../actions/modals/topology"; +import { editRoomName } from "../../actions/topology/room"; +import TextInputModal from "../../components/modals/TextInputModal"; + +const EditRoomNameModalComponent = ({ visible, previousName, callback }) => ( + <TextInputModal + title="Edit room name" + label="Room name" + show={visible} + initialValue={previousName} + callback={callback} + /> +); + +const mapStateToProps = state => { + return { + visible: state.modals.editRoomNameModalVisible, + previousName: + state.interactionLevel.mode === "ROOM" + ? state.objects.room[state.interactionLevel.roomId].name + : "" + }; +}; + +const mapDispatchToProps = dispatch => { + return { + callback: name => { + if (name) { + dispatch(editRoomName(name)); + } + dispatch(closeEditRoomNameModal()); + } + }; +}; + +const EditRoomNameModal = connect(mapStateToProps, mapDispatchToProps)( + EditRoomNameModalComponent +); + +export default EditRoomNameModal; diff --git a/frontend/src/containers/modals/NewExperimentModal.js b/frontend/src/containers/modals/NewExperimentModal.js new file mode 100644 index 00000000..c703c39a --- /dev/null +++ b/frontend/src/containers/modals/NewExperimentModal.js @@ -0,0 +1,39 @@ +import { connect } from "react-redux"; +import { addExperiment } from "../../actions/experiments"; +import { closeNewExperimentModal } from "../../actions/modals/experiments"; +import NewExperimentModalComponent from "../../components/modals/custom-components/NewExperimentModalComponent"; + +const mapStateToProps = state => { + return { + show: state.modals.newExperimentModalVisible, + paths: Object.values(state.objects.path).filter( + path => path.simulationId === state.currentSimulationId + ), + traces: Object.values(state.objects.trace), + schedulers: Object.values(state.objects.scheduler) + }; +}; + +const mapDispatchToProps = dispatch => { + return { + callback: (name, pathId, traceId, schedulerName) => { + if (name) { + dispatch( + addExperiment({ + name, + pathId, + traceId, + schedulerName + }) + ); + } + dispatch(closeNewExperimentModal()); + } + }; +}; + +const NewExperimentModal = connect(mapStateToProps, mapDispatchToProps)( + NewExperimentModalComponent +); + +export default NewExperimentModal; diff --git a/frontend/src/containers/modals/NewSimulationModal.js b/frontend/src/containers/modals/NewSimulationModal.js new file mode 100644 index 00000000..80789cd2 --- /dev/null +++ b/frontend/src/containers/modals/NewSimulationModal.js @@ -0,0 +1,37 @@ +import React from "react"; +import { connect } from "react-redux"; +import { closeNewSimulationModal } from "../../actions/modals/simulations"; +import { addSimulation } from "../../actions/simulations"; +import TextInputModal from "../../components/modals/TextInputModal"; + +const NewSimulationModalComponent = ({ visible, callback }) => ( + <TextInputModal + title="New Simulation" + label="Simulation title" + show={visible} + callback={callback} + /> +); + +const mapStateToProps = state => { + return { + visible: state.modals.newSimulationModalVisible + }; +}; + +const mapDispatchToProps = dispatch => { + return { + callback: text => { + if (text) { + dispatch(addSimulation(text)); + } + dispatch(closeNewSimulationModal()); + } + }; +}; + +const NewSimulationModal = connect(mapStateToProps, mapDispatchToProps)( + NewSimulationModalComponent +); + +export default NewSimulationModal; diff --git a/frontend/src/containers/simulations/FilterLink.js b/frontend/src/containers/simulations/FilterLink.js new file mode 100644 index 00000000..2c5f4ed5 --- /dev/null +++ b/frontend/src/containers/simulations/FilterLink.js @@ -0,0 +1,19 @@ +import { connect } from "react-redux"; +import { setAuthVisibilityFilter } from "../../actions/simulations"; +import FilterButton from "../../components/simulations/FilterButton"; + +const mapStateToProps = (state, ownProps) => { + return { + active: state.simulationList.authVisibilityFilter === ownProps.filter + }; +}; + +const mapDispatchToProps = (dispatch, ownProps) => { + return { + onClick: () => dispatch(setAuthVisibilityFilter(ownProps.filter)) + }; +}; + +const FilterLink = connect(mapStateToProps, mapDispatchToProps)(FilterButton); + +export default FilterLink; diff --git a/frontend/src/containers/simulations/NewSimulationButtonContainer.js b/frontend/src/containers/simulations/NewSimulationButtonContainer.js new file mode 100644 index 00000000..3ea04d24 --- /dev/null +++ b/frontend/src/containers/simulations/NewSimulationButtonContainer.js @@ -0,0 +1,15 @@ +import { connect } from "react-redux"; +import { openNewSimulationModal } from "../../actions/modals/simulations"; +import NewSimulationButtonComponent from "../../components/simulations/NewSimulationButtonComponent"; + +const mapDispatchToProps = dispatch => { + return { + onClick: () => dispatch(openNewSimulationModal()) + }; +}; + +const NewSimulationButtonContainer = connect(undefined, mapDispatchToProps)( + NewSimulationButtonComponent +); + +export default NewSimulationButtonContainer; diff --git a/frontend/src/containers/simulations/SimulationActions.js b/frontend/src/containers/simulations/SimulationActions.js new file mode 100644 index 00000000..32243eff --- /dev/null +++ b/frontend/src/containers/simulations/SimulationActions.js @@ -0,0 +1,22 @@ +import { connect } from "react-redux"; +import { deleteSimulation } from "../../actions/simulations"; +import SimulationActionButtons from "../../components/simulations/SimulationActionButtons"; + +const mapStateToProps = (state, ownProps) => { + return { + simulationId: ownProps.simulationId + }; +}; + +const mapDispatchToProps = dispatch => { + return { + onViewUsers: id => {}, // TODO implement user viewing + onDelete: id => dispatch(deleteSimulation(id)) + }; +}; + +const SimulationActions = connect(mapStateToProps, mapDispatchToProps)( + SimulationActionButtons +); + +export default SimulationActions; diff --git a/frontend/src/containers/simulations/VisibleSimulationAuthList.js b/frontend/src/containers/simulations/VisibleSimulationAuthList.js new file mode 100644 index 00000000..ffc74d9e --- /dev/null +++ b/frontend/src/containers/simulations/VisibleSimulationAuthList.js @@ -0,0 +1,42 @@ +import { connect } from "react-redux"; +import SimulationList from "../../components/simulations/SimulationAuthList"; + +const getVisibleSimulationAuths = (simulationAuths, filter) => { + switch (filter) { + case "SHOW_ALL": + return simulationAuths; + case "SHOW_OWN": + return simulationAuths.filter( + simulationAuth => simulationAuth.authorizationLevel === "OWN" + ); + case "SHOW_SHARED": + return simulationAuths.filter( + simulationAuth => simulationAuth.authorizationLevel !== "OWN" + ); + default: + return simulationAuths; + } +}; + +const mapStateToProps = state => { + const denormalizedAuthorizations = state.simulationList.authorizationsOfCurrentUser.map( + authorizationIds => { + const authorization = state.objects.authorization[authorizationIds]; + authorization.user = state.objects.user[authorization.userId]; + authorization.simulation = + state.objects.simulation[authorization.simulationId]; + return authorization; + } + ); + + return { + authorizations: getVisibleSimulationAuths( + denormalizedAuthorizations, + state.simulationList.authVisibilityFilter + ) + }; +}; + +const VisibleSimulationAuthList = connect(mapStateToProps)(SimulationList); + +export default VisibleSimulationAuthList; diff --git a/frontend/src/index.js b/frontend/src/index.js new file mode 100644 index 00000000..dad662c4 --- /dev/null +++ b/frontend/src/index.js @@ -0,0 +1,21 @@ +import React from "react"; +import ReactDOM from "react-dom"; +import { Provider } from "react-redux"; +import { setupSocketConnection } from "./api/socket"; +import "./index.css"; +import registerServiceWorker from "./registerServiceWorker"; +import Routes from "./routes"; +import configureStore from "./store/configure-store"; + +setupSocketConnection(() => { + const store = configureStore(); + + ReactDOM.render( + <Provider store={store}> + <Routes /> + </Provider>, + document.getElementById("root") + ); + + registerServiceWorker(); +}); diff --git a/frontend/src/index.sass b/frontend/src/index.sass new file mode 100644 index 00000000..248987ab --- /dev/null +++ b/frontend/src/index.sass @@ -0,0 +1,39 @@ +@import ./style-globals/_mixins.sass + +html, body, #root + margin: 0 + padding: 0 + width: 100% + height: 100% + + font-family: Roboto, Helvetica, Verdana, sans-serif + background: #eee + +.full-height + position: relative + height: 100% + +.page-container + padding-top: 60px + +.text-page-container + padding-top: 80px + display: flex + flex-flow: column + +.vertically-expanding-container + flex: 1 1 auto + overflow-y: auto + +.bottom-btn-container + flex: 0 1 auto + padding: 20px 0 + +.btn, .list-group-item-action + +clickable + +.btn-circle + +border-radius(50%) + +a, a:hover + text-decoration: none diff --git a/frontend/src/pages/App.js b/frontend/src/pages/App.js new file mode 100644 index 00000000..ad201e7d --- /dev/null +++ b/frontend/src/pages/App.js @@ -0,0 +1,125 @@ +import PropTypes from "prop-types"; +import React from "react"; +import DocumentTitle from "react-document-title"; +import { connect } from "react-redux"; +import { ShortcutManager } from "react-shortcuts"; +import { openExperimentSucceeded } from "../actions/experiments"; +import { openSimulationSucceeded } from "../actions/simulations"; +import { resetCurrentDatacenter } from "../actions/topology/building"; +import ToolPanelComponent from "../components/app/map/controls/ToolPanelComponent"; +import LoadingScreen from "../components/app/map/LoadingScreen"; +import SimulationSidebarComponent from "../components/app/sidebars/simulation/SimulationSidebarComponent"; +import AppNavbar from "../components/navigation/AppNavbar"; +import ScaleIndicatorContainer from "../containers/app/map/controls/ScaleIndicatorContainer"; +import MapStage from "../containers/app/map/MapStage"; +import TopologySidebar from "../containers/app/sidebars/topology/TopologySidebar"; +import TimelineContainer from "../containers/app/timeline/TimelineContainer"; +import DeleteMachineModal from "../containers/modals/DeleteMachineModal"; +import DeleteRackModal from "../containers/modals/DeleteRackModal"; +import DeleteRoomModal from "../containers/modals/DeleteRoomModal"; +import EditRackNameModal from "../containers/modals/EditRackNameModal"; +import EditRoomNameModal from "../containers/modals/EditRoomNameModal"; +import KeymapConfiguration from "../shortcuts/keymap"; + +const shortcutManager = new ShortcutManager(KeymapConfiguration); + +class AppComponent extends React.Component { + static propTypes = { + simulationId: PropTypes.number.isRequired, + inSimulation: PropTypes.bool, + experimentId: PropTypes.number, + simulationName: PropTypes.string + }; + static childContextTypes = { + shortcuts: PropTypes.object.isRequired + }; + + componentDidMount() { + this.props.resetCurrentDatacenter(); + if (this.props.inSimulation) { + this.props.openExperimentSucceeded( + this.props.simulationId, + this.props.experimentId + ); + return; + } + this.props.openSimulationSucceeded(this.props.simulationId); + } + + getChildContext() { + return { + shortcuts: shortcutManager + }; + } + + render() { + return ( + <DocumentTitle + title={ + this.props.simulationName + ? this.props.simulationName + " - OpenDC" + : "Simulation - OpenDC" + } + > + <div className="page-container full-height"> + <AppNavbar + simulationId={this.props.simulationId} + inSimulation={true} + fullWidth={true} + /> + {this.props.datacenterIsLoading ? ( + <div className="full-height d-flex align-items-center justify-content-center"> + <LoadingScreen /> + </div> + ) : ( + <div className="full-height"> + <MapStage /> + <ScaleIndicatorContainer /> + <ToolPanelComponent /> + <TopologySidebar /> + {this.props.inSimulation ? <TimelineContainer /> : undefined} + {this.props.inSimulation ? ( + <SimulationSidebarComponent /> + ) : ( + undefined + )} + </div> + )} + <EditRoomNameModal /> + <DeleteRoomModal /> + <EditRackNameModal /> + <DeleteRackModal /> + <DeleteMachineModal /> + </div> + </DocumentTitle> + ); + } +} + +const mapStateToProps = state => { + let simulationName = undefined; + if ( + state.currentSimulationId !== -1 && + state.objects.simulation[state.currentSimulationId] + ) { + simulationName = state.objects.simulation[state.currentSimulationId].name; + } + + return { + datacenterIsLoading: state.currentDatacenterId === -1, + simulationName + }; +}; + +const mapDispatchToProps = dispatch => { + return { + resetCurrentDatacenter: () => dispatch(resetCurrentDatacenter()), + openSimulationSucceeded: id => dispatch(openSimulationSucceeded(id)), + openExperimentSucceeded: (simulationId, experimentId) => + dispatch(openExperimentSucceeded(simulationId, experimentId)) + }; +}; + +const App = connect(mapStateToProps, mapDispatchToProps)(AppComponent); + +export default App; diff --git a/frontend/src/pages/Experiments.js b/frontend/src/pages/Experiments.js new file mode 100644 index 00000000..2f73cd7e --- /dev/null +++ b/frontend/src/pages/Experiments.js @@ -0,0 +1,75 @@ +import PropTypes from "prop-types"; +import React from "react"; +import DocumentTitle from "react-document-title"; +import { connect } from "react-redux"; +import { fetchExperimentsOfSimulation } from "../actions/experiments"; +import { openSimulationSucceeded } from "../actions/simulations"; +import AppNavbar from "../components/navigation/AppNavbar"; +import ExperimentListContainer from "../containers/experiments/ExperimentListContainer"; +import NewExperimentButtonContainer from "../containers/experiments/NewExperimentButtonContainer"; +import NewExperimentModal from "../containers/modals/NewExperimentModal"; + +class ExperimentsComponent extends React.Component { + static propTypes = { + simulationId: PropTypes.number.isRequired, + simulationName: PropTypes.string + }; + + componentDidMount() { + this.props.storeSimulationId(this.props.simulationId); + this.props.fetchExperimentsOfSimulation(this.props.simulationId); + } + + render() { + return ( + <DocumentTitle + title={ + this.props.simulationName + ? "Experiments - " + this.props.simulationName + " - OpenDC" + : "Experiments - OpenDC" + } + > + <div className="full-height"> + <AppNavbar + simulationId={this.props.simulationId} + inSimulation={true} + fullWidth={true} + /> + <div className="container text-page-container full-height"> + <ExperimentListContainer /> + <NewExperimentButtonContainer /> + </div> + <NewExperimentModal /> + </div> + </DocumentTitle> + ); + } +} + +const mapStateToProps = state => { + let simulationName = undefined; + if ( + state.currentSimulationId !== -1 && + state.objects.simulation[state.currentSimulationId] + ) { + simulationName = state.objects.simulation[state.currentSimulationId].name; + } + + return { + simulationName + }; +}; + +const mapDispatchToProps = dispatch => { + return { + storeSimulationId: id => dispatch(openSimulationSucceeded(id)), + fetchExperimentsOfSimulation: id => + dispatch(fetchExperimentsOfSimulation(id)) + }; +}; + +const Experiments = connect(mapStateToProps, mapDispatchToProps)( + ExperimentsComponent +); + +export default Experiments; diff --git a/frontend/src/pages/Home.js b/frontend/src/pages/Home.js new file mode 100644 index 00000000..f6479722 --- /dev/null +++ b/frontend/src/pages/Home.js @@ -0,0 +1,62 @@ +import React from "react"; +import DocumentTitle from "react-document-title"; +import ContactSection from "../components/home/ContactSection"; +import IntroSection from "../components/home/IntroSection"; +import JumbotronHeader from "../components/home/JumbotronHeader"; +import ModelingSection from "../components/home/ModelingSection"; +import SimulationSection from "../components/home/SimulationSection"; +import StakeholderSection from "../components/home/StakeholderSection"; +import TeamSection from "../components/home/TeamSection"; +import TechnologiesSection from "../components/home/TechnologiesSection"; +import HomeNavbar from "../components/navigation/HomeNavbar"; +import jQuery from "../util/jquery"; +import "./Home.css"; + +class Home extends React.Component { + state = { + scrollSpySetup: false + }; + + componentDidMount() { + const scrollOffset = 60; + jQuery("#navbar") + .find("li a") + .click(function(e) { + if (jQuery(e.target).parents(".auth-links").length > 0) { + return; + } + e.preventDefault(); + jQuery(jQuery(this).attr("href"))[0].scrollIntoView(); + window.scrollBy(0, -scrollOffset); + }); + + if (!this.state.scrollSpySetup) { + jQuery("body").scrollspy({ + target: "#navbar", + offset: scrollOffset + }); + this.setState({ scrollSpySetup: true }); + } + } + + render() { + return ( + <div> + <HomeNavbar /> + <div className="body-wrapper page-container"> + <JumbotronHeader /> + <IntroSection /> + <StakeholderSection /> + <ModelingSection /> + <SimulationSection /> + <TechnologiesSection /> + <TeamSection /> + <ContactSection /> + <DocumentTitle title="OpenDC" /> + </div> + </div> + ); + } +} + +export default Home; diff --git a/frontend/src/pages/Home.sass b/frontend/src/pages/Home.sass new file mode 100644 index 00000000..9c812db2 --- /dev/null +++ b/frontend/src/pages/Home.sass @@ -0,0 +1,9 @@ +.body-wrapper + position: relative + overflow-y: hidden + +.intro-section, .modeling-section, .technologies-section + background-color: #fff + +.stakeholder-section, .simulation-section, .team-section + background-color: #f2f2f2 diff --git a/frontend/src/pages/NotFound.js b/frontend/src/pages/NotFound.js new file mode 100644 index 00000000..b344e923 --- /dev/null +++ b/frontend/src/pages/NotFound.js @@ -0,0 +1,14 @@ +import React from "react"; +import DocumentTitle from "react-document-title"; +import TerminalWindow from "../components/not-found/TerminalWindow"; +import "./NotFound.css"; + +const NotFound = () => ( + <DocumentTitle title="Page Not Found - OpenDC"> + <div className="not-found-backdrop"> + <TerminalWindow /> + </div> + </DocumentTitle> +); + +export default NotFound; diff --git a/frontend/src/pages/NotFound.sass b/frontend/src/pages/NotFound.sass new file mode 100644 index 00000000..9457da01 --- /dev/null +++ b/frontend/src/pages/NotFound.sass @@ -0,0 +1,11 @@ +.not-found-backdrop + position: absolute + left: 0 + top: 0 + + margin: 0 + padding: 0 + width: 100% + height: 100% + + background-image: linear-gradient(135deg, #00678a, #008fbf, #00A6D6) diff --git a/frontend/src/pages/Profile.js b/frontend/src/pages/Profile.js new file mode 100644 index 00000000..106ec97e --- /dev/null +++ b/frontend/src/pages/Profile.js @@ -0,0 +1,40 @@ +import React from "react"; +import DocumentTitle from "react-document-title"; +import { connect } from "react-redux"; +import { openDeleteProfileModal } from "../actions/modals/profile"; +import AppNavbar from "../components/navigation/AppNavbar"; +import DeleteProfileModal from "../containers/modals/DeleteProfileModal"; + +const ProfileContainer = ({ onDelete }) => ( + <DocumentTitle title="My Profile - OpenDC"> + <div className="full-height"> + <AppNavbar inSimulation={false} fullWidth={false} /> + <div className="container text-page-container full-height"> + <button + className="btn btn-danger mb-2 ml-auto mr-auto" + style={{ maxWidth: 300 }} + onClick={onDelete} + > + Delete my account on OpenDC + </button> + <p className="text-muted text-center"> + This does not delete your Google account, but simply disconnects it + from the OpenDC platform and deletes any simulation info that is + associated with you (simulations you own and any authorizations you + may have on other projects). + </p> + </div> + <DeleteProfileModal /> + </div> + </DocumentTitle> +); + +const mapDispatchToProps = dispatch => { + return { + onDelete: () => dispatch(openDeleteProfileModal()) + }; +}; + +const Profile = connect(undefined, mapDispatchToProps)(ProfileContainer); + +export default Profile; diff --git a/frontend/src/pages/Simulations.js b/frontend/src/pages/Simulations.js new file mode 100644 index 00000000..ecff8fe6 --- /dev/null +++ b/frontend/src/pages/Simulations.js @@ -0,0 +1,46 @@ +import React from "react"; +import DocumentTitle from "react-document-title"; +import { connect } from "react-redux"; +import { openNewSimulationModal } from "../actions/modals/simulations"; +import { fetchAuthorizationsOfCurrentUser } from "../actions/users"; +import AppNavbar from "../components/navigation/AppNavbar"; +import SimulationFilterPanel from "../components/simulations/FilterPanel"; +import NewSimulationModal from "../containers/modals/NewSimulationModal"; +import NewSimulationButtonContainer from "../containers/simulations/NewSimulationButtonContainer"; +import VisibleSimulationList from "../containers/simulations/VisibleSimulationAuthList"; + +class SimulationsContainer extends React.Component { + componentDidMount() { + this.props.fetchAuthorizationsOfCurrentUser(); + } + + render() { + return ( + <DocumentTitle title="My Simulations - OpenDC"> + <div className="full-height"> + <AppNavbar inSimulation={false} fullWidth={false} /> + <div className="container text-page-container full-height"> + <SimulationFilterPanel /> + <VisibleSimulationList /> + <NewSimulationButtonContainer /> + </div> + <NewSimulationModal /> + </div> + </DocumentTitle> + ); + } +} + +const mapDispatchToProps = dispatch => { + return { + fetchAuthorizationsOfCurrentUser: () => + dispatch(fetchAuthorizationsOfCurrentUser()), + openNewSimulationModal: () => dispatch(openNewSimulationModal()) + }; +}; + +const Simulations = connect(undefined, mapDispatchToProps)( + SimulationsContainer +); + +export default Simulations; diff --git a/frontend/src/reducers/auth.js b/frontend/src/reducers/auth.js new file mode 100644 index 00000000..635929d4 --- /dev/null +++ b/frontend/src/reducers/auth.js @@ -0,0 +1,12 @@ +import { LOG_IN_SUCCEEDED, LOG_OUT } from "../actions/auth"; + +export function auth(state = {}, action) { + switch (action.type) { + case LOG_IN_SUCCEEDED: + return action.payload; + case LOG_OUT: + return {}; + default: + return state; + } +} diff --git a/frontend/src/reducers/construction-mode.js b/frontend/src/reducers/construction-mode.js new file mode 100644 index 00000000..b5e6e781 --- /dev/null +++ b/frontend/src/reducers/construction-mode.js @@ -0,0 +1,50 @@ +import { combineReducers } from "redux"; +import { OPEN_EXPERIMENT_SUCCEEDED } from "../actions/experiments"; +import { GO_DOWN_ONE_INTERACTION_LEVEL } from "../actions/interaction-level"; +import { + CANCEL_NEW_ROOM_CONSTRUCTION_SUCCEEDED, + FINISH_NEW_ROOM_CONSTRUCTION, + FINISH_ROOM_EDIT, + START_NEW_ROOM_CONSTRUCTION_SUCCEEDED, + START_ROOM_EDIT +} from "../actions/topology/building"; +import { + DELETE_ROOM, + START_RACK_CONSTRUCTION, + STOP_RACK_CONSTRUCTION +} from "../actions/topology/room"; + +export function currentRoomInConstruction(state = -1, action) { + switch (action.type) { + case START_NEW_ROOM_CONSTRUCTION_SUCCEEDED: + return action.roomId; + case START_ROOM_EDIT: + return action.roomId; + case CANCEL_NEW_ROOM_CONSTRUCTION_SUCCEEDED: + case FINISH_NEW_ROOM_CONSTRUCTION: + case OPEN_EXPERIMENT_SUCCEEDED: + case FINISH_ROOM_EDIT: + case DELETE_ROOM: + return -1; + default: + return state; + } +} + +export function inRackConstructionMode(state = false, action) { + switch (action.type) { + case START_RACK_CONSTRUCTION: + return true; + case STOP_RACK_CONSTRUCTION: + case OPEN_EXPERIMENT_SUCCEEDED: + case GO_DOWN_ONE_INTERACTION_LEVEL: + return false; + default: + return state; + } +} + +export const construction = combineReducers({ + currentRoomInConstruction, + inRackConstructionMode +}); diff --git a/frontend/src/reducers/current-ids.js b/frontend/src/reducers/current-ids.js new file mode 100644 index 00000000..4e16630d --- /dev/null +++ b/frontend/src/reducers/current-ids.js @@ -0,0 +1,28 @@ +import { OPEN_EXPERIMENT_SUCCEEDED } from "../actions/experiments"; +import { OPEN_SIMULATION_SUCCEEDED } from "../actions/simulations"; +import { + RESET_CURRENT_DATACENTER, + SET_CURRENT_DATACENTER +} from "../actions/topology/building"; + +export function currentDatacenterId(state = -1, action) { + switch (action.type) { + case SET_CURRENT_DATACENTER: + return action.datacenterId; + case RESET_CURRENT_DATACENTER: + return -1; + default: + return state; + } +} + +export function currentSimulationId(state = -1, action) { + switch (action.type) { + case OPEN_SIMULATION_SUCCEEDED: + return action.id; + case OPEN_EXPERIMENT_SUCCEEDED: + return action.simulationId; + default: + return state; + } +} diff --git a/frontend/src/reducers/index.js b/frontend/src/reducers/index.js new file mode 100644 index 00000000..6f4d0c94 --- /dev/null +++ b/frontend/src/reducers/index.js @@ -0,0 +1,37 @@ +import { combineReducers } from "redux"; +import { auth } from "./auth"; +import { construction } from "./construction-mode"; +import { currentDatacenterId, currentSimulationId } from "./current-ids"; +import { interactionLevel } from "./interaction-level"; +import { map } from "./map"; +import { modals } from "./modals"; +import { objects } from "./objects"; +import { simulationList } from "./simulation-list"; +import { + currentExperimentId, + currentTick, + isPlaying, + lastSimulatedTick, + loadMetric +} from "./simulation-mode"; +import { states } from "./states"; + +const rootReducer = combineReducers({ + objects, + states, + modals, + simulationList, + construction, + map, + currentSimulationId, + currentDatacenterId, + currentExperimentId, + currentTick, + lastSimulatedTick, + loadMetric, + isPlaying, + interactionLevel, + auth +}); + +export default rootReducer; diff --git a/frontend/src/reducers/interaction-level.js b/frontend/src/reducers/interaction-level.js new file mode 100644 index 00000000..581906c5 --- /dev/null +++ b/frontend/src/reducers/interaction-level.js @@ -0,0 +1,59 @@ +import { OPEN_EXPERIMENT_SUCCEEDED } from "../actions/experiments"; +import { + GO_DOWN_ONE_INTERACTION_LEVEL, + GO_FROM_BUILDING_TO_ROOM, + GO_FROM_RACK_TO_MACHINE, + GO_FROM_ROOM_TO_RACK +} from "../actions/interaction-level"; +import { OPEN_SIMULATION_SUCCEEDED } from "../actions/simulations"; +import { SET_CURRENT_DATACENTER } from "../actions/topology/building"; + +export function interactionLevel(state = { mode: "BUILDING" }, action) { + switch (action.type) { + case OPEN_EXPERIMENT_SUCCEEDED: + case OPEN_SIMULATION_SUCCEEDED: + case SET_CURRENT_DATACENTER: + return { + mode: "BUILDING" + }; + case GO_FROM_BUILDING_TO_ROOM: + return { + mode: "ROOM", + roomId: action.roomId + }; + case GO_FROM_ROOM_TO_RACK: + return { + mode: "RACK", + roomId: state.roomId, + tileId: action.tileId + }; + case GO_FROM_RACK_TO_MACHINE: + return { + mode: "MACHINE", + roomId: state.roomId, + tileId: state.tileId, + position: action.position + }; + case GO_DOWN_ONE_INTERACTION_LEVEL: + if (state.mode === "ROOM") { + return { + mode: "BUILDING" + }; + } else if (state.mode === "RACK") { + return { + mode: "ROOM", + roomId: state.roomId + }; + } else if (state.mode === "MACHINE") { + return { + mode: "RACK", + roomId: state.roomId, + tileId: state.tileId + }; + } else { + return state; + } + default: + return state; + } +} diff --git a/frontend/src/reducers/map.js b/frontend/src/reducers/map.js new file mode 100644 index 00000000..b75dc051 --- /dev/null +++ b/frontend/src/reducers/map.js @@ -0,0 +1,39 @@ +import { combineReducers } from "redux"; +import { + SET_MAP_DIMENSIONS, + SET_MAP_POSITION, + SET_MAP_SCALE +} from "../actions/map"; + +export function position(state = { x: 0, y: 0 }, action) { + switch (action.type) { + case SET_MAP_POSITION: + return { x: action.x, y: action.y }; + default: + return state; + } +} + +export function dimensions(state = { width: 600, height: 400 }, action) { + switch (action.type) { + case SET_MAP_DIMENSIONS: + return { width: action.width, height: action.height }; + default: + return state; + } +} + +export function scale(state = 1, action) { + switch (action.type) { + case SET_MAP_SCALE: + return action.scale; + default: + return state; + } +} + +export const map = combineReducers({ + position, + dimensions, + scale +}); diff --git a/frontend/src/reducers/modals.js b/frontend/src/reducers/modals.js new file mode 100644 index 00000000..78527feb --- /dev/null +++ b/frontend/src/reducers/modals.js @@ -0,0 +1,75 @@ +import { combineReducers } from "redux"; +import { OPEN_EXPERIMENT_SUCCEEDED } from "../actions/experiments"; +import { + CLOSE_NEW_EXPERIMENT_MODAL, + OPEN_NEW_EXPERIMENT_MODAL +} from "../actions/modals/experiments"; +import { + CLOSE_DELETE_PROFILE_MODAL, + OPEN_DELETE_PROFILE_MODAL +} from "../actions/modals/profile"; +import { + CLOSE_NEW_SIMULATION_MODAL, + OPEN_NEW_SIMULATION_MODAL +} from "../actions/modals/simulations"; +import { + CLOSE_DELETE_MACHINE_MODAL, + CLOSE_DELETE_RACK_MODAL, + CLOSE_DELETE_ROOM_MODAL, + CLOSE_EDIT_RACK_NAME_MODAL, + CLOSE_EDIT_ROOM_NAME_MODAL, + OPEN_DELETE_MACHINE_MODAL, + OPEN_DELETE_RACK_MODAL, + OPEN_DELETE_ROOM_MODAL, + OPEN_EDIT_RACK_NAME_MODAL, + OPEN_EDIT_ROOM_NAME_MODAL +} from "../actions/modals/topology"; + +function modal(openAction, closeAction) { + return function(state = false, action) { + switch (action.type) { + case openAction: + return true; + case closeAction: + case OPEN_EXPERIMENT_SUCCEEDED: + return false; + default: + return state; + } + }; +} + +export const modals = combineReducers({ + newSimulationModalVisible: modal( + OPEN_NEW_SIMULATION_MODAL, + CLOSE_NEW_SIMULATION_MODAL + ), + deleteProfileModalVisible: modal( + OPEN_DELETE_PROFILE_MODAL, + CLOSE_DELETE_PROFILE_MODAL + ), + editRoomNameModalVisible: modal( + OPEN_EDIT_ROOM_NAME_MODAL, + CLOSE_EDIT_ROOM_NAME_MODAL + ), + deleteRoomModalVisible: modal( + OPEN_DELETE_ROOM_MODAL, + CLOSE_DELETE_ROOM_MODAL + ), + editRackNameModalVisible: modal( + OPEN_EDIT_RACK_NAME_MODAL, + CLOSE_EDIT_RACK_NAME_MODAL + ), + deleteRackModalVisible: modal( + OPEN_DELETE_RACK_MODAL, + CLOSE_DELETE_RACK_MODAL + ), + deleteMachineModalVisible: modal( + OPEN_DELETE_MACHINE_MODAL, + CLOSE_DELETE_MACHINE_MODAL + ), + newExperimentModalVisible: modal( + OPEN_NEW_EXPERIMENT_MODAL, + CLOSE_NEW_EXPERIMENT_MODAL + ) +}); diff --git a/frontend/src/reducers/objects.js b/frontend/src/reducers/objects.js new file mode 100644 index 00000000..99d91092 --- /dev/null +++ b/frontend/src/reducers/objects.js @@ -0,0 +1,80 @@ +import { combineReducers } from "redux"; +import { + ADD_ID_TO_STORE_OBJECT_LIST_PROP, + ADD_PROP_TO_STORE_OBJECT, + ADD_TO_STORE, + REMOVE_ID_FROM_STORE_OBJECT_LIST_PROP +} from "../actions/objects"; + +export const objects = combineReducers({ + simulation: object("simulation"), + user: object("user"), + authorization: objectWithId("authorization", object => [ + object.userId, + object.simulationId + ]), + failureModel: object("failureModel"), + cpu: object("cpu"), + gpu: object("gpu"), + memory: object("memory"), + storage: object("storage"), + machine: object("machine"), + rack: object("rack"), + coolingItem: object("coolingItem"), + psu: object("psu"), + tile: object("tile"), + room: object("room"), + datacenter: object("datacenter"), + section: object("section"), + path: object("path"), + task: object("task"), + job: object("job"), + trace: object("trace"), + scheduler: object("scheduler"), + experiment: object("experiment") +}); + +function object(type) { + return objectWithId(type, object => object.id); +} + +function objectWithId(type, getId) { + return (state = {}, action) => { + if (action.objectType !== type) { + return state; + } + + if (action.type === ADD_TO_STORE) { + return Object.assign({}, state, { + [getId(action.object)]: action.object + }); + } else if (action.type === ADD_PROP_TO_STORE_OBJECT) { + return Object.assign({}, state, { + [action.objectId]: Object.assign( + {}, + state[action.objectId], + action.propObject + ) + }); + } else if (action.type === ADD_ID_TO_STORE_OBJECT_LIST_PROP) { + return Object.assign({}, state, { + [action.objectId]: Object.assign({}, state[action.objectId], { + [action.propName]: [ + ...state[action.objectId][action.propName], + action.id + ] + }) + }); + } else if (action.type === REMOVE_ID_FROM_STORE_OBJECT_LIST_PROP) { + return Object.assign({}, state, { + [action.objectId]: Object.assign({}, state[action.objectId], { + [action.propName]: state[action.objectId][action.propName].filter( + id => id !== action.id + ) + }) + }); + } + + return state; + }; +} diff --git a/frontend/src/reducers/simulation-list.js b/frontend/src/reducers/simulation-list.js new file mode 100644 index 00000000..9afa3586 --- /dev/null +++ b/frontend/src/reducers/simulation-list.js @@ -0,0 +1,34 @@ +import { combineReducers } from "redux"; +import { + ADD_SIMULATION_SUCCEEDED, + DELETE_SIMULATION_SUCCEEDED, + SET_AUTH_VISIBILITY_FILTER +} from "../actions/simulations"; +import { FETCH_AUTHORIZATIONS_OF_CURRENT_USER_SUCCEEDED } from "../actions/users"; + +export function authorizationsOfCurrentUser(state = [], action) { + switch (action.type) { + case FETCH_AUTHORIZATIONS_OF_CURRENT_USER_SUCCEEDED: + return action.authorizationsOfCurrentUser; + case ADD_SIMULATION_SUCCEEDED: + return [...state, action.authorization]; + case DELETE_SIMULATION_SUCCEEDED: + return state.filter(authorization => authorization[1] !== action.id); + default: + return state; + } +} + +export function authVisibilityFilter(state = "SHOW_ALL", action) { + switch (action.type) { + case SET_AUTH_VISIBILITY_FILTER: + return action.filter; + default: + return state; + } +} + +export const simulationList = combineReducers({ + authorizationsOfCurrentUser, + authVisibilityFilter +}); diff --git a/frontend/src/reducers/simulation-mode.js b/frontend/src/reducers/simulation-mode.js new file mode 100644 index 00000000..02041468 --- /dev/null +++ b/frontend/src/reducers/simulation-mode.js @@ -0,0 +1,61 @@ +import { OPEN_EXPERIMENT_SUCCEEDED } from "../actions/experiments"; +import { CHANGE_LOAD_METRIC } from "../actions/simulation/load-metric"; +import { SET_PLAYING } from "../actions/simulation/playback"; +import { + GO_TO_TICK, + SET_LAST_SIMULATED_TICK +} from "../actions/simulation/tick"; +import { OPEN_SIMULATION_SUCCEEDED } from "../actions/simulations"; + +export function currentExperimentId(state = -1, action) { + switch (action.type) { + case OPEN_EXPERIMENT_SUCCEEDED: + return action.experimentId; + case OPEN_SIMULATION_SUCCEEDED: + return -1; + default: + return state; + } +} + +export function currentTick(state = 0, action) { + switch (action.type) { + case GO_TO_TICK: + return action.tick; + case OPEN_EXPERIMENT_SUCCEEDED: + return 0; + default: + return state; + } +} + +export function loadMetric(state = "LOAD", action) { + switch (action.type) { + case CHANGE_LOAD_METRIC: + return action.metric; + default: + return state; + } +} + +export function isPlaying(state = false, action) { + switch (action.type) { + case SET_PLAYING: + return action.playing; + case OPEN_EXPERIMENT_SUCCEEDED: + return false; + default: + return state; + } +} + +export function lastSimulatedTick(state = -1, action) { + switch (action.type) { + case SET_LAST_SIMULATED_TICK: + return action.tick; + case OPEN_EXPERIMENT_SUCCEEDED: + return -1; + default: + return state; + } +} diff --git a/frontend/src/reducers/states.js b/frontend/src/reducers/states.js new file mode 100644 index 00000000..793f7b7d --- /dev/null +++ b/frontend/src/reducers/states.js @@ -0,0 +1,33 @@ +import { combineReducers } from "redux"; +import { ADD_BATCH_TO_STATES } from "../actions/states"; + +export const states = combineReducers({ + task: objectStates("task"), + room: objectStates("room"), + rack: objectStates("rack"), + machine: objectStates("machine") +}); + +function objectStates(type) { + return (state = {}, action) => { + if (action.objectType !== type) { + return state; + } + + if (action.type === ADD_BATCH_TO_STATES) { + const batch = {}; + for (let i in action.objects) { + batch[action.objects[i].tick] = Object.assign( + {}, + state[action.objects[i].tick], + batch[action.objects[i].tick], + { [action.objects[i][action.objectType + "Id"]]: action.objects[i] } + ); + } + + return Object.assign({}, state, batch); + } + + return state; + }; +} diff --git a/frontend/src/registerServiceWorker.js b/frontend/src/registerServiceWorker.js new file mode 100644 index 00000000..0fe89a23 --- /dev/null +++ b/frontend/src/registerServiceWorker.js @@ -0,0 +1,108 @@ +// In production, we register a service worker to serve assets from local cache. + +// This lets the app load faster on subsequent visits in production, and gives +// it offline capabilities. However, it also means that developers (and users) +// will only see deployed updates on the "N+1" visit to a page, since previously +// cached resources are updated in the background. + +// To learn more about the benefits of this model, read https://goo.gl/KwvDNy. +// This link also includes instructions on opting out of this behavior. + +const isLocalhost = Boolean( + window.location.hostname === "localhost" || + // [::1] is the IPv6 localhost address. + window.location.hostname === "[::1]" || + // 127.0.0.1/8 is considered localhost for IPv4. + window.location.hostname.match( + /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ + ) +); + +export default function register() { + if (process.env.NODE_ENV === "production" && "serviceWorker" in navigator) { + // The URL constructor is available in all browsers that support SW. + const publicUrl = new URL(process.env.PUBLIC_URL, window.location); + if (publicUrl.origin !== window.location.origin) { + // Our service worker won't work if PUBLIC_URL is on a different origin + // from what our page is served on. This might happen if a CDN is used to + // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374 + return; + } + + window.addEventListener("load", () => { + const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; + + if (!isLocalhost) { + // Is not local host. Just register service worker + registerValidSW(swUrl); + } else { + // This is running on localhost. Lets check if a service worker still exists or not. + checkValidServiceWorker(swUrl); + } + }); + } +} + +function registerValidSW(swUrl) { + navigator.serviceWorker + .register(swUrl) + .then(registration => { + registration.onupdatefound = () => { + const installingWorker = registration.installing; + installingWorker.onstatechange = () => { + if (installingWorker.state === "installed") { + if (navigator.serviceWorker.controller) { + // At this point, the old content will have been purged and + // the fresh content will have been added to the cache. + // It's the perfect time to display a "New content is + // available; please refresh." message in your web app. + console.log("New content is available; please refresh."); + } else { + // At this point, everything has been precached. + // It's the perfect time to display a + // "Content is cached for offline use." message. + console.log("Content is cached for offline use."); + } + } + }; + }; + }) + .catch(error => { + console.error("Error during service worker registration:", error); + }); +} + +function checkValidServiceWorker(swUrl) { + // Check if the service worker can be found. If it can't reload the page. + fetch(swUrl) + .then(response => { + // Ensure service worker exists, and that we really are getting a JS file. + if ( + response.status === 404 || + response.headers.get("content-type").indexOf("javascript") === -1 + ) { + // No service worker found. Probably a different app. Reload the page. + navigator.serviceWorker.ready.then(registration => { + registration.unregister().then(() => { + window.location.reload(); + }); + }); + } else { + // Service worker found. Proceed as normal. + registerValidSW(swUrl); + } + }) + .catch(() => { + console.log( + "No internet connection found. App is running in offline mode." + ); + }); +} + +export function unregister() { + if ("serviceWorker" in navigator) { + navigator.serviceWorker.ready.then(registration => { + registration.unregister(); + }); + } +} diff --git a/frontend/src/routes/index.js b/frontend/src/routes/index.js new file mode 100644 index 00000000..f7523458 --- /dev/null +++ b/frontend/src/routes/index.js @@ -0,0 +1,64 @@ +import React from "react"; +import { BrowserRouter, Redirect, Route, Switch } from "react-router-dom"; +import { userIsLoggedIn } from "../auth/index"; +import App from "../pages/App"; +import Experiments from "../pages/Experiments"; +import Home from "../pages/Home"; +import NotFound from "../pages/NotFound"; +import Profile from "../pages/Profile"; +import Simulations from "../pages/Simulations"; + +const ProtectedComponent = component => () => + userIsLoggedIn() ? component : <Redirect to="/" />; +const AppComponent = ({ match }) => + userIsLoggedIn() ? ( + <App simulationId={parseInt(match.params.simulationId, 10)} /> + ) : ( + <Redirect to="/" /> + ); + +const ExperimentsComponent = ({ match }) => + userIsLoggedIn() ? ( + <Experiments simulationId={parseInt(match.params.simulationId, 10)} /> + ) : ( + <Redirect to="/" /> + ); + +const SimulationComponent = ({ match }) => + userIsLoggedIn() ? ( + <App + simulationId={parseInt(match.params.simulationId, 10)} + inSimulation={true} + experimentId={parseInt(match.params.experimentId, 10)} + /> + ) : ( + <Redirect to="/" /> + ); + +const Routes = () => ( + <BrowserRouter> + <Switch> + <Route exact path="/" component={Home} /> + <Route + exact + path="/simulations" + render={ProtectedComponent(<Simulations />)} + /> + <Route exact path="/simulations/:simulationId" component={AppComponent} /> + <Route + exact + path="/simulations/:simulationId/experiments" + component={ExperimentsComponent} + /> + <Route + exact + path="/simulations/:simulationId/experiments/:experimentId" + component={SimulationComponent} + /> + <Route exact path="/profile" render={ProtectedComponent(<Profile />)} /> + <Route path="/*" component={NotFound} /> + </Switch> + </BrowserRouter> +); + +export default Routes; diff --git a/frontend/src/sagas/experiments.js b/frontend/src/sagas/experiments.js new file mode 100644 index 00000000..d9c410f7 --- /dev/null +++ b/frontend/src/sagas/experiments.js @@ -0,0 +1,183 @@ +import { call, put, select, delay } from "redux-saga/effects"; +import { addPropToStoreObject, addToStore } from "../actions/objects"; +import { setLastSimulatedTick } from "../actions/simulation/tick"; +import { addBatchToStates } from "../actions/states"; +import { + deleteExperiment, + getAllMachineStates, + getAllRackStates, + getAllRoomStates, + getAllTaskStates, + getExperiment, + getLastSimulatedTick +} from "../api/routes/experiments"; +import { getTasksOfJob } from "../api/routes/jobs"; +import { + addExperiment, + getExperimentsOfSimulation, + getSimulation +} from "../api/routes/simulations"; +import { getJobsOfTrace } from "../api/routes/traces"; +import { + fetchAndStoreAllSchedulers, + fetchAndStoreAllTraces, + fetchAndStorePathsOfSimulation +} from "./objects"; +import { fetchAllDatacentersOfExperiment } from "./topology"; + +export function* onOpenExperimentSucceeded(action) { + try { + const simulation = yield call(getSimulation, action.simulationId); + yield put(addToStore("simulation", simulation)); + + const experiment = yield call(getExperiment, action.experimentId); + yield put(addToStore("experiment", experiment)); + + yield fetchExperimentSpecifications(); + yield fetchWorkloadOfTrace(experiment.traceId); + + yield fetchAllDatacentersOfExperiment(experiment); + yield startStateFetchLoop(action.experimentId); + } catch (error) { + console.error(error); + } +} + +function* startStateFetchLoop(experimentId) { + try { + while ((yield select(state => state.currentExperimentId)) !== -1) { + const lastSimulatedTick = (yield call(getLastSimulatedTick, experimentId)) + .lastSimulatedTick; + if ( + lastSimulatedTick !== (yield select(state => state.lastSimulatedTick)) + ) { + yield put(setLastSimulatedTick(lastSimulatedTick)); + + const taskStates = yield call(getAllTaskStates, experimentId); + const machineStates = yield call(getAllMachineStates, experimentId); + const rackStates = yield call(getAllRackStates, experimentId); + const roomStates = yield call(getAllRoomStates, experimentId); + + yield put(addBatchToStates("task", taskStates)); + yield put(addBatchToStates("machine", machineStates)); + yield put(addBatchToStates("rack", rackStates)); + yield put(addBatchToStates("room", roomStates)); + + yield delay(5000); + } else { + yield delay(10000); + } + } + } catch (error) { + console.error(error); + } +} + +export function* onFetchExperimentsOfSimulation() { + try { + const currentSimulationId = yield select( + state => state.currentSimulationId + ); + + yield fetchExperimentSpecifications(); + const experiments = yield call( + getExperimentsOfSimulation, + currentSimulationId + ); + for (let i in experiments) { + yield put(addToStore("experiment", experiments[i])); + } + yield put( + addPropToStoreObject("simulation", currentSimulationId, { + experimentIds: experiments.map(experiment => experiment.id) + }) + ); + } catch (error) { + console.error(error); + } +} + +function* fetchExperimentSpecifications() { + try { + const currentSimulationId = yield select( + state => state.currentSimulationId + ); + yield fetchAndStorePathsOfSimulation(currentSimulationId); + yield fetchAndStoreAllTraces(); + yield fetchAndStoreAllSchedulers(); + } catch (error) { + console.error(error); + } +} + +function* fetchWorkloadOfTrace(traceId) { + try { + const jobs = yield call(getJobsOfTrace, traceId); + for (let i in jobs) { + const job = jobs[i]; + const tasks = yield call(getTasksOfJob, job.id); + job.taskIds = tasks.map(task => task.id); + for (let j in tasks) { + yield put(addToStore("task", tasks[j])); + } + yield put(addToStore("job", job)); + } + yield put( + addPropToStoreObject("trace", traceId, { + jobIds: jobs.map(job => job.id) + }) + ); + } catch (error) { + console.error(error); + } +} + +export function* onAddExperiment(action) { + try { + const currentSimulationId = yield select( + state => state.currentSimulationId + ); + + const experiment = yield call( + addExperiment, + currentSimulationId, + Object.assign({}, action.experiment, { + id: -1, + simulationId: currentSimulationId + }) + ); + yield put(addToStore("experiment", experiment)); + + const experimentIds = yield select( + state => state.objects.simulation[currentSimulationId].experimentIds + ); + yield put( + addPropToStoreObject("simulation", currentSimulationId, { + experimentIds: experimentIds.concat([experiment.id]) + }) + ); + } catch (error) { + console.error(error); + } +} + +export function* onDeleteExperiment(action) { + try { + yield call(deleteExperiment, action.id); + + const currentSimulationId = yield select( + state => state.currentSimulationId + ); + const experimentIds = yield select( + state => state.objects.simulation[currentSimulationId].experimentIds + ); + + yield put( + addPropToStoreObject("simulation", currentSimulationId, { + experimentIds: experimentIds.filter(id => id !== action.id) + }) + ); + } catch (error) { + console.error(error); + } +} diff --git a/frontend/src/sagas/index.js b/frontend/src/sagas/index.js new file mode 100644 index 00000000..56c8f09b --- /dev/null +++ b/frontend/src/sagas/index.js @@ -0,0 +1,106 @@ +import { takeEvery } from "redux-saga/effects"; +import { LOG_IN } from "../actions/auth"; +import { + ADD_EXPERIMENT, + DELETE_EXPERIMENT, + FETCH_EXPERIMENTS_OF_SIMULATION, + OPEN_EXPERIMENT_SUCCEEDED +} from "../actions/experiments"; +import { + ADD_SIMULATION, + DELETE_SIMULATION, + OPEN_SIMULATION_SUCCEEDED +} from "../actions/simulations"; +import { + ADD_TILE, + CANCEL_NEW_ROOM_CONSTRUCTION, + DELETE_TILE, + START_NEW_ROOM_CONSTRUCTION +} from "../actions/topology/building"; +import { + ADD_UNIT, + DELETE_MACHINE, + DELETE_UNIT +} from "../actions/topology/machine"; +import { + ADD_MACHINE, + DELETE_RACK, + EDIT_RACK_NAME +} from "../actions/topology/rack"; +import { + ADD_RACK_TO_TILE, + DELETE_ROOM, + EDIT_ROOM_NAME +} from "../actions/topology/room"; +import { + DELETE_CURRENT_USER, + FETCH_AUTHORIZATIONS_OF_CURRENT_USER +} from "../actions/users"; +import { + onAddExperiment, + onDeleteExperiment, + onFetchExperimentsOfSimulation, + onOpenExperimentSucceeded +} from "./experiments"; +import { onDeleteCurrentUser } from "./profile"; +import { + onOpenSimulationSucceeded, + onSimulationAdd, + onSimulationDelete +} from "./simulations"; +import { + onAddMachine, + onAddRackToTile, + onAddTile, + onAddUnit, + onCancelNewRoomConstruction, + onDeleteMachine, + onDeleteRack, + onDeleteRoom, + onDeleteTile, + onDeleteUnit, + onEditRackName, + onEditRoomName, + onStartNewRoomConstruction +} from "./topology"; +import { + onFetchAuthorizationsOfCurrentUser, + onFetchLoggedInUser +} from "./users"; + +export default function* rootSaga() { + yield takeEvery(LOG_IN, onFetchLoggedInUser); + + yield takeEvery( + FETCH_AUTHORIZATIONS_OF_CURRENT_USER, + onFetchAuthorizationsOfCurrentUser + ); + yield takeEvery(ADD_SIMULATION, onSimulationAdd); + yield takeEvery(DELETE_SIMULATION, onSimulationDelete); + + yield takeEvery(DELETE_CURRENT_USER, onDeleteCurrentUser); + + yield takeEvery(OPEN_SIMULATION_SUCCEEDED, onOpenSimulationSucceeded); + yield takeEvery(OPEN_EXPERIMENT_SUCCEEDED, onOpenExperimentSucceeded); + + yield takeEvery(START_NEW_ROOM_CONSTRUCTION, onStartNewRoomConstruction); + yield takeEvery(CANCEL_NEW_ROOM_CONSTRUCTION, onCancelNewRoomConstruction); + yield takeEvery(ADD_TILE, onAddTile); + yield takeEvery(DELETE_TILE, onDeleteTile); + yield takeEvery(EDIT_ROOM_NAME, onEditRoomName); + yield takeEvery(DELETE_ROOM, onDeleteRoom); + yield takeEvery(EDIT_RACK_NAME, onEditRackName); + yield takeEvery(DELETE_RACK, onDeleteRack); + yield takeEvery(ADD_RACK_TO_TILE, onAddRackToTile); + yield takeEvery(ADD_MACHINE, onAddMachine); + yield takeEvery(DELETE_MACHINE, onDeleteMachine); + yield takeEvery(ADD_UNIT, onAddUnit); + yield takeEvery(DELETE_UNIT, onDeleteUnit); + + yield takeEvery( + FETCH_EXPERIMENTS_OF_SIMULATION, + onFetchExperimentsOfSimulation + ); + yield takeEvery(ADD_EXPERIMENT, onAddExperiment); + yield takeEvery(DELETE_EXPERIMENT, onDeleteExperiment); +} diff --git a/frontend/src/sagas/objects.js b/frontend/src/sagas/objects.js new file mode 100644 index 00000000..3cfd43a6 --- /dev/null +++ b/frontend/src/sagas/objects.js @@ -0,0 +1,140 @@ +import { call, put, select } from "redux-saga/effects"; +import { addToStore } from "../actions/objects"; +import { getDatacenter, getRoomsOfDatacenter } from "../api/routes/datacenters"; +import { getPath, getSectionsOfPath } from "../api/routes/paths"; +import { getTilesOfRoom } from "../api/routes/rooms"; +import { getAllSchedulers } from "../api/routes/schedulers"; +import { getSection } from "../api/routes/sections"; +import { getPathsOfSimulation, getSimulation } from "../api/routes/simulations"; +import { + getAllCPUs, + getAllGPUs, + getAllMemories, + getAllStorages, + getCoolingItem, + getCPU, + getFailureModel, + getGPU, + getMemory, + getPSU, + getStorage +} from "../api/routes/specifications"; +import { getMachinesOfRackByTile, getRackByTile } from "../api/routes/tiles"; +import { getAllTraces } from "../api/routes/traces"; +import { getUser } from "../api/routes/users"; + +export const OBJECT_SELECTORS = { + simulation: state => state.objects.simulation, + user: state => state.objects.user, + authorization: state => state.objects.authorization, + failureModel: state => state.objects.failureModel, + cpu: state => state.objects.cpu, + gpu: state => state.objects.gpu, + memory: state => state.objects.memory, + storage: state => state.objects.storage, + machine: state => state.objects.machine, + rack: state => state.objects.rack, + coolingItem: state => state.objects.coolingItem, + psu: state => state.objects.psu, + tile: state => state.objects.tile, + room: state => state.objects.room, + datacenter: state => state.objects.datacenter, + section: state => state.objects.section, + path: state => state.objects.path +}; + +function* fetchAndStoreObject(objectType, id, apiCall) { + const objectStore = yield select(OBJECT_SELECTORS[objectType]); + let object = objectStore[id]; + if (!object) { + object = yield apiCall; + yield put(addToStore(objectType, object)); + } + return object; +} + +function* fetchAndStoreObjects(objectType, apiCall) { + const objects = yield apiCall; + for (let index in objects) { + yield put(addToStore(objectType, objects[index])); + } + return objects; +} + +export const fetchAndStoreSimulation = id => + fetchAndStoreObject("simulation", id, call(getSimulation, id)); + +export const fetchAndStoreUser = id => + fetchAndStoreObject("user", id, call(getUser, id)); + +export const fetchAndStoreFailureModel = id => + fetchAndStoreObject("failureModel", id, call(getFailureModel, id)); + +export const fetchAndStoreAllCPUs = () => + fetchAndStoreObjects("cpu", call(getAllCPUs)); + +export const fetchAndStoreCPU = id => + fetchAndStoreObject("cpu", id, call(getCPU, id)); + +export const fetchAndStoreAllGPUs = () => + fetchAndStoreObjects("gpu", call(getAllGPUs)); + +export const fetchAndStoreGPU = id => + fetchAndStoreObject("gpu", id, call(getGPU, id)); + +export const fetchAndStoreAllMemories = () => + fetchAndStoreObjects("memory", call(getAllMemories)); + +export const fetchAndStoreMemory = id => + fetchAndStoreObject("memory", id, call(getMemory, id)); + +export const fetchAndStoreAllStorages = () => + fetchAndStoreObjects("storage", call(getAllStorages)); + +export const fetchAndStoreStorage = id => + fetchAndStoreObject("storage", id, call(getStorage, id)); + +export const fetchAndStoreMachinesOfTile = tileId => + fetchAndStoreObjects("machine", call(getMachinesOfRackByTile, tileId)); + +export const fetchAndStoreRackOnTile = (id, tileId) => + fetchAndStoreObject("rack", id, call(getRackByTile, tileId)); + +export const fetchAndStoreCoolingItem = id => + fetchAndStoreObject("coolingItem", id, call(getCoolingItem, id)); + +export const fetchAndStorePSU = id => + fetchAndStoreObject("psu", id, call(getPSU, id)); + +export const fetchAndStoreTilesOfRoom = roomId => + fetchAndStoreObjects("tile", call(getTilesOfRoom, roomId)); + +export const fetchAndStoreRoomsOfDatacenter = datacenterId => + fetchAndStoreObjects("room", call(getRoomsOfDatacenter, datacenterId)); + +export const fetchAndStoreDatacenter = id => + fetchAndStoreObject("datacenter", id, call(getDatacenter, id)); + +export const fetchAndStoreSection = id => + fetchAndStoreObject("section", id, call(getSection, id)); + +export const fetchAndStoreSectionsOfPath = pathId => + fetchAndStoreObjects("section", call(getSectionsOfPath, pathId)); + +export const fetchAndStorePath = id => + fetchAndStoreObject("path", id, call(getPath, id)); + +export const fetchAndStorePathsOfSimulation = simulationId => + fetchAndStoreObjects("path", call(getPathsOfSimulation, simulationId)); + +export const fetchAndStoreAllTraces = () => + fetchAndStoreObjects("trace", call(getAllTraces)); + +export const fetchAndStoreAllSchedulers = function*() { + const objects = yield call(getAllSchedulers); + for (let index in objects) { + objects[index].id = objects[index].name; + yield put(addToStore("scheduler", objects[index])); + } + return objects; +}; diff --git a/frontend/src/sagas/profile.js b/frontend/src/sagas/profile.js new file mode 100644 index 00000000..31d4dd4f --- /dev/null +++ b/frontend/src/sagas/profile.js @@ -0,0 +1,12 @@ +import { call, put } from "redux-saga/effects"; +import { deleteCurrentUserSucceeded } from "../actions/users"; +import { deleteUser } from "../api/routes/users"; + +export function* onDeleteCurrentUser(action) { + try { + yield call(deleteUser, action.userId); + yield put(deleteCurrentUserSucceeded()); + } catch (error) { + console.error(error); + } +} diff --git a/frontend/src/sagas/simulations.js b/frontend/src/sagas/simulations.js new file mode 100644 index 00000000..9df4e4b5 --- /dev/null +++ b/frontend/src/sagas/simulations.js @@ -0,0 +1,51 @@ +import { call, put } from "redux-saga/effects"; +import { addToStore } from "../actions/objects"; +import { + addSimulationSucceeded, + deleteSimulationSucceeded +} from "../actions/simulations"; +import { + addSimulation, + deleteSimulation, + getSimulation +} from "../api/routes/simulations"; +import { fetchLatestDatacenter } from "./topology"; + +export function* onOpenSimulationSucceeded(action) { + try { + const simulation = yield call(getSimulation, action.id); + yield put(addToStore("simulation", simulation)); + + yield fetchLatestDatacenter(action.id); + } catch (error) { + console.error(error); + } +} + +export function* onSimulationAdd(action) { + try { + const simulation = yield call(addSimulation, { name: action.name }); + yield put(addToStore("simulation", simulation)); + + const authorization = { + simulationId: simulation.id, + userId: action.userId, + authorizationLevel: "OWN" + }; + yield put(addToStore("authorization", authorization)); + yield put( + addSimulationSucceeded([authorization.userId, authorization.simulationId]) + ); + } catch (error) { + console.error(error); + } +} + +export function* onSimulationDelete(action) { + try { + yield call(deleteSimulation, action.id); + yield put(deleteSimulationSucceeded(action.id)); + } catch (error) { + console.error(error); + } +} diff --git a/frontend/src/sagas/topology.js b/frontend/src/sagas/topology.js new file mode 100644 index 00000000..13b4ed17 --- /dev/null +++ b/frontend/src/sagas/topology.js @@ -0,0 +1,434 @@ +import { call, put, select } from "redux-saga/effects"; +import { goDownOneInteractionLevel } from "../actions/interaction-level"; +import { + addIdToStoreObjectListProp, + addPropToStoreObject, + addToStore, + removeIdFromStoreObjectListProp +} from "../actions/objects"; +import { + cancelNewRoomConstructionSucceeded, + setCurrentDatacenter, + startNewRoomConstructionSucceeded +} from "../actions/topology/building"; +import { addRoomToDatacenter } from "../api/routes/datacenters"; +import { addTileToRoom, deleteRoom, updateRoom } from "../api/routes/rooms"; +import { + addMachineToRackOnTile, + addRackToTile, + deleteMachineInRackOnTile, + deleteRackFromTile, + deleteTile, + updateMachineInRackOnTile, + updateRackOnTile +} from "../api/routes/tiles"; +import { + DEFAULT_RACK_POWER_CAPACITY, + DEFAULT_RACK_SLOT_CAPACITY, + MAX_NUM_UNITS_PER_MACHINE +} from "../components/app/map/MapConstants"; +import { + fetchAndStoreAllCPUs, + fetchAndStoreAllGPUs, + fetchAndStoreAllMemories, + fetchAndStoreAllStorages, + fetchAndStoreCoolingItem, + fetchAndStoreCPU, + fetchAndStoreDatacenter, + fetchAndStoreGPU, + fetchAndStoreMachinesOfTile, + fetchAndStoreMemory, + fetchAndStorePath, + fetchAndStorePathsOfSimulation, + fetchAndStorePSU, + fetchAndStoreRackOnTile, + fetchAndStoreRoomsOfDatacenter, + fetchAndStoreSectionsOfPath, + fetchAndStoreStorage, + fetchAndStoreTilesOfRoom +} from "./objects"; + +export function* fetchLatestDatacenter(simulationId) { + try { + const paths = yield fetchAndStorePathsOfSimulation(simulationId); + const latestPath = paths[paths.length - 1]; + const sections = yield fetchAndStoreSectionsOfPath(latestPath.id); + const latestSection = sections[sections.length - 1]; + yield fetchAllUnitSpecifications(); + yield fetchDatacenter(latestSection.datacenterId); + yield put(setCurrentDatacenter(latestSection.datacenterId)); + } catch (error) { + console.error(error); + } +} + +export function* fetchAllDatacentersOfExperiment(experiment) { + try { + const path = yield fetchAndStorePath(experiment.pathId); + const sections = yield fetchAndStoreSectionsOfPath(path.id); + path.sectionIds = sections.map(section => section.id); + yield fetchAllUnitSpecifications(); + + for (let i in sections) { + yield fetchDatacenter(sections[i].datacenterId); + } + yield put(setCurrentDatacenter(sections[0].datacenterId)); + } catch (error) { + console.error(error); + } +} + +function* fetchDatacenter(datacenterId) { + try { + yield fetchAndStoreDatacenter(datacenterId); + const rooms = yield fetchAndStoreRoomsOfDatacenter(datacenterId); + yield put( + addPropToStoreObject("datacenter", datacenterId, { + roomIds: rooms.map(room => room.id) + }) + ); + + for (let index in rooms) { + yield fetchRoom(rooms[index].id); + } + } catch (error) { + console.error(error); + } +} + +function* fetchAllUnitSpecifications() { + try { + yield fetchAndStoreAllCPUs(); + yield fetchAndStoreAllGPUs(); + yield fetchAndStoreAllMemories(); + yield fetchAndStoreAllStorages(); + } catch (error) { + console.error(error); + } +} + +function* fetchRoom(roomId) { + const tiles = yield fetchAndStoreTilesOfRoom(roomId); + yield put( + addPropToStoreObject("room", roomId, { + tileIds: tiles.map(tile => tile.id) + }) + ); + + for (let index in tiles) { + yield fetchTile(tiles[index]); + } +} + +function* fetchTile(tile) { + if (!tile.objectType) { + return; + } + + switch (tile.objectType) { + case "RACK": + const rack = yield fetchAndStoreRackOnTile(tile.objectId, tile.id); + yield put(addPropToStoreObject("tile", tile.id, { rackId: rack.id })); + yield fetchMachinesOfRack(tile.id, rack); + break; + case "COOLING_ITEM": + const coolingItem = yield fetchAndStoreCoolingItem(tile.objectId); + yield put( + addPropToStoreObject("tile", tile.id, { coolingItemId: coolingItem.id }) + ); + break; + case "PSU": + const psu = yield fetchAndStorePSU(tile.objectId); + yield put(addPropToStoreObject("tile", tile.id, { psuId: psu.id })); + break; + default: + console.warn("Unknown rack type encountered while fetching tile objects"); + } +} + +function* fetchMachinesOfRack(tileId, rack) { + const machines = yield fetchAndStoreMachinesOfTile(tileId); + const machineIds = new Array(rack.capacity).fill(null); + machines.forEach(machine => (machineIds[machine.position - 1] = machine.id)); + + yield put(addPropToStoreObject("rack", rack.id, { machineIds })); + + for (let index in machines) { + for (let i in machines[index].cpuIds) { + yield fetchAndStoreCPU(machines[index].cpuIds[i]); + } + for (let i in machines[index].gpuIds) { + yield fetchAndStoreGPU(machines[index].gpuIds[i]); + } + for (let i in machines[index].memoryIds) { + yield fetchAndStoreMemory(machines[index].memoryIds[i]); + } + for (let i in machines[index].storageIds) { + yield fetchAndStoreStorage(machines[index].storageIds[i]); + } + } +} + +export function* onStartNewRoomConstruction() { + try { + const datacenterId = yield select(state => state.currentDatacenterId); + const room = yield call(addRoomToDatacenter, { + id: -1, + datacenterId, + roomType: "SERVER" + }); + const roomWithEmptyTileList = Object.assign({}, room, { tileIds: [] }); + yield put(addToStore("room", roomWithEmptyTileList)); + yield put( + addIdToStoreObjectListProp("datacenter", datacenterId, "roomIds", room.id) + ); + yield put(startNewRoomConstructionSucceeded(room.id)); + } catch (error) { + console.error(error); + } +} + +export function* onCancelNewRoomConstruction() { + try { + const datacenterId = yield select(state => state.currentDatacenterId); + const roomId = yield select( + state => state.construction.currentRoomInConstruction + ); + yield call(deleteRoom, roomId); + yield put( + removeIdFromStoreObjectListProp( + "datacenter", + datacenterId, + "roomIds", + roomId + ) + ); + yield put(cancelNewRoomConstructionSucceeded()); + } catch (error) { + console.error(error); + } +} + +export function* onAddTile(action) { + try { + const roomId = yield select( + state => state.construction.currentRoomInConstruction + ); + const tile = yield call(addTileToRoom, { + roomId, + positionX: action.positionX, + positionY: action.positionY + }); + yield put(addToStore("tile", tile)); + yield put(addIdToStoreObjectListProp("room", roomId, "tileIds", tile.id)); + } catch (error) { + console.error(error); + } +} + +export function* onDeleteTile(action) { + try { + const roomId = yield select( + state => state.construction.currentRoomInConstruction + ); + yield call(deleteTile, action.tileId); + yield put( + removeIdFromStoreObjectListProp("room", roomId, "tileIds", action.tileId) + ); + } catch (error) { + console.error(error); + } +} + +export function* onEditRoomName(action) { + try { + const roomId = yield select(state => state.interactionLevel.roomId); + const room = Object.assign( + {}, + yield select(state => state.objects.room[roomId]) + ); + room.name = action.name; + yield call(updateRoom, room); + yield put(addPropToStoreObject("room", roomId, { name: action.name })); + } catch (error) { + console.error(error); + } +} + +export function* onDeleteRoom() { + try { + const datacenterId = yield select(state => state.currentDatacenterId); + const roomId = yield select(state => state.interactionLevel.roomId); + yield call(deleteRoom, roomId); + yield put(goDownOneInteractionLevel()); + yield put( + removeIdFromStoreObjectListProp( + "datacenter", + datacenterId, + "roomIds", + roomId + ) + ); + } catch (error) { + console.error(error); + } +} + +export function* onEditRackName(action) { + try { + const tileId = yield select(state => state.interactionLevel.tileId); + const rackId = yield select( + state => state.objects.tile[state.interactionLevel.tileId].objectId + ); + const rack = Object.assign( + {}, + yield select(state => state.objects.rack[rackId]) + ); + rack.name = action.name; + yield call(updateRackOnTile, tileId, rack); + yield put(addPropToStoreObject("rack", rackId, { name: action.name })); + } catch (error) { + console.error(error); + } +} + +export function* onDeleteRack() { + try { + const tileId = yield select(state => state.interactionLevel.tileId); + yield call(deleteRackFromTile, tileId); + yield put(goDownOneInteractionLevel()); + yield put(addPropToStoreObject("tile", tileId, { objectType: undefined })); + yield put(addPropToStoreObject("tile", tileId, { objectId: undefined })); + } catch (error) { + console.error(error); + } +} + +export function* onAddRackToTile(action) { + try { + const rack = yield call(addRackToTile, action.tileId, { + id: -1, + name: "Rack", + capacity: DEFAULT_RACK_SLOT_CAPACITY, + powerCapacityW: DEFAULT_RACK_POWER_CAPACITY + }); + rack.machineIds = new Array(rack.capacity).fill(null); + yield put(addToStore("rack", rack)); + yield put( + addPropToStoreObject("tile", action.tileId, { objectId: rack.id }) + ); + yield put( + addPropToStoreObject("tile", action.tileId, { objectType: "RACK" }) + ); + } catch (error) { + console.error(error); + } +} + +export function* onAddMachine(action) { + try { + const tileId = yield select(state => state.interactionLevel.tileId); + const rackId = yield select( + state => state.objects.tile[state.interactionLevel.tileId].objectId + ); + const rack = yield select(state => state.objects.rack[rackId]); + + const machine = yield call(addMachineToRackOnTile, tileId, { + id: -1, + rackId, + position: action.position, + tags: [], + cpuIds: [], + gpuIds: [], + memoryIds: [], + storageIds: [] + }); + yield put(addToStore("machine", machine)); + + const machineIds = [...rack.machineIds]; + machineIds[machine.position - 1] = machine.id; + yield put(addPropToStoreObject("rack", rackId, { machineIds })); + } catch (error) { + console.error(error); + } +} + +export function* onDeleteMachine() { + try { + const tileId = yield select(state => state.interactionLevel.tileId); + const position = yield select(state => state.interactionLevel.position); + const rack = yield select( + state => state.objects.rack[state.objects.tile[tileId].objectId] + ); + yield call(deleteMachineInRackOnTile, tileId, position); + const machineIds = [...rack.machineIds]; + machineIds[position - 1] = null; + yield put(goDownOneInteractionLevel()); + yield put(addPropToStoreObject("rack", rack.id, { machineIds })); + } catch (error) { + console.error(error); + } +} + +export function* onAddUnit(action) { + try { + const tileId = yield select(state => state.interactionLevel.tileId); + const position = yield select(state => state.interactionLevel.position); + const machine = yield select( + state => + state.objects.machine[ + state.objects.rack[state.objects.tile[tileId].objectId].machineIds[ + position - 1 + ] + ] + ); + + if (machine[action.unitType + "Ids"].length >= MAX_NUM_UNITS_PER_MACHINE) { + return; + } + + const units = [...machine[action.unitType + "Ids"], action.id]; + const updatedMachine = Object.assign({}, machine, { + [action.unitType + "Ids"]: units + }); + + yield call(updateMachineInRackOnTile, tileId, position, updatedMachine); + + yield put( + addPropToStoreObject("machine", machine.id, { + [action.unitType + "Ids"]: units + }) + ); + } catch (error) { + console.error(error); + } +} + +export function* onDeleteUnit(action) { + try { + const tileId = yield select(state => state.interactionLevel.tileId); + const position = yield select(state => state.interactionLevel.position); + const machine = yield select( + state => + state.objects.machine[ + state.objects.rack[state.objects.tile[tileId].objectId].machineIds[ + position - 1 + ] + ] + ); + const unitIds = machine[action.unitType + "Ids"].slice(); + unitIds.splice(action.index, 1); + const updatedMachine = Object.assign({}, machine, { + [action.unitType + "Ids"]: unitIds + }); + + yield call(updateMachineInRackOnTile, tileId, position, updatedMachine); + yield put( + addPropToStoreObject("machine", machine.id, { + [action.unitType + "Ids"]: unitIds + }) + ); + } catch (error) { + console.error(error); + } +} diff --git a/frontend/src/sagas/users.js b/frontend/src/sagas/users.js new file mode 100644 index 00000000..3825443a --- /dev/null +++ b/frontend/src/sagas/users.js @@ -0,0 +1,50 @@ +import { call, put } from "redux-saga/effects"; +import { logInSucceeded } from "../actions/auth"; +import { addToStore } from "../actions/objects"; +import { fetchAuthorizationsOfCurrentUserSucceeded } from "../actions/users"; +import { performTokenSignIn } from "../api/routes/token-signin"; +import { addUser, getAuthorizationsByUser } from "../api/routes/users"; +import { saveAuthLocalStorage } from "../auth/index"; +import { fetchAndStoreSimulation, fetchAndStoreUser } from "./objects"; + +export function* onFetchLoggedInUser(action) { + try { + const tokenResponse = yield call( + performTokenSignIn, + action.payload.authToken + ); + let userId = tokenResponse.userId; + + if (tokenResponse.isNewUser) { + saveAuthLocalStorage({ authToken: action.payload.authToken }); + const newUser = yield call(addUser, action.payload); + userId = newUser.id; + } + + yield put(logInSucceeded(Object.assign({ userId }, action.payload))); + } catch (error) { + console.error(error); + } +} + +export function* onFetchAuthorizationsOfCurrentUser(action) { + try { + const authorizations = yield call(getAuthorizationsByUser, action.userId); + + for (const authorization of authorizations) { + yield put(addToStore("authorization", authorization)); + + yield fetchAndStoreSimulation(authorization.simulationId); + yield fetchAndStoreUser(authorization.userId); + } + + const authorizationIds = authorizations.map(authorization => [ + authorization.userId, + authorization.simulationId + ]); + + yield put(fetchAuthorizationsOfCurrentUserSucceeded(authorizationIds)); + } catch (error) { + console.error(error); + } +} diff --git a/frontend/src/shapes/index.js b/frontend/src/shapes/index.js new file mode 100644 index 00000000..5570ef34 --- /dev/null +++ b/frontend/src/shapes/index.js @@ -0,0 +1,188 @@ +import PropTypes from "prop-types"; + +const Shapes = {}; + +Shapes.User = PropTypes.shape({ + id: PropTypes.number.isRequired, + googleId: PropTypes.string.isRequired, + email: PropTypes.string.isRequired, + givenName: PropTypes.string.isRequired, + familyName: PropTypes.string.isRequired +}); + +Shapes.Simulation = PropTypes.shape({ + id: PropTypes.number.isRequired, + name: PropTypes.string.isRequired, + datetimeCreated: PropTypes.string.isRequired, + datetimeLastEdited: PropTypes.string.isRequired +}); + +Shapes.Authorization = PropTypes.shape({ + userId: PropTypes.number.isRequired, + user: Shapes.User, + simulationId: PropTypes.number.isRequired, + simulation: Shapes.Simulation, + authorizationLevel: PropTypes.string.isRequired +}); + +Shapes.FailureModel = PropTypes.shape({ + id: PropTypes.number.isRequired, + name: PropTypes.string.isRequired, + rate: PropTypes.number.isRequired +}); + +Shapes.ProcessingUnit = PropTypes.shape({ + id: PropTypes.number.isRequired, + manufacturer: PropTypes.string.isRequired, + family: PropTypes.string.isRequired, + generation: PropTypes.string.isRequired, + model: PropTypes.string.isRequired, + clockRateMhz: PropTypes.number.isRequired, + numberOfCores: PropTypes.number.isRequired, + energyConsumptionW: PropTypes.number.isRequired, + failureModelId: PropTypes.number.isRequired, + failureModel: Shapes.FailureModel +}); + +Shapes.StorageUnit = PropTypes.shape({ + id: PropTypes.number.isRequired, + manufacturer: PropTypes.string.isRequired, + family: PropTypes.string.isRequired, + generation: PropTypes.string.isRequired, + model: PropTypes.string.isRequired, + speedMbPerS: PropTypes.number.isRequired, + sizeMb: PropTypes.number.isRequired, + energyConsumptionW: PropTypes.number.isRequired, + failureModelId: PropTypes.number.isRequired, + failureModel: Shapes.FailureModel +}); + +Shapes.Machine = PropTypes.shape({ + id: PropTypes.number.isRequired, + rackId: PropTypes.number.isRequired, + position: PropTypes.number.isRequired, + cpuIds: PropTypes.arrayOf(PropTypes.number.isRequired), + cpus: PropTypes.arrayOf(Shapes.ProcessingUnit), + gpuIds: PropTypes.arrayOf(PropTypes.number.isRequired), + gpus: PropTypes.arrayOf(Shapes.ProcessingUnit), + memoryIds: PropTypes.arrayOf(PropTypes.number.isRequired), + memories: PropTypes.arrayOf(Shapes.StorageUnit), + storageIds: PropTypes.arrayOf(PropTypes.number.isRequired), + storages: PropTypes.arrayOf(Shapes.StorageUnit) +}); + +Shapes.Rack = PropTypes.shape({ + id: PropTypes.number.isRequired, + name: PropTypes.string.isRequired, + capacity: PropTypes.number.isRequired, + powerCapacityW: PropTypes.number.isRequired, + machines: PropTypes.arrayOf(Shapes.Machine) +}); + +Shapes.CoolingItem = PropTypes.shape({ + id: PropTypes.number.isRequired, + energyConsumptionW: PropTypes.number.isRequired, + type: PropTypes.string.isRequired, + failureModelId: PropTypes.number.isRequired, + failureModel: Shapes.FailureModel +}); + +Shapes.PSU = PropTypes.shape({ + id: PropTypes.number.isRequired, + energyKwh: PropTypes.number.isRequired, + type: PropTypes.string.isRequired, + failureModelId: PropTypes.number.isRequired, + failureModel: Shapes.FailureModel +}); + +Shapes.Tile = PropTypes.shape({ + id: PropTypes.number.isRequired, + roomId: PropTypes.number.isRequired, + positionX: PropTypes.number.isRequired, + positionY: PropTypes.number.isRequired, + objectId: PropTypes.number, + objectType: PropTypes.string, + rack: Shapes.Rack, + coolingItem: Shapes.CoolingItem, + psu: Shapes.PSU +}); + +Shapes.Room = PropTypes.shape({ + id: PropTypes.number.isRequired, + datacenterId: PropTypes.number.isRequired, + name: PropTypes.string.isRequired, + roomType: PropTypes.string.isRequired, + tiles: PropTypes.arrayOf(Shapes.Tile) +}); + +Shapes.Datacenter = PropTypes.shape({ + id: PropTypes.number.isRequired, + rooms: PropTypes.arrayOf(Shapes.Room) +}); + +Shapes.Section = PropTypes.shape({ + id: PropTypes.number.isRequired, + pathId: PropTypes.number.isRequired, + startTick: PropTypes.number.isRequired, + datacenterId: PropTypes.number.isRequired, + datacenter: Shapes.Datacenter +}); + +Shapes.Path = PropTypes.shape({ + id: PropTypes.number.isRequired, + simulationId: PropTypes.number.isRequired, + name: PropTypes.string, + datetimeCreated: PropTypes.string.isRequired, + sections: PropTypes.arrayOf(Shapes.Section) +}); + +Shapes.Scheduler = PropTypes.shape({ + name: PropTypes.string.isRequired +}); + +Shapes.Task = PropTypes.shape({ + id: PropTypes.number.isRequired, + jobId: PropTypes.number.isRequired, + startTick: PropTypes.number.isRequired, + totalFlopCount: PropTypes.number.isRequired +}); + +Shapes.Job = PropTypes.shape({ + id: PropTypes.number.isRequired, + name: PropTypes.string.isRequired, + traceId: PropTypes.number.isRequired, + taskIds: PropTypes.arrayOf(PropTypes.number) +}); + +Shapes.Trace = PropTypes.shape({ + id: PropTypes.number.isRequired, + name: PropTypes.string.isRequired, + jobIds: PropTypes.arrayOf(PropTypes.number) +}); + +Shapes.Experiment = PropTypes.shape({ + id: PropTypes.number.isRequired, + simulationId: PropTypes.number.isRequired, + traceId: PropTypes.number.isRequired, + trace: Shapes.Trace, + pathId: PropTypes.number.isRequired, + path: Shapes.Path, + schedulerName: PropTypes.string.isRequired, + scheduler: Shapes.Scheduler, + name: PropTypes.string.isRequired +}); + +Shapes.WallSegment = PropTypes.shape({ + startPosX: PropTypes.number.isRequired, + startPosY: PropTypes.number.isRequired, + isHorizontal: PropTypes.bool.isRequired, + length: PropTypes.number.isRequired +}); + +Shapes.InteractionLevel = PropTypes.shape({ + mode: PropTypes.string.isRequired, + roomId: PropTypes.number, + rackId: PropTypes.bool +}); + +export default Shapes; diff --git a/frontend/src/shortcuts/keymap.js b/frontend/src/shortcuts/keymap.js new file mode 100644 index 00000000..7bc24e83 --- /dev/null +++ b/frontend/src/shortcuts/keymap.js @@ -0,0 +1,10 @@ +const KeymapConfiguration = { + MAP: { + MOVE_LEFT: ["a", "left"], + MOVE_RIGHT: ["d", "right"], + MOVE_UP: ["w", "up"], + MOVE_DOWN: ["s", "down"] + } +}; + +export default KeymapConfiguration; diff --git a/frontend/src/store/configure-store.js b/frontend/src/store/configure-store.js new file mode 100644 index 00000000..29af25ab --- /dev/null +++ b/frontend/src/store/configure-store.js @@ -0,0 +1,41 @@ +import { applyMiddleware, compose, createStore } from "redux"; +import persistState from "redux-localstorage"; +import { createLogger } from "redux-logger"; +import createSagaMiddleware from "redux-saga"; +import thunk from "redux-thunk"; +import { authRedirectMiddleware } from "../auth/index"; +import rootReducer from "../reducers/index"; +import rootSaga from "../sagas/index"; +import { dummyMiddleware } from "./middlewares/dummy-middleware"; +import { viewportAdjustmentMiddleware } from "./middlewares/viewport-adjustment"; + +const sagaMiddleware = createSagaMiddleware(); + +let logger; +if (process.env.NODE_ENV !== "production") { + logger = createLogger(); +} + +const middlewares = [ + process.env.NODE_ENV === "production" ? dummyMiddleware : logger, + thunk, + sagaMiddleware, + authRedirectMiddleware, + viewportAdjustmentMiddleware +]; + +export let store = undefined; + +export default function configureStore() { + const configuredStore = createStore( + rootReducer, + compose( + persistState("auth"), + applyMiddleware(...middlewares) + ) + ); + sagaMiddleware.run(rootSaga); + store = configuredStore; + + return configuredStore; +} diff --git a/frontend/src/store/middlewares/dummy-middleware.js b/frontend/src/store/middlewares/dummy-middleware.js new file mode 100644 index 00000000..468b15d1 --- /dev/null +++ b/frontend/src/store/middlewares/dummy-middleware.js @@ -0,0 +1,3 @@ +export const dummyMiddleware = store => next => action => { + next(action); +}; diff --git a/frontend/src/store/middlewares/viewport-adjustment.js b/frontend/src/store/middlewares/viewport-adjustment.js new file mode 100644 index 00000000..132391f3 --- /dev/null +++ b/frontend/src/store/middlewares/viewport-adjustment.js @@ -0,0 +1,90 @@ +import { + SET_MAP_DIMENSIONS, + setMapPosition, + setMapScale +} from "../../actions/map"; +import { SET_CURRENT_DATACENTER } from "../../actions/topology/building"; +import { + MAP_MAX_SCALE, + MAP_MIN_SCALE, + SIDEBAR_WIDTH, + TILE_SIZE_IN_PIXELS, + VIEWPORT_PADDING +} from "../../components/app/map/MapConstants"; +import { calculateRoomListBounds } from "../../util/tile-calculations"; + +export const viewportAdjustmentMiddleware = store => next => action => { + const state = store.getState(); + + let datacenterId = -1; + let mapDimensions = {}; + if (action.type === SET_CURRENT_DATACENTER && action.datacenterId !== -1) { + datacenterId = action.datacenterId; + mapDimensions = state.map.dimensions; + } else if ( + action.type === SET_MAP_DIMENSIONS && + state.currentDatacenterId !== -1 + ) { + datacenterId = state.currentDatacenterId; + mapDimensions = { width: action.width, height: action.height }; + } + + if (datacenterId !== -1) { + const roomIds = state.objects.datacenter[datacenterId].roomIds; + const rooms = roomIds.map(id => Object.assign({}, state.objects.room[id])); + rooms.forEach( + room => + (room.tiles = room.tileIds.map(tileId => state.objects.tile[tileId])) + ); + + let hasNoTiles = true; + for (let i in rooms) { + if (rooms[i].tiles.length > 0) { + hasNoTiles = false; + break; + } + } + + if (!hasNoTiles) { + const viewportParams = calculateParametersToZoomInOnRooms( + rooms, + mapDimensions.width, + mapDimensions.height + ); + store.dispatch(setMapPosition(viewportParams.newX, viewportParams.newY)); + store.dispatch(setMapScale(viewportParams.newScale)); + } + } + + next(action); +}; + +function calculateParametersToZoomInOnRooms(rooms, mapWidth, mapHeight) { + const bounds = calculateRoomListBounds(rooms); + const newScale = calculateNewScale(bounds, mapWidth, mapHeight); + + // Coordinates of the center of the room, relative to the global origin of the map + const roomCenterCoordinates = { + x: bounds.center.x * TILE_SIZE_IN_PIXELS * newScale, + y: bounds.center.y * TILE_SIZE_IN_PIXELS * newScale + }; + + const newX = -roomCenterCoordinates.x + mapWidth / 2; + const newY = -roomCenterCoordinates.y + mapHeight / 2; + + return { newScale, newX, newY }; +} + +function calculateNewScale(bounds, mapWidth, mapHeight) { + const width = bounds.max.x - bounds.min.x; + const height = bounds.max.y - bounds.min.y; + + const scaleX = + (mapWidth - 2 * SIDEBAR_WIDTH) / + (width * TILE_SIZE_IN_PIXELS + 2 * VIEWPORT_PADDING); + const scaleY = + mapHeight / (height * TILE_SIZE_IN_PIXELS + 2 * VIEWPORT_PADDING); + const newScale = Math.min(scaleX, scaleY); + + return Math.min(Math.max(MAP_MIN_SCALE, newScale), MAP_MAX_SCALE); +} diff --git a/frontend/src/style-globals/_mixins.sass b/frontend/src/style-globals/_mixins.sass new file mode 100644 index 00000000..4ac5a9bc --- /dev/null +++ b/frontend/src/style-globals/_mixins.sass @@ -0,0 +1,21 @@ +=transition($property, $time) + -webkit-transition: $property $time + -moz-transition: $property $time + -o-transition: $property $time + transition: $property $time + +=user-select + -webkit-user-select: none + -moz-user-select: none + -ms-user-select: none + user-select: none + +=border-radius($length) + -webkit-border-radius: $length + -moz-border-radius: $length + border-radius: $length + +/* General Button Abstractions */ +=clickable + cursor: pointer + +user-select diff --git a/frontend/src/style-globals/_variables.sass b/frontend/src/style-globals/_variables.sass new file mode 100644 index 00000000..00c2b479 --- /dev/null +++ b/frontend/src/style-globals/_variables.sass @@ -0,0 +1,31 @@ +// Sizes and Margins +$document-padding: 20px +$inter-element-margin: 5px +$standard-border-radius: 5px +$side-menu-width: 350px +$color-indicator-width: 140px + +$global-padding: 30px +$side-bar-width: 250px +$navbar-height: 50px +$navbar-padding: 10px + +// Durations +$transition-length: 150ms + +// Colors +$gray-very-dark: #5c5c5c +$gray-dark: #aaa +$gray-semi-dark: #bbb +$gray-semi-light: #ccc +$gray-light: #ddd +$gray-very-light: #eee +$blue: #00A6D6 +$blue-dark: #0087b5 +$blue-very-dark: #006182 +$blue-light: #deebf7 + +// Media queries +$screen-sm: 768px +$screen-md: 992px +$screen-lg: 1200px diff --git a/frontend/src/util/authorizations.js b/frontend/src/util/authorizations.js new file mode 100644 index 00000000..ef649c9c --- /dev/null +++ b/frontend/src/util/authorizations.js @@ -0,0 +1,11 @@ +export const AUTH_ICON_MAP = { + OWN: "home", + EDIT: "pencil", + VIEW: "eye" +}; + +export const AUTH_DESCRIPTION_MAP = { + OWN: "Own", + EDIT: "Can Edit", + VIEW: "Can View" +}; diff --git a/frontend/src/util/colors.js b/frontend/src/util/colors.js new file mode 100644 index 00000000..1e84e162 --- /dev/null +++ b/frontend/src/util/colors.js @@ -0,0 +1,29 @@ +export const GRID_COLOR = "rgba(0, 0, 0, 0.5)"; +export const BACKDROP_COLOR = "rgba(255, 255, 255, 1)"; +export const WALL_COLOR = "rgba(0, 0, 0, 1)"; + +export const ROOM_DEFAULT_COLOR = "rgba(150, 150, 150, 1)"; +export const ROOM_IN_CONSTRUCTION_COLOR = "rgba(51, 153, 255, 1)"; +export const ROOM_HOVER_VALID_COLOR = "rgba(51, 153, 255, 1)"; +export const ROOM_HOVER_INVALID_COLOR = "rgba(255, 102, 0, 1)"; +export const ROOM_NAME_COLOR = "rgba(245, 245, 245, 1)"; +export const ROOM_TYPE_COLOR = "rgba(245, 245, 245, 1)"; + +export const TILE_PLUS_COLOR = "rgba(0, 0, 0, 1)"; + +export const OBJECT_BORDER_COLOR = "rgba(0, 0, 0, 1)"; + +export const RACK_BACKGROUND_COLOR = "rgba(170, 170, 170, 1)"; +export const RACK_SPACE_BAR_BACKGROUND_COLOR = "rgba(222, 235, 247, 0.6)"; +export const RACK_SPACE_BAR_FILL_COLOR = "rgba(91, 155, 213, 0.7)"; +export const RACK_ENERGY_BAR_BACKGROUND_COLOR = "rgba(255, 242, 204, 0.6)"; +export const RACK_ENERGY_BAR_FILL_COLOR = "rgba(244, 215, 0, 0.7)"; +export const COOLING_ITEM_BACKGROUND_COLOR = "rgba(40, 50, 230, 1)"; +export const PSU_BACKGROUND_COLOR = "rgba(230, 50, 60, 1)"; + +export const GRAYED_OUT_AREA_COLOR = "rgba(0, 0, 0, 0.6)"; + +export const SIM_LOW_COLOR = "rgba(197, 224, 180, 1)"; +export const SIM_MID_LOW_COLOR = "rgba(255, 230, 153, 1)"; +export const SIM_MID_HIGH_COLOR = "rgba(248, 203, 173, 1)"; +export const SIM_HIGH_COLOR = "rgba(249, 165, 165, 1)"; diff --git a/frontend/src/util/date-time.js b/frontend/src/util/date-time.js new file mode 100644 index 00000000..0b752600 --- /dev/null +++ b/frontend/src/util/date-time.js @@ -0,0 +1,104 @@ +/** + * Parses and formats the given date-time string representation. + * + * The format assumed is "YYYY-MM-DDTHH:MM:SS". + * + * @param dateTimeString A string expressing a date and a time, in the above mentioned format. + * @returns {string} A human-friendly string version of that date and time. + */ +export function parseAndFormatDateTime(dateTimeString) { + return formatDateTime(parseDateTime(dateTimeString)); +} + +/** + * Parses date-time string representations and returns a parsed object. + * + * The format assumed is "YYYY-MM-DDTHH:MM:SS". + * + * @param dateTimeString A string expressing a date and a time, in the above mentioned format. + * @returns {object} A Date object with the parsed date and time information as content. + */ +export function parseDateTime(dateTimeString) { + return new Date(dateTimeString + ".000Z"); +} + +/** + * Serializes the given date and time value to a human-friendly string. + * + * @param dateTime An object representation of a date and time. + * @returns {string} A human-friendly string version of that date and time. + */ +export function formatDateTime(dateTime) { + let date; + const currentDate = new Date(); + + date = + addPaddingToTwo(dateTime.getDay()) + + "/" + + addPaddingToTwo(dateTime.getMonth()) + + "/" + + addPaddingToTwo(dateTime.getFullYear()); + + if ( + dateTime.getFullYear() === currentDate.getFullYear() && + dateTime.getMonth() === currentDate.getMonth() + ) { + if (dateTime.getDate() === currentDate.getDate()) { + date = "Today"; + } else if (dateTime.getDate() === currentDate.getDate() - 1) { + date = "Yesterday"; + } + } + + return ( + date + + ", " + + addPaddingToTwo(dateTime.getHours()) + + ":" + + addPaddingToTwo(dateTime.getMinutes()) + ); +} + +/** + * Formats the given number of seconds/ticks to a formatted time representation. + * + * @param seconds The number of seconds. + * @returns {string} A string representation of that amount of second, in the from of HH:MM:SS. + */ +export function convertSecondsToFormattedTime(seconds) { + if (seconds <= 0) { + return "0s"; + } + + let hour = Math.floor(seconds / 3600); + let minute = Math.floor(seconds / 60) % 60; + let second = seconds % 60; + + hour = isNaN(hour) ? 0 : hour; + minute = isNaN(minute) ? 0 : minute; + second = isNaN(second) ? 0 : second; + + if (hour === 0 && minute === 0) { + return second + "s"; + } else if (hour === 0) { + return minute + "m" + addPaddingToTwo(second) + "s"; + } else { + return ( + hour + "h" + addPaddingToTwo(minute) + "m" + addPaddingToTwo(second) + "s" + ); + } +} + +/** + * Pads the given integer to have at least two digits. + * + * @param integer An integer to be padded. + * @returns {string} A string containing the padded integer. + */ +function addPaddingToTwo(integer) { + if (integer < 10) { + return "0" + integer.toString(); + } else { + return integer.toString(); + } +} diff --git a/frontend/src/util/date-time.test.js b/frontend/src/util/date-time.test.js new file mode 100644 index 00000000..6c7a6b16 --- /dev/null +++ b/frontend/src/util/date-time.test.js @@ -0,0 +1,35 @@ +import { convertSecondsToFormattedTime, parseDateTime } from "./date-time"; + +describe("date-time parsing", () => { + it("reads components properly", () => { + const dateString = "2017-09-27T20:55:01"; + const parsedDate = parseDateTime(dateString); + + expect(parsedDate.getUTCFullYear()).toEqual(2017); + expect(parsedDate.getUTCMonth()).toEqual(8); + expect(parsedDate.getUTCDate()).toEqual(27); + expect(parsedDate.getUTCHours()).toEqual(20); + expect(parsedDate.getUTCMinutes()).toEqual(55); + expect(parsedDate.getUTCSeconds()).toEqual(1); + }); +}); + +describe("tick formatting", () => { + it("returns '0s' for numbers <= 0", () => { + expect(convertSecondsToFormattedTime(-1)).toEqual("0s"); + expect(convertSecondsToFormattedTime(0)).toEqual("0s"); + }); + it("returns only seconds for values under a minute", () => { + expect(convertSecondsToFormattedTime(1)).toEqual("1s"); + expect(convertSecondsToFormattedTime(59)).toEqual("59s"); + }); + it("returns seconds and minutes for values under an hour", () => { + expect(convertSecondsToFormattedTime(60)).toEqual("1m00s"); + expect(convertSecondsToFormattedTime(61)).toEqual("1m01s"); + expect(convertSecondsToFormattedTime(3599)).toEqual("59m59s"); + }); + it("returns full time for values over an hour", () => { + expect(convertSecondsToFormattedTime(3600)).toEqual("1h00m00s"); + expect(convertSecondsToFormattedTime(3601)).toEqual("1h00m01s"); + }); +}); diff --git a/frontend/src/util/jquery.js b/frontend/src/util/jquery.js new file mode 100644 index 00000000..12a64fc6 --- /dev/null +++ b/frontend/src/util/jquery.js @@ -0,0 +1,8 @@ +/** + * Binding of the global jQuery variable for use within React. + * + * This should be used instead of '$', to address ESLint warnings relating to undefined global variables. + */ +const jQuery = window["$"]; + +export default jQuery; diff --git a/frontend/src/util/room-types.js b/frontend/src/util/room-types.js new file mode 100644 index 00000000..5cfe3887 --- /dev/null +++ b/frontend/src/util/room-types.js @@ -0,0 +1,7 @@ +export const ROOM_TYPE_TO_NAME_MAP = { + SERVER: "Server room", + HALLWAY: "Hallway", + OFFICE: "Office", + POWER: "Power room", + COOLING: "Cooling room" +}; diff --git a/frontend/src/util/simulation-load.js b/frontend/src/util/simulation-load.js new file mode 100644 index 00000000..95e17fed --- /dev/null +++ b/frontend/src/util/simulation-load.js @@ -0,0 +1,37 @@ +import { + SIM_HIGH_COLOR, + SIM_LOW_COLOR, + SIM_MID_HIGH_COLOR, + SIM_MID_LOW_COLOR +} from "./colors"; + +export const LOAD_NAME_MAP = { + LOAD: "computational load", + TEMPERATURE: "temperature", + MEMORY: "memory use" +}; + +export function convertLoadToSimulationColor(load) { + if (load <= 0.25) { + return SIM_LOW_COLOR; + } else if (load <= 0.5) { + return SIM_MID_LOW_COLOR; + } else if (load <= 0.75) { + return SIM_MID_HIGH_COLOR; + } else { + return SIM_HIGH_COLOR; + } +} + +export function getStateLoad(loadMetric, state) { + switch (loadMetric) { + case "LOAD": + return state.loadFraction; + case "TEMPERATURE": + return state.temperatureC / 100.0; + case "MEMORY": + return state.inUseMemoryMb / 10000.0; + default: + return -1; + } +} diff --git a/frontend/src/util/tile-calculations.js b/frontend/src/util/tile-calculations.js new file mode 100644 index 00000000..95886eeb --- /dev/null +++ b/frontend/src/util/tile-calculations.js @@ -0,0 +1,261 @@ +export function deriveWallLocations(tiles) { + const { verticalWalls, horizontalWalls } = getWallSegments(tiles); + return mergeWallSegments(verticalWalls, horizontalWalls); +} + +function getWallSegments(tiles) { + const verticalWalls = {}; + const horizontalWalls = {}; + + tiles.forEach(tile => { + const x = tile.positionX, + y = tile.positionY; + + for (let dX = -1; dX <= 1; dX++) { + for (let dY = -1; dY <= 1; dY++) { + if (Math.abs(dX) === Math.abs(dY)) { + continue; + } + + let doInsert = true; + for (let tileIndex in tiles) { + if ( + tiles[tileIndex].positionX === x + dX && + tiles[tileIndex].positionY === y + dY + ) { + doInsert = false; + break; + } + } + if (!doInsert) { + continue; + } + + if (dX === -1) { + if (verticalWalls[x] === undefined) { + verticalWalls[x] = []; + } + if (verticalWalls[x].indexOf(y) === -1) { + verticalWalls[x].push(y); + } + } else if (dX === 1) { + if (verticalWalls[x + 1] === undefined) { + verticalWalls[x + 1] = []; + } + if (verticalWalls[x + 1].indexOf(y) === -1) { + verticalWalls[x + 1].push(y); + } + } else if (dY === -1) { + if (horizontalWalls[y] === undefined) { + horizontalWalls[y] = []; + } + if (horizontalWalls[y].indexOf(x) === -1) { + horizontalWalls[y].push(x); + } + } else if (dY === 1) { + if (horizontalWalls[y + 1] === undefined) { + horizontalWalls[y + 1] = []; + } + if (horizontalWalls[y + 1].indexOf(x) === -1) { + horizontalWalls[y + 1].push(x); + } + } + } + } + }); + + return { verticalWalls, horizontalWalls }; +} + +function mergeWallSegments(vertical, horizontal) { + const result = []; + const walls = [vertical, horizontal]; + + for (let i = 0; i < 2; i++) { + const wallList = walls[i]; + for (let a in wallList) { + a = parseInt(a, 10); + + wallList[a].sort((a, b) => { + return a - b; + }); + + let startPos = wallList[a][0]; + const isHorizontal = i === 1; + + if (wallList[a].length === 1) { + const startPosX = isHorizontal ? startPos : a; + const startPosY = isHorizontal ? a : startPos; + result.push({ + startPosX, + startPosY, + isHorizontal, + length: 1 + }); + } else { + let consecutiveCount = 1; + for (let b = 0; b < wallList[a].length - 1; b++) { + if (b + 1 === wallList[a].length - 1) { + if (wallList[a][b + 1] - wallList[a][b] > 1) { + const startPosX = isHorizontal ? startPos : a; + const startPosY = isHorizontal ? a : startPos; + result.push({ + startPosX, + startPosY, + isHorizontal, + length: consecutiveCount + }); + consecutiveCount = 0; + startPos = wallList[a][b + 1]; + } + const startPosX = isHorizontal ? startPos : a; + const startPosY = isHorizontal ? a : startPos; + result.push({ + startPosX, + startPosY, + isHorizontal, + length: consecutiveCount + 1 + }); + break; + } else if (wallList[a][b + 1] - wallList[a][b] > 1) { + const startPosX = isHorizontal ? startPos : a; + const startPosY = isHorizontal ? a : startPos; + result.push({ + startPosX, + startPosY, + isHorizontal, + length: consecutiveCount + }); + startPos = wallList[a][b + 1]; + consecutiveCount = 0; + } + consecutiveCount++; + } + } + } + } + + return result; +} + +export function deriveValidNextTilePositions(rooms, selectedTiles) { + const result = [], + newPosition = { x: 0, y: 0 }; + let isSurroundingTile; + + selectedTiles.forEach(tile => { + const x = tile.positionX; + const y = tile.positionY; + result.push({ x, y }); + + for (let dX = -1; dX <= 1; dX++) { + for (let dY = -1; dY <= 1; dY++) { + if (Math.abs(dX) === Math.abs(dY)) { + continue; + } + + newPosition.x = x + dX; + newPosition.y = y + dY; + + isSurroundingTile = true; + for (let index in selectedTiles) { + if ( + selectedTiles[index].positionX === newPosition.x && + selectedTiles[index].positionY === newPosition.y + ) { + isSurroundingTile = false; + break; + } + } + + if ( + isSurroundingTile && + findPositionInRooms(rooms, newPosition.x, newPosition.y) === -1 + ) { + result.push({ x: newPosition.x, y: newPosition.y }); + } + } + } + }); + + return result; +} + +export function findPositionInPositions(positions, positionX, positionY) { + for (let i = 0; i < positions.length; i++) { + const position = positions[i]; + if (positionX === position.x && positionY === position.y) { + return i; + } + } + + return -1; +} + +export function findPositionInRooms(rooms, positionX, positionY) { + for (let i = 0; i < rooms.length; i++) { + const room = rooms[i]; + if (findPositionInTiles(room.tiles, positionX, positionY) !== -1) { + return i; + } + } + + return -1; +} + +function findPositionInTiles(tiles, positionX, positionY) { + let index = -1; + + for (let i = 0; i < tiles.length; i++) { + const tile = tiles[i]; + if (positionX === tile.positionX && positionY === tile.positionY) { + index = i; + break; + } + } + + return index; +} + +export function findTileWithPosition(tiles, positionX, positionY) { + for (let i = 0; i < tiles.length; i++) { + if (tiles[i].positionX === positionX && tiles[i].positionY === positionY) { + return tiles[i]; + } + } + + return null; +} + +export function calculateRoomListBounds(rooms) { + const min = { x: Number.MAX_VALUE, y: Number.MAX_VALUE }; + const max = { x: -1, y: -1 }; + + rooms.forEach(room => { + room.tiles.forEach(tile => { + if (tile.positionX < min.x) { + min.x = tile.positionX; + } + if (tile.positionY < min.y) { + min.y = tile.positionY; + } + + if (tile.positionX > max.x) { + max.x = tile.positionX; + } + if (tile.positionY > max.y) { + max.y = tile.positionY; + } + }); + }); + + max.x++; + max.y++; + + const center = { + x: min.x + (max.x - min.x) / 2.0, + y: min.y + (max.y - min.y) / 2.0 + }; + + return { min, center, max }; +} diff --git a/frontend/src/util/timeline.js b/frontend/src/util/timeline.js new file mode 100644 index 00000000..e20d5823 --- /dev/null +++ b/frontend/src/util/timeline.js @@ -0,0 +1,19 @@ +export function convertTickToPercentage(tick, maxTick) { + if (maxTick === 0) { + return "0%"; + } else if (tick > maxTick) { + return maxTick / (maxTick + 1) * 100 + "%"; + } + + return tick / (maxTick + 1) * 100 + "%"; +} + +export function getDatacenterIdOfTick(tick, sections) { + for (let i in sections.reverse()) { + if (tick >= sections[i].startTick) { + return sections[i].datacenterId; + } + } + + return -1; +} diff --git a/images/logo.png b/images/logo.png Binary files differnew file mode 100644 index 00000000..d743038b --- /dev/null +++ b/images/logo.png diff --git a/images/opendc-component-diagram.png b/images/opendc-component-diagram.png Binary files differnew file mode 100644 index 00000000..4aa535b9 --- /dev/null +++ b/images/opendc-component-diagram.png diff --git a/images/opendc-frontend-construction.PNG b/images/opendc-frontend-construction.PNG Binary files differnew file mode 100644 index 00000000..223e8d48 --- /dev/null +++ b/images/opendc-frontend-construction.PNG diff --git a/images/opendc-frontend-simulation-zoom.PNG b/images/opendc-frontend-simulation-zoom.PNG Binary files differnew file mode 100644 index 00000000..d7744926 --- /dev/null +++ b/images/opendc-frontend-simulation-zoom.PNG diff --git a/images/opendc-frontend-simulation.PNG b/images/opendc-frontend-simulation.PNG Binary files differnew file mode 100644 index 00000000..bbf4cbd6 --- /dev/null +++ b/images/opendc-frontend-simulation.PNG diff --git a/mongodb/Dockerfile b/mongodb/Dockerfile new file mode 100644 index 00000000..b4eb9dd1 --- /dev/null +++ b/mongodb/Dockerfile @@ -0,0 +1,5 @@ +FROM mongo:4.2.5 +MAINTAINER Jacob Burley <j.burley@vu.nl> + +# Import init script +ADD mongo-init-opendc-db.sh /docker-entrypoint-initdb.d
\ No newline at end of file diff --git a/mongodb/docker-compose.yml b/mongodb/docker-compose.yml new file mode 100644 index 00000000..aa54a74c --- /dev/null +++ b/mongodb/docker-compose.yml @@ -0,0 +1,30 @@ +version: "3" +services: + mongo: + build: + context: ./ + restart: on-failure + environment: + MONGO_INITDB_ROOT_USERNAME: root + MONGO_INITDB_ROOT_PASSWORD: rootpassword + MONGO_INITDB_DATABASE: admin + OPENDC_DB: opendc + OPENDC_DB_USERNAME: opendc + OPENDC_DB_PASSWORD: opendcpassword + ports: + - 27017:27017 + #volumes: + # - mongo-volume:/data/db + + mongo-express: + image: mongo-express + restart: on-failure + ports: + - 8082:8081 + environment: + ME_CONFIG_MONGODB_ADMINUSERNAME: root + ME_CONFIG_MONGODB_ADMINPASSWORD: rootpassword + +volumes: + mongo-volume: + external: false diff --git a/mongodb/mongo-init-opendc-db.sh b/mongodb/mongo-init-opendc-db.sh new file mode 100644 index 00000000..e7a787fe --- /dev/null +++ b/mongodb/mongo-init-opendc-db.sh @@ -0,0 +1,122 @@ +#!/bin/bash + +echo 'Creating opendc user and db' + +mongo opendc --host localhost \ + --port 27017 \ + -u "$MONGO_INITDB_ROOT_USERNAME" \ + -p "$MONGO_INITDB_ROOT_PASSWORD" \ + --authenticationDatabase admin \ + --eval "db.createUser({user: '$OPENDC_DB_USERNAME', pwd: '$OPENDC_DB_PASSWORD', roles:[{role:'dbOwner', db: '$OPENDC_DB'}]});" + +MONGO_CMD="mongo $OPENDC_DB -u $OPENDC_DB_USERNAME -p $OPENDC_DB_PASSWORD --authenticationDatabase $OPENDC_DB" + +echo 'Creating collections' + +$MONGO_CMD --eval 'db.createCollection("users");' +$MONGO_CMD --eval 'db.createCollection("simulations");' +$MONGO_CMD --eval 'db.createCollection("topologies");' +$MONGO_CMD --eval 'db.createCollection("experiments");' +$MONGO_CMD --eval 'db.createCollection("prefabs");' + +echo 'Loading test data' + +$MONGO_CMD --eval 'db.users.insertOne( + { + "googleId": "23483578932789231", + "email": "jorgos.andreadis@gmail.com", + "givenName": "Jorgos", + "familyName": "Andreadis", + "authorizations": [] + });' + +$MONGO_CMD --eval 'db.prefabs.insertOne( + { + "type": "rack", + "name": "testRack3", + "size": 42, + "depth": 42, + "author": "Jacob Burley", + "visibility": "public", + "children": [ + { + "type": "switch", + "ports": 48, + "powerDraw": 150, + "psus": 1, + "size": 1 + }, + { + "type": "chassis", + "size": 4, + "children": [ + { + "type": "mainboard", + "sockets": 1, + "dimmSlots": 4, + "nics": 1, + "pcieSlots": 2, + "children": [ + { + "type": "CPU", + "coreCount": 4, + "SMT": true, + "baseClk": 3.5, + "boostClk": 3.9, + "brand": "Intel", + "SKU": "i7-3770K", + "socket": "LGA1155", + "TDP": 77 + }, + { + "type": "DDR3", + "capacity": 4096, + "memfreq": 1333, + "ecc": false + }, + { + "type": "DDR3", + "capacity": 4096, + "memfreq": 1333, + "ecc": false + }, + { + "type": "DDR3", + "capacity": 4096, + "memfreq": 1333, + "ecc": false + }, + { + "type": "DDR3", + "capacity": 4096, + "memfreq": 1333, + "ecc": false + }, + { + "type": "GPU", + "VRAM": 8192, + "coreCount": 2304, + "brand": "AMD", + "technologies": "OpenCL", + "pcieGen": "3x16", + "tdp": 169, + "slots": 2 + } + ] + }, + { + "type": "PSU", + "wattage": 550, + "ac": true + }, + { + "type": "disk", + "size": 2000, + "interface": "SATA", + "media": "flash", + "formFactor": 2.5 + } + ] + } + ] + });' diff --git a/mongodb/prefab.py b/mongodb/prefab.py new file mode 100755 index 00000000..124f45e3 --- /dev/null +++ b/mongodb/prefab.py @@ -0,0 +1,112 @@ +#!/Users/jacobburley/thesis-src/opendc/mongodb/opendc_testing/bin/python3 +#Change shebang to /usr/bin/python3 before using with docker +# encoding: utf-8 +""" +prefab + +CLI frontend for viewing, modifying and creating prefabs in OpenDC. + +""" +import sys +import prefabs + +def usage(): + print("Usage: prefab add <prefab>: imports a prefab from JSON") + print(" list: lists all (public) prefabs") + print(" export <prefab> [json|yaml]: exports the specified prefab to the specified filetype (with JSON used by default)") + print(" clone <prefab> [new prefab name]: clones the specified prefab, giving the new prefab a name if specified") + print(" remove <prefab>: removes the specified prefab from the database") + +def interactive(): #interactive CLI mode: recommended + print("OpenDC Prefab CLI") + running = True + while(exit): + print(">", end=" ") + try: + command = input() + command = command.split() + except EOFError as e: + print("exit") + print("bye!") + exit() + except KeyboardInterrupt as KI: + print("\nbye!") + exit() + if(len(command) >= 1): + if(command[0] == "exit"): + print("bye!") + exit() + elif(command[0] == "list"): # decrypt + prefabs.list() + elif(command[0] == "help"): # decrypt + usage() + elif(command[0] == "add"): + if(len(command) == 3): + prefabs.add(command[1], command[2]) + else: + prefabs.add(command[1], None) + elif(command[0] == "clone"): + if(len(command) == 3): + prefabs.clone(command[1], command[2]) + else: + prefabs.clone(command[1], None) + elif(command[0] == "export"): + #print(sys.argv[2]) + prefabs.export(command[1], "json") + elif(command[0] == "remove"): + print("WARNING: Doing so will permanently remove the specified prefab. \nThis action CANNOT be undone. Please type the name of the prefab to confirm deletion.") + confirm = input() + if confirm == command[1]: + prefabs.remove(command[1]) + print(f'Prefab {command[1]} has been removed.') + else: + print("Confirmation failed. The prefab has not been removed.") + else: + print("prefabs: try 'help' for more information\n") + else: + print("prefabs: try 'help' for more information\n") + + +def main(): + if(len(sys.argv) >= 2): + if(sys.argv[1] == "list"): # decrypt + prefabs.list() + exit() + #elif(sys.argv[1] == "-e"): # encrypt + # encrypt(sys.argv[2], sys.argv[3], sys.argv[4]) + #elif(sys.argv[1] == "-v"): # verify + # verify(sys.argv[2], sys.argv[3], sys.argv[4]) + elif(sys.argv[1] == "help"): # decrypt + usage() + exit() + elif(sys.argv[1] == "add"): + if(sys.argv[3]): + prefabs.add(sys.argv[2], sys.argv[3]) + else: + prefabs.add(sys.argv[2]) + exit() + elif(sys.argv[1] == "export"): + #print(sys.argv[2]) + prefabs.export(sys.argv[2], "json") + exit() + elif(sys.argv[1] == "remove"): + print("WARNING: Doing so will permanently remove the specified prefab. \nThis action CANNOT be undone. Please type the name of the prefab to confirm deletion.") + confirm = input() + if confirm == sys.argv[2]: + prefabs.remove(sys.argv[2]) + print(f'Prefab {sys.argv[2]} has been removed.') + else: + print("Confirmation failed. The prefab has not been removed.") + exit() + else: + print("prefabs: try 'prefabs help' for more information\n") + elif(len(sys.argv) == 1): + interactive() + + else: + # print "Incorrect number of arguments!\n" + print("prefabs: try 'prefabs help' for more information\n") + + +if __name__ == "__main__": + main()
\ No newline at end of file diff --git a/mongodb/prefabs.py b/mongodb/prefabs.py new file mode 100755 index 00000000..f6f46cbc --- /dev/null +++ b/mongodb/prefabs.py @@ -0,0 +1,124 @@ +#!/Users/jacobburley/thesis-src/opendc/mongodb/opendc_testing/bin/python3 +#Change shebang to /usr/bin/python3 before using with docker +# encoding: utf-8 +""" +prefabs + +Python Library for interacting with mongoDB prefabs collection. + +""" +import urllib.parse +import pprint +import sys +import os +import json +import re +import ujson +#import pyyaml + +from pymongo import MongoClient +from bson.json_util import loads, dumps, RELAXED_JSON_OPTIONS, CANONICAL_JSON_OPTIONS + +#mongodb_opendc_db = os.environ['OPENDC_DB'] +#mongodb_opendc_user = os.environ['OPENDC_DB_USERNAME'] +#mongodb_opendc_password = os.environ['OPENDC_DB_PASSWORD'] + +#if mongodb_opendc_db == None or mongodb_opendc_user == None or mongodb_opendc_password == None: +# print("One or more environment variables are not set correctly. \nYou may experience issues connecting to the mongodb database.") + +user = urllib.parse.quote_plus('opendc') #TODO: replace this with environment variable +password = urllib.parse.quote_plus('opendcpassword') #TODO: same as above +database = urllib.parse.quote_plus('opendc') + +client = MongoClient('mongodb://%s:%s@localhost/default_db?authSource=%s' % (user, password, database)) +opendcdb = client.opendc +prefabs_collection = opendcdb.prefabs + + +def add(prefab_file, name): + if(re.match(r"\w+(\\\ \w*)*\.json", prefab_file)): + try: + with open(prefab_file, "r") as json_file: + json_prefab = json.load(json_file) + #print(json_prefab) + if name != None: + json_prefab["name"] = name + try: + prefab_id = prefabs_collection.insert(json_prefab) + except ConnectionFailure: + print("ERROR: Could not connect to the mongoDB database.") + except DuplicateKeyError: + print("ERROR: A prefab with the same unique ID already exists in the database. \nPlease remove the '_id' before trying again.\nYour prefab has not been imported.") + except: + print("ERROR: A general error has occurred. Your prefab has not been imported.") + if prefab_id != None: + if name != None: + print(f'Prefab "{name}" has been imported successfully.') + else: + print(f'Prefab "{prefab_file}" has been imported successfully.') + except FileNotFoundError: + print(f"ERROR: {prefab_file} could not be found in the specified path. No prefabs have been imported.") + elif(re.match(r"\w+(\\\ \w*)*\.yml", prefab_file)): + print("expecting a yaml file here") + #yaml + else: + print("The filetype provided is an unsupported filetype.") + #unsupported filetype + +def clone(prefab_name, new_name): + bson = prefabs_collection.find_one({'name': prefab_name}) + json_string = dumps(bson) #convert BSON representation to JSON + chosen_prefab = json.loads(json_string) #load as a JSON object + + chosen_prefab.pop("_id") # clean out our _id field from the export: mongo will generate a new one if this is imported back in + + if new_name != None: + chosen_prefab["name"] = new_name + try: + prefab_id = prefabs_collection.insert_one(chosen_prefab) + except ConnectionFailure: + print("ERROR: Could not connect to the mongoDB database.") + except: + print("ERROR: A general error has occurred. Your selected prefab has not been cloned.") + if prefab_id != None: + if new_name != None: + print(f'Prefab "{prefab_name}" has been cloned successfully as {new_name}.') + else: + print(f'Prefab "{prefab_name}" has been cloned successfully.') + +def export(prefab_name, type): + bson = prefabs_collection.find_one({'name': prefab_name}) + json_string = dumps(bson) #convert BSON representation to JSON + chosen_prefab = json.loads(json_string) #load as a JSON object + + chosen_prefab.pop("_id") # clean out our _id field from the export: mongo will generate a new one if this is imported back in + + with open(f'{prefab_name}.json', 'w', encoding='utf8') as f: + json.dump(chosen_prefab, f, ensure_ascii=False, indent=4) + print(f'Prefab {prefab_name} written to {os.getcwd()}/{prefab_name}.json.') + #pprint.pprint(json_string) + #pprint.pprint(json.loads(str(json_string))) + +def list(): + #TODO: why does it output in single quotations? + cursor = prefabs_collection.find() + prefabs = [] + for record in cursor: + #pprint.pprint(record) + #print(record) + json_string = dumps(record, json_options=RELAXED_JSON_OPTIONS) ##pymongo retrieves BSON objects, which need to be converted to json for pythons json module + prefabs.append(json.loads(json_string)) + + #print(f'There are {str(len(prefabs))} prefabs in the database. They are:') + print("Name Author") + for prefab in prefabs: + if(prefab['visibility'] == "private"): + continue + print(f"{prefab['name']} {prefab['author']}") + #pprint.pprint(prefab) + + +def remove(prefab_name): + prefabs_collection.delete_one({'name': prefab_name}) + + diff --git a/opendc-api-spec.yml b/opendc-api-spec.yml new file mode 100644 index 00000000..56f1d529 --- /dev/null +++ b/opendc-api-spec.yml @@ -0,0 +1,999 @@ +swagger: '2.0' +info: + version: 1.0.0 + title: OpenDC API + description: 'OpenDC is an open-source datacenter simulator for education, featuring real-time online collaboration, diverse simulation models, and detailed performance feedback statistics.' +host: opendc.org +basePath: /v2 +schemes: + - https + +paths: + '/users': + get: + tags: + - users + description: Search for a User using their email address. + parameters: + - name: email + in: query + description: User's email address. + required: true + type: string + responses: + '200': + description: Successfully searched Users. + schema: + $ref: '#/definitions/User' + '400': + description: Missing or incorrectly typed parameter. + '401': + description: Unauthorized. + '404': + description: User not found. + post: + tags: + - users + description: Add a new User. + parameters: + - name: user + in: body + description: The new User. + required: true + schema: + $ref: '#/definitions/User' + responses: + '200': + description: Successfully added User. + schema: + $ref: '#/definitions/User' + '400': + description: Missing or incorrectly typed parameter. + '401': + description: Unauthorized. + '409': + description: User already exists. + '/users/{userId}': + get: + tags: + - users + description: Get this User. + parameters: + - name: userId + in: path + description: User's ID. + required: true + type: string + responses: + '200': + description: Successfully retrieved User. + schema: + $ref: '#/definitions/User' + '400': + description: Missing or incorrectly typed parameter. + '401': + description: Unauthorized. + '404': + description: User not found. + put: + tags: + - users + description: Update this User's given name and/ or family name. + parameters: + - name: userId + in: path + description: User's ID. + required: true + type: string + - name: user + in: body + description: User's new properties. + required: true + schema: + properties: + givenName: + type: string + familyName: + type: string + responses: + '200': + description: Successfully updated User. + schema: + $ref: '#/definitions/User' + '400': + description: Missing or incorrectly typed parameter. + '401': + description: Unauthorized. + '403': + description: Forbidden from updating User. + '404': + description: User not found. + delete: + tags: + - users + description: Delete this User. + parameters: + - name: userId + in: path + description: User's ID. + required: true + type: string + responses: + '200': + description: Successfully deleted User. + schema: + $ref: '#/definitions/User' + '400': + description: Missing or incorrectly typed parameter. + '401': + description: Unauthorized. + '403': + description: Forbidden from deleting User. + '404': + description: User not found. + '/simulations': + post: + tags: + - simulations + description: Add a Simulation. + parameters: + - name: simulation + in: body + description: The new Simulation. + required: true + schema: + properties: + name: + type: string + responses: + '200': + description: Successfully added Simulation. + schema: + $ref: '#/definitions/Simulation' + '400': + description: Missing or incorrectly typed parameter. + '401': + description: Unauthorized. + '/simulations/{simulationId}': + get: + tags: + - simulations + description: Get this Simulation. + parameters: + - name: simulationId + in: path + description: Simulation's ID. + required: true + type: string + responses: + '200': + description: Successfully retrieved Simulation. + schema: + $ref: '#/definitions/Simulation' + '400': + description: Missing or incorrectly typed parameter. + '401': + description: Unauthorized. + '403': + description: Forbidden from retrieving Simulation. + '404': + description: Simulation not found + put: + tags: + - simulations + description: Update this Simulation. + parameters: + - name: simulationId + in: path + description: Simulation's ID. + required: true + type: string + - name: simulation + in: body + description: Simulation's new properties. + required: true + schema: + properties: + simulation: + $ref: '#/definitions/Simulation' + responses: + '200': + description: Successfully updated Simulation. + schema: + $ref: '#/definitions/Simulation' + '400': + description: Missing or incorrectly typed parameter. + '401': + description: Unauthorized. + '403': + description: Forbidden from updating Simulation. + '404': + description: Simulation not found. + delete: + tags: + - simulations + description: Delete this simulation. + parameters: + - name: simulationId + in: path + description: Simulation's ID. + required: true + type: string + responses: + '200': + description: Successfully deleted Simulation. + schema: + $ref: '#/definitions/Simulation' + '400': + description: Missing or incorrectly typed parameter. + '401': + description: Unauthorized. + '403': + description: Forbidden from deleting Simulation. + '404': + description: Simulation not found. + '/simulations/{simulationId}/authorizations': + get: + tags: + - simulations + description: Get this Simulation's Authorizations. + parameters: + - name: simulationId + in: path + description: Simulation's ID. + required: true + type: string + responses: + '200': + description: Successfully retrieved Simulation's Authorizations. + schema: + type: array + items: + type: object + properties: + userId: + type: string + simulationId: + type: string + authorizationLevel: + type: string + '400': + description: Missing or incorrectly typed parameter. + '401': + description: Unauthorized. + '403': + description: Forbidden from retrieving this Simulation's Authorizations. + '404': + description: Simulation not found. + '/simulations/{simulationId}/topologies': + post: + tags: + - simulations + description: Add a Topology. + parameters: + - name: simulationId + in: path + description: Simulation's ID. + required: true + type: string + - name: topology + in: body + description: The new Topology. + required: true + schema: + properties: + topology: + $ref: '#/definitions/Topology' + responses: + '200': + description: Successfully added Topology. + schema: + $ref: '#/definitions/Topology' + '400': + description: Missing or incorrectly typed parameter. + '401': + description: Unauthorized. + '/topologies/{topologyId}': + get: + tags: + - topologies + description: Get this Topology. + parameters: + - name: topologyId + in: path + description: Topology's ID. + required: true + type: string + responses: + '200': + description: Successfully retrieved Topology. + schema: + $ref: '#/definitions/Topology' + '400': + description: Missing or incorrectly typed parameter. + '401': + description: Unauthorized. + '403': + description: Forbidden from retrieving Topology. + '404': + description: Topology not found. + put: + tags: + - topologies + description: Update this Topology's name. + parameters: + - name: topologyId + in: path + description: Topology's ID. + required: true + type: string + - name: topology + in: body + description: Topology's new properties. + required: true + schema: + properties: + topology: + $ref: '#/definitions/Topology' + responses: + '200': + description: Successfully updated Topology. + schema: + $ref: '#/definitions/Topology' + '400': + description: Missing or incorrectly typed parameter. + '401': + description: Unauthorized. + '403': + description: Forbidden from updating Topology. + '404': + description: Topology not found. + delete: + tags: + - topologies + description: Delete this Topology. + parameters: + - name: topologyId + in: path + description: Topology's ID. + required: true + type: string + responses: + '200': + description: Successfully deleted Topology. + schema: + $ref: '#/definitions/Topology' + '400': + description: Missing or incorrectly typed parameter. + '401': + description: Unauthorized. + '403': + description: Forbidden from deleting Topology. + '404': + description: Topology not found. + '/simulations/{simulationId}/experiments': + get: + tags: + - experiments + description: Get this Simulation's Experiments. + parameters: + - name: simulationId + in: path + description: Simulation's ID. + required: true + type: string + responses: + '200': + description: Successfully retrieved Experiments. + schema: + type: array + items: + $ref: '#/definitions/Experiment' + '400': + description: Missing or incorrectly typed parameter. + '401': + description: Unauthorized. + '403': + description: Forbidden from retrieving Simulation's Experiments. + '404': + description: Simulation not found. + post: + tags: + - experiments + description: Add a new Experiment for this Simulation. + parameters: + - name: simulationId + in: path + description: Simulation's ID. + required: true + type: string + - name: experiment + in: body + description: Experiment to add to this Simulation. + required: true + schema: + $ref: '#/definitions/Experiment' + responses: + '200': + description: Successfully added new Experiment. + schema: + $ref: '#/definitions/Experiment' + '400': + description: Missing or incorrectly typed parameter. + '401': + description: Unauthorized. + '403': + description: Forbidden from adding an Experiment to this Simulation. + '404': + description: 'Simulation, Topology, Scheduler or Trace not found.' + '/experiments/{experimentId}': + get: + tags: + - experiments + description: Get this Experiment. + parameters: + - name: experimentId + in: path + description: Experiment's ID. + required: true + type: string + responses: + '200': + description: Successfully retrieved Experiment. + schema: + $ref: '#/definitions/Experiment' + '400': + description: Missing or incorrectly typed parameter. + '401': + description: Unauthorized. + '403': + description: Forbidden from retrieving Experiment. + '404': + description: Experiment not found. + put: + tags: + - experiments + description: "Update this Experiment's Topology, Trace, Scheduler, and/or name." + parameters: + - name: experimentId + in: path + description: Experiment's ID. + required: true + type: string + - name: experiment + in: body + description: Experiment's new properties. + required: true + schema: + properties: + topologyId: + type: string + traceId: + type: string + schedulerName: + type: string + name: + type: string + responses: + '200': + description: Successfully updated Experiment. + schema: + $ref: '#/definitions/Experiment' + '400': + description: Missing or incorrectly typed parameter. + '401': + description: Unauthorized. + '403': + description: Forbidden from updating Experiment. + '404': + description: 'Experiment, Topology, Trace, or Scheduler not found.' + delete: + tags: + - experiments + description: Delete this Experiment. + parameters: + - name: experimentId + in: path + description: Experiment's ID. + required: true + type: string + responses: + '200': + description: Successfully deleted Experiment. + schema: + $ref: '#/definitions/Experiment' + '401': + description: Unauthorized. + '403': + description: Forbidden from deleting Experiment. + '404': + description: Experiment not found. + '/experiments/{experimentId}/last-simulated-tick': + get: + tags: + - simulations + description: Get this Experiment's last simulated tick. + parameters: + - name: experimentId + in: path + description: Experiment's ID. + required: true + type: string + responses: + '200': + description: Successfully retrieved Experiment's last simulated tick. + schema: + properties: + lastSimulatedTick: + type: integer + '400': + description: Missing or incorrectly typed parameter. + '401': + description: Unauthorized + '403': + description: Forbidden from getting this simulation + '404': + description: Simulation not found + '/experiments/{experimentId}/machine-states': + get: + tags: + - simulations + - states + description: Get this experiment's Machine States. + parameters: + - name: experimentId + in: path + description: Experiment's ID. + required: true + type: string + - name: tick + in: query + description: Tick to filter on. + required: false + type: integer + - name: machineId + in: query + description: Machine's ID to filter on. + required: false + type: string + - name: rackId + in: query + description: Rack's ID to filter on. + required: false + type: string + - name: roomId + in: query + description: Room's ID to filter on. + required: false + type: string + responses: + '200': + description: Successfully retrieved Machine States. + schema: + type: array + items: + $ref: '#/definitions/MachineState' + '400': + description: Missing or incorrectly typed parameter. + '401': + description: Unauthorized. + '403': + description: Forbidden from getting Experiment's Machine States. + '404': + description: 'Experiment, Machine, Rack, Room or Tick not found.' + '/experiments/{experimentId}/rack-states': + get: + tags: + - simulations + - states + description: Get this Experiment's Rack States. + parameters: + - name: experimentId + in: path + description: Experiment's ID. + required: true + type: string + - name: tick + in: query + description: Tick to filter on. + required: false + type: integer + - name: rackId + in: query + description: Rack's ID to filter on. + required: false + type: string + - name: roomId + in: query + description: Room's ID to filter on. + required: false + type: string + responses: + '200': + description: Successfully retrieved Rack States. + schema: + type: array + items: + $ref: '#/definitions/RackState' + '400': + description: Missing or incorrectly typed parameter. + '401': + description: Unauthorized. + '403': + description: Forbidden from getting Experiment's Rack States. + '404': + description: 'Experiment, Room, Rack or Tick not found.' + '/experiments/{experimentId}/room-states': + get: + tags: + - simulations + - states + description: Get this Experiment's Room States. + parameters: + - name: experimentId + in: path + description: Experiment's ID. + required: true + type: string + - name: tick + in: query + description: Tick to filter on. + required: false + type: integer + - name: roomId + in: query + description: Room's ID to filter on. + required: false + type: string + responses: + '200': + description: Successfully retrieved Room States. + schema: + type: array + items: + $ref: '#/definitions/RoomState' + '400': + description: Missing or incorrectly typed parameter. + '401': + description: Unauthorized. + '403': + description: Forbidden from getting Experiment's Room States. + '404': + description: 'Experiment, Room or Tick not found.' + '/experiments/{experimentId}/task-states': + get: + tags: + - simulations + - states + description: Get this Experiment's Task States. + parameters: + - name: experimentId + in: path + description: Experiment's ID. + required: true + type: string + - name: tick + in: query + description: Tick to filter on. + required: false + type: integer + - name: taskId + in: query + description: Task's ID to filter on. + required: false + type: string + - name: machineId + in: query + description: Machine's ID to filter on. + required: false + type: string + - name: rackId + in: query + description: ID the rack whose machines' task states to get + required: false + type: string + - name: roomId + in: query + description: ID of the room whose racks' machines' states to get + required: false + type: string + responses: + '200': + description: Successfully retrieved Task States. + schema: + type: array + items: + $ref: '#/definitions/TaskState' + '400': + description: Missing or incorrectly typed parameter. + '401': + description: Unauthorized. + '403': + description: Forbidden from retrieving Experiment's Task States. + '404': + description: 'Experiment, Tick, Task, Machine, Rack or Room not found.' + /schedulers: + get: + tags: + - experiments + description: Get all available Schedulers + responses: + '200': + description: Successfully retrieved Schedulers. + schema: + type: array + items: + $ref: '#/definitions/Scheduler' + '401': + description: Unauthorized. + /traces: + get: + tags: + - experiments + description: Get all available Traces (non-populated). + responses: + '200': + description: Successfully retrieved Traces (non-populated). + schema: + type: array + items: + type: object + properties: + _id: + type: string + name: + type: string + '401': + description: Unauthorized. + '/traces/{traceId}': + get: + tags: + - experiments + description: Get this Trace. + parameters: + - name: traceId + in: path + description: Trace's ID. + required: true + type: string + responses: + '200': + description: Successfully retrieved Trace. + schema: + $ref: '#/definitions/Trace' + '401': + description: Unauthorized. + '404': + description: Trace not found. + +definitions: + Experiment: + type: object + properties: + _id: + type: string + simulationId: + type: string + topologyId: + type: string + traceId: + type: string + schedulerName: + type: string + name: + type: string + MachineState: + type: object + properties: + _id: + type: string + machineId: + type: string + experimentId: + type: string + tick: + type: integer + inUseMemoryMb: + type: integer + loadFraction: + type: number + format: float + RackState: + type: object + properties: + _id: + type: string + rackId: + type: string + experimentId: + type: string + tick: + type: integer + inUseMemoryMb: + type: integer + loadFraction: + type: number + format: float + RoomState: + type: object + properties: + _id: + type: string + roomId: + type: string + experimentId: + type: string + tick: + type: integer + inUseMemoryMb: + type: integer + loadFraction: + type: number + format: float + Scheduler: + type: object + properties: + name: + type: string + Simulation: + type: object + properties: + _id: + type: string + name: + type: string + datetimeCreated: + type: string + format: dateTime + datetimeLastEdited: + type: string + format: dateTime + topologyIds: + type: array + items: + type: string + TaskState: + type: object + properties: + _id: + type: string + taskId: + type: string + experimentId: + type: string + tick: + type: integer + flopsLeft: + type: integer + Topology: + type: object + properties: + _id: + type: string + name: + type: string + rooms: + type: array + items: + type: object + properties: + _id: + type: string + name: + type: string + tiles: + type: array + items: + type: object + properties: + _id: + type: string + positionX: + type: integer + positionY: + type: integer + object: + type: object + properties: + capacity: + type: integer + powerCapacityW: + type: integer + machines: + type: array + items: + type: object + properties: + position: + type: integer + cpuItems: + type: array + items: + type: object + properties: + name: + type: string + clockRateMhz: + type: integer + numberOfCores: + type: integer + gpuItems: + type: array + items: + type: object + properties: + name: + type: string + clockRateMhz: + type: integer + numberOfCores: + type: integer + memoryItems: + type: array + items: + type: object + properties: + name: + type: string + speedMbPerS: + type: integer + sizeMb: + type: integer + storageItems: + type: array + items: + type: integer + properties: + name: + type: string + speedMbPerS: + type: integer + sizeMb: + type: integer + Trace: + type: object + properties: + _id: + type: string + name: + type: string + jobs: + type: array + items: + type: object + properties: + _id: + type: string + name: + type: string + tasks: + type: array + items: + type: object + properties: + startTick: + type: integer + totalFlopCount: + type: integer + User: + type: object + properties: + _id: + type: string + googleId: + type: integer + email: + type: string + givenName: + type: string + familyName: + type: string + authorizations: + type: array + items: + type: object + properties: + simulationId: + type: string + authorizationLevel: + type: string diff --git a/simulator/.DS_Store b/simulator/.DS_Store Binary files differdeleted file mode 100644 index e553180f..00000000 --- a/simulator/.DS_Store +++ /dev/null diff --git a/web-server/.gitignore b/web-server/.gitignore new file mode 100644 index 00000000..fef0da65 --- /dev/null +++ b/web-server/.gitignore @@ -0,0 +1,15 @@ +.DS_Store +*.pyc +*.pyo +venv +venv* +dist +build +*.egg +*.egg-info +_mailinglist +.tox +.cache/ +.idea/ +config.json +test.json diff --git a/web-server/.gitlab-ci.yml b/web-server/.gitlab-ci.yml new file mode 100644 index 00000000..c8c07a56 --- /dev/null +++ b/web-server/.gitlab-ci.yml @@ -0,0 +1,27 @@ +image: "python:3.8" + +variables: + PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip" + +cache: + paths: + - .cache/pip + +stages: + - static-analysis + - test + +static-analysis: + stage: static-analysis + allow_failure: true + script: + - python --version + - pip install -r requirements.txt + - pylint opendc + +test: + stage: test + script: + - python --version + - pip install -r requirements.txt + - pytest opendc diff --git a/web-server/.pylintrc b/web-server/.pylintrc new file mode 100644 index 00000000..c7855f0e --- /dev/null +++ b/web-server/.pylintrc @@ -0,0 +1,519 @@ +[MASTER] + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. +extension-pkg-whitelist= + +# Specify a score threshold to be exceeded before program exits with error. +fail-under=10 + +# Add files or directories to the blacklist. They should be base names, not +# paths. +ignore=CVS + +# Add files or directories matching the regex patterns to the blacklist. The +# regex matches against base names, not paths. +ignore-patterns= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the +# number of processors available to use. +jobs=1 + +# Control the amount of potential inferred values when inferring a single +# object. This can help the performance when dealing with large functions or +# complex, nested conditions. +limit-inference-results=100 + +# List of plugins (as comma separated values of python module names) to load, +# usually to register additional checkers. +load-plugins= + +# Pickle collected data for later comparisons. +persistent=yes + +# When enabled, pylint would attempt to guess common misconfiguration and emit +# user-friendly hints instead of false-positive error messages. +suggestion-mode=yes + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED. +confidence= + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once). You can also use "--disable=all" to +# disable everything first and then reenable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use "--disable=all --enable=classes +# --disable=W". +disable=duplicate-code, + missing-module-docstring, + invalid-name + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). See also the "--disable" option for examples. +enable=c-extension-no-member + + +[REPORTS] + +# Python expression which should return a score less than or equal to 10. You +# have access to the variables 'error', 'warning', 'refactor', and 'convention' +# which contain the number of messages in each category, as well as 'statement' +# which is the total number of statements analyzed. This score is used by the +# global evaluation report (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details. +#msg-template= + +# Set the output format. Available formats are text, parseable, colorized, json +# and msvs (visual studio). You can also give a reporter class, e.g. +# mypackage.mymodule.MyReporterClass. +output-format=text + +# Tells whether to display a full report or only the messages. +reports=no + +# Activate the evaluation score. +score=yes + + +[REFACTORING] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 + +# Complete name of functions that never returns. When checking for +# inconsistent-return-statements if a never returning function is called then +# it will be considered as an explicit return statement and no message will be +# printed. +never-returning-functions=sys.exit + + +[LOGGING] + +# The type of string formatting that logging methods do. `old` means using % +# formatting, `new` is for `{}` formatting. +logging-format-style=old + +# Logging modules to check that the string format arguments are in logging +# function parameter format. +logging-modules=logging + + +[SPELLING] + +# Limits count of emitted suggestions for spelling mistakes. +max-spelling-suggestions=4 + +# Spelling dictionary name. Available dictionaries: none. To make it work, +# install the python-enchant package. +spelling-dict= + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains the private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to the private dictionary (see the +# --spelling-private-dict-file option) instead of raising a message. +spelling-store-unknown-words=no + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME, + XXX, + TODO + +# Regular expression of note tags to take in consideration. +#notes-rgx= + + +[TYPECHECK] + +# List of decorators that produce context managers, such as +# contextlib.contextmanager. Add to this list to register other decorators that +# produce valid context managers. +contextmanager-decorators=contextlib.contextmanager + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members= + +# Tells whether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# Tells whether to warn about missing members when the owner of the attribute +# is inferred to be None. +ignore-none=yes + +# This flag controls whether pylint should warn about no-member and similar +# checks whenever an opaque object is returned when inferring. The inference +# can return multiple potential results while evaluating a Python object, but +# some branches might not be evaluated, which results in partial inference. In +# that case, it might be useful to still emit no-member and other checks for +# the rest of the inferred objects. +ignore-on-opaque-inference=yes + +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. +ignored-classes=optparse.Values,thread._local,_thread._local + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis). It +# supports qualified module names, as well as Unix pattern matching. +ignored-modules= + +# Show a hint with possible names when a member name was not found. The aspect +# of finding the hint is based on edit distance. +missing-member-hint=yes + +# The minimum edit distance a name should have in order to be considered a +# similar match for a missing member name. +missing-member-hint-distance=1 + +# The total number of similar names that should be taken in consideration when +# showing a hint for a missing member. +missing-member-max-choices=1 + +# List of decorators that change the signature of a decorated function. +signature-mutators= + + +[VARIABLES] + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid defining new builtins when possible. +additional-builtins= + +# Tells whether unused global variables should be treated as a violation. +allow-global-unused-variables=yes + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_, + _cb + +# A regular expression matching the name of dummy variables (i.e. expected to +# not be used). +dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore. +ignored-argument-names=_.*|^ignored_|^unused_ + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io + + +[FORMAT] + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )?<?https?://\S+>?$ + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Maximum number of characters on a single line. +max-line-length=120 + +# Maximum number of lines in a module. +max-module-lines=1000 + +# List of optional constructs for which whitespace checking is disabled. `dict- +# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. +# `trailing-comma` allows a space between comma and closing bracket: (a, ). +# `empty-line` allows space-only lines. +no-space-check=trailing-comma, + dict-separator + +# Allow the body of a class to be on the same line as the declaration if body +# contains single statement. +single-line-class-stmt=no + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + + +[SIMILARITIES] + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + +# Ignore imports when computing similarities. +ignore-imports=no + +# Minimum lines number of a similarity. +min-similarity-lines=4 + + +[BASIC] + +# Naming style matching correct argument names. +argument-naming-style=snake_case + +# Regular expression matching correct argument names. Overrides argument- +# naming-style. +#argument-rgx= + +# Naming style matching correct attribute names. +attr-naming-style=snake_case + +# Regular expression matching correct attribute names. Overrides attr-naming- +# style. +#attr-rgx= + +# Bad variable names which should always be refused, separated by a comma. +bad-names=foo, + bar, + baz, + toto, + tutu, + tata + +# Bad variable names regexes, separated by a comma. If names match any regex, +# they will always be refused +bad-names-rgxs= + +# Naming style matching correct class attribute names. +class-attribute-naming-style=any + +# Regular expression matching correct class attribute names. Overrides class- +# attribute-naming-style. +#class-attribute-rgx= + +# Naming style matching correct class names. +class-naming-style=PascalCase + +# Regular expression matching correct class names. Overrides class-naming- +# style. +#class-rgx= + +# Naming style matching correct constant names. +const-naming-style=UPPER_CASE + +# Regular expression matching correct constant names. Overrides const-naming- +# style. +#const-rgx= + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + +# Naming style matching correct function names. +function-naming-style=snake_case + +# Regular expression matching correct function names. Overrides function- +# naming-style. +#function-rgx= + +# Good variable names which should always be accepted, separated by a comma. +good-names=i, + j, + k, + ex, + Run, + _ + +# Good variable names regexes, separated by a comma. If names match any regex, +# they will always be accepted +good-names-rgxs= + +# Include a hint for the correct naming format with invalid-name. +include-naming-hint=no + +# Naming style matching correct inline iteration names. +inlinevar-naming-style=any + +# Regular expression matching correct inline iteration names. Overrides +# inlinevar-naming-style. +#inlinevar-rgx= + +# Naming style matching correct method names. +method-naming-style=snake_case + +# Regular expression matching correct method names. Overrides method-naming- +# style. +#method-rgx= + +# Naming style matching correct module names. +module-naming-style=snake_case + +# Regular expression matching correct module names. Overrides module-naming- +# style. +#module-rgx= + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=^_ + +# List of decorators that produce properties, such as abc.abstractproperty. Add +# to this list to register other decorators that produce valid properties. +# These decorators are taken in consideration only for invalid-name. +property-classes=abc.abstractproperty + +# Naming style matching correct variable names. +variable-naming-style=snake_case + +# Regular expression matching correct variable names. Overrides variable- +# naming-style. +#variable-rgx= + + +[STRING] + +# This flag controls whether inconsistent-quotes generates a warning when the +# character used as a quote delimiter is used inconsistently within a module. +check-quote-consistency=no + +# This flag controls whether the implicit-str-concat should generate a warning +# on implicit string concatenation in sequences defined over several lines. +check-str-concat-over-line-jumps=no + + +[IMPORTS] + +# List of modules that can be imported at any level, not just the top level +# one. +allow-any-import-level= + +# Allow wildcard imports from modules that define __all__. +allow-wildcard-with-all=no + +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + +# Deprecated modules which should not be used, separated by a comma. +deprecated-modules=optparse,tkinter.tix + +# Create a graph of external dependencies in the given file (report RP0402 must +# not be disabled). +ext-import-graph= + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled). +import-graph= + +# Create a graph of internal dependencies in the given file (report RP0402 must +# not be disabled). +int-import-graph= + +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant + +# Couples of modules and preferred modules, separated by a comma. +preferred-modules= + + +[CLASSES] + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__, + __new__, + setUp, + __post_init__ + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict, + _fields, + _replace, + _source, + _make + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=cls + + +[DESIGN] + +# Maximum number of arguments for function / method. +max-args=5 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Maximum number of boolean expressions in an if statement (see R0916). +max-bool-expr=5 + +# Maximum number of branch for function / method body. +max-branches=12 + +# Maximum number of locals for function / method body. +max-locals=15 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + +# Maximum number of return / yield for function / method body. +max-returns=6 + +# Maximum number of statements in function / method body. +max-statements=50 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "BaseException, Exception". +overgeneral-exceptions=BaseException, + Exception diff --git a/web-server/.style.yapf b/web-server/.style.yapf new file mode 100644 index 00000000..f5c26c57 --- /dev/null +++ b/web-server/.style.yapf @@ -0,0 +1,3 @@ +[style] +based_on_style = pep8 +column_limit=120 diff --git a/web-server/LICENSE.md b/web-server/LICENSE.md new file mode 100644 index 00000000..57288ae2 --- /dev/null +++ b/web-server/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 atlarge-research + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/web-server/OpenDC.postman_collection.json b/web-server/OpenDC.postman_collection.json new file mode 100644 index 00000000..c34dc310 --- /dev/null +++ b/web-server/OpenDC.postman_collection.json @@ -0,0 +1,61 @@ +{ + "variables": [], + "info": { + "name": "OpenDC", + "_postman_id": "e8b68f59-29cb-71d0-6237-b22932c40f9c", + "description": "Sample requests for developing the OpenDC API", + "schema": "https://schema.getpostman.com/json/collection/v2.0.0/collection.json" + }, + "item": [ + { + "name": "Create New Simulation", + "request": { + "url": "localhost:8081/api/v1/simulations", + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "" + }, + { + "key": "auth-token", + "value": "PUT YOUR AUTH TOKEN HERE", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"simulation\": {\r\n \"name\": \"Simulation Name\"\r\n }\r\n}" + }, + "description": "" + }, + "response": [] + }, + { + "name": "Create New User", + "request": { + "url": "localhost:8081/api/v1/users", + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "" + }, + { + "key": "auth-token", + "value": "PUT YOUR AUTH TOKEN HERE", + "description": "" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"user\": {\r\n \"email\": \"email@example.com\"\r\n }\r\n}" + }, + "description": "" + }, + "response": [] + } + ] +}
\ No newline at end of file diff --git a/web-server/README.md b/web-server/README.md new file mode 100644 index 00000000..04146f59 --- /dev/null +++ b/web-server/README.md @@ -0,0 +1,130 @@ +<h1 align="center"> + <img src="images/logo.png" width="100" alt="OpenDC"> + <br> + OpenDC Web Server +</h1> +<p align="center"> +Collaborative Datacenter Simulation and Exploration for Everybody +</p> + +The OpenDC web server is the bridge between OpenDC's frontend and database. It is built with Flask/SocketIO in Python and implements the OpenAPI-compliant [OpenDC API specification](https://github.com/atlarge-research/opendc/blob/master/opendc-api-spec.json). + +This document explains a high-level view of the web server architecture ([jump](#architecture)), and describes how to set up the web server for local development ([jump](#setup-for-local-development)). + +## Architecture + +The following diagram shows a high-level view of the architecture of the OpenDC web server. Squared-off colored boxes indicate packages (colors become more saturated as packages are nested); rounded-off boxes indicate individual components; dotted lines indicate control flow; and solid lines indicate data flow. + + + +The OpenDC API is implemented by the `Main Server Loop`, which is the only component in the base package. + +### Util Package + +The `Util` package handles several miscellaneous tasks: + +* `REST`: Parses SockerIO messages into `Request` objects, and calls the appropriate `API` endpoint to get a `Response` object to return to the `Main Server Loop`. +* `Param Checker`: Recursively checks whether required `Request` parameters are present and correctly typed. +* `Exceptions`: Holds definitions for exceptions used throughout the web server. +* `Database API`: Wraps SQLite functionality used by `Models` to read themselves from/ write themselves into the database. + +### API Package + +The `API` package contains the logic for the HTTP methods in each API endpoint. Packages are structured to mirror the API: the code for the endpoint `GET simulations/authorizations`, for example, would be located at the `Endpoint` inside the `authorizations` package, inside the `simulations` package (so at `api/simulations/authorizations/endpoint.py`). + +An `Endpoint` contains methods for each HTTP method it supports, which takes a request as input (such as `def GET(request):`). Typically, such a method checks whether the parameters were passed correctly (using the `Param Checker`); fetches some model from the database; checks whether the data exists and is accessible by the user who made the request; possibly modifies this data and writes it back to the database; and returns a JSON representation of the model. + +The `REST` component dynamically imports the appropriate method from the appropriate `Endpoint`, according to request it receives, and executes it. + +### Models Package + +The `Models` package contains the logic for mapping Python objects to their database representations. This involves an abstract `model` which has methods to `read`, `insert`, `update` and `delete` objects. Extensions of `model`, such as a `User` or `Simulation`, specify some metadata such as their tabular representation in the database and how they map to a JSON object, which the code in `model` uses in the database interaction methods. + +`Endpoint`s import these `Models` and use them to execute requests. + +## Setup for Local Development + +The following steps will guide you through setting up the OpenDC web server locally for development. To test individual endpoints, edit `static/index.html`. This guide was tested and developed on Windows 10. + +### Local Setup + +#### Install requirements + +Make sure you have Python 2.7 installed (if not, get it [here](https://www.python.org/)), as well as pip (if not, get it [here](https://pip.pypa.io/en/stable/installing/)). Then run the following to install the requirements. + +```bash +python setup.py install +``` + +The web server also requires MariaDB >= 10.1. Instructions to install MariaDB can be found [here](https://mariadb.com/kb/en/mariadb/getting-installing-and-upgrading-mariadb/). The Docker image can be found [here](https://hub.docker.com/_/mariadb/). + +#### Get the code + +Clone both this repository and the main OpenDC repository, from the same base directory. + +```bash +git clone https://github.com/atlarge-research/opendc-web-server.git +git clone https://github.com/atlarge-research/opendc.git +``` + +#### Set up the database + +The database can be rebuilt by using the `schema.sql` file from main opendc repository. + +#### Configure OpenDC + +Create a file `config.json` in `opendc-web-server`, containing: + +```json +{ + "ROOT_DIR": "BASE_DIRECTORY", + "OAUTH_CLIENT_ID": "OAUTH_CLIENT_ID", + "FLASK_SECRET": "FLASK_SECRET", + "MYSQL_DATABASE": "opendc", + "MYSQL_USER": "opendc", + "MYSQL_PASSWORD": "opendcpassword", + "MYSQL_HOST": "127.0.0.1", + "MYSQL_PORT": 3306 +} +``` + +Make the following replacements: +* Replace `BASE_DIRECTORY` with the base directory in which you cloned `opendc` and `opendc-web-server`. +* Replace `OAUTH_CLIENT_ID` with your OAuth client ID (see the [OpenDC README](https://github.com/atlarge-research/opendc#preamble)). +* Replace `FLASK_SECRET`, come up with some string. +* Replace the `MYSQL_*` variables with the correct settings for accessing the MariaDB database that was just created. + +In `opendc-web-server/static/index.html`, add your own `OAUTH_CLIENT_ID` in `content=` on line `2`. + +#### Set up Postman and OpenDC account + +To easily make HTTP requests to the web server, we recommend Postman (get it [here](https://www.getpostman.com/)). + +Once Postman is installed and set up, `Import` the OpenDC requests collection (`OpenDC.postman_collection.json`). In the `Collections` tab, expand `OpenDC` and click `Create New User`. This should open the request in the `Builder` pane. + +Navigate to `http://localhost:8081/my-auth-token` and copy the authentication token on this page to your clipboard. In the Postman `Builder` pane, navigate to the `Headers (2)` tab, and paste the authentication token as value for the `auth-token` header. (This token expires every hour - refresh the auth token page to get a new token.) + +(Optional: navigate to the `Body` tab and change the email address to the gmail address you used to get an authentication token.) + +Click `Send` in Postman to send your request and see the server's response. If it's a `200`, your account is set up! + +### Local Development + +Run the server. + +```bash +cd opendc-web-server +python main.py config.json +``` + +To try a different query, use the Postman `Builder` to edit the method, path, body, query parameters, etc. `Create New Simulation` is provided as an additional example. + +When editing the web server code, restart the server (`CTRL` + `c` followed by `python main.py config.json` in the console running the server) to see the result of your changes. + +#### Code Style + +To format all files, run `format.sh` in this directory. + +#### Testing + +Run `pytest` in this directory to run all tests. diff --git a/web-server/conftest.py b/web-server/conftest.py new file mode 100644 index 00000000..05172b04 --- /dev/null +++ b/web-server/conftest.py @@ -0,0 +1,12 @@ +import pytest + +from main import FLASK_CORE_APP + + +@pytest.fixture +def client(): + """Returns a Flask API client to interact with.""" + FLASK_CORE_APP.config['TESTING'] = True + + with FLASK_CORE_APP.test_client() as client: + yield client diff --git a/web-server/format.sh b/web-server/format.sh new file mode 100644 index 00000000..18cba452 --- /dev/null +++ b/web-server/format.sh @@ -0,0 +1 @@ +yapf **/*.py -i diff --git a/web-server/images/logo.png b/web-server/images/logo.png Binary files differnew file mode 100644 index 00000000..d743038b --- /dev/null +++ b/web-server/images/logo.png diff --git a/web-server/images/opendc-web-server-component-diagram.png b/web-server/images/opendc-web-server-component-diagram.png Binary files differnew file mode 100644 index 00000000..91b26006 --- /dev/null +++ b/web-server/images/opendc-web-server-component-diagram.png diff --git a/web-server/main.py b/web-server/main.py new file mode 100644 index 00000000..cd9394d5 --- /dev/null +++ b/web-server/main.py @@ -0,0 +1,186 @@ +import flask_socketio +import json +import os +import sys +import traceback +import urllib.request +from flask import Flask, request, send_from_directory, jsonify +from flask_compress import Compress +from oauth2client import client, crypt +from flask_cors import CORS + +from opendc.models_old.user import User +from opendc.util import rest, path_parser, database +from opendc.util.exceptions import AuthorizationTokenError, RequestInitializationError + +TEST_MODE = "OPENDC_FLASK_TESTING" in os.environ + +if TEST_MODE: + STATIC_ROOT = os.curdir +else: + database.DB.initialize_database(user=os.environ['OPENDC_DB_USERNAME'], + password=os.environ['OPENDC_DB_PASSWORD'], + database=os.environ['OPENDC_DB'], + host='localhost') + STATIC_ROOT = os.path.join(os.environ['OPENDC_ROOT_DIR'], 'opendc-frontend', 'build') + +FLASK_CORE_APP = Flask(__name__, static_url_path='', static_folder=STATIC_ROOT) +FLASK_CORE_APP.config['SECRET_KEY'] = os.environ['OPENDC_FLASK_SECRET'] +if 'localhost' in os.environ['OPENDC_SERVER_BASE_URL']: + CORS(FLASK_CORE_APP) + +compress = Compress() +compress.init_app(FLASK_CORE_APP) + +if 'OPENDC_SERVER_BASE_URL' in os.environ or 'localhost' in os.environ['OPENDC_SERVER_BASE_URL']: + SOCKET_IO_CORE = flask_socketio.SocketIO(FLASK_CORE_APP, cors_allowed_origins="*") +else: + SOCKET_IO_CORE = flask_socketio.SocketIO(FLASK_CORE_APP) + + +@FLASK_CORE_APP.errorhandler(404) +def page_not_found(e): + return send_from_directory(STATIC_ROOT, 'index.html') + + +@FLASK_CORE_APP.route('/tokensignin', methods=['POST']) +def sign_in(): + """Authenticate a user with Google sign in""" + + try: + token = request.form['idtoken'] + except KeyError: + return 'No idtoken provided', 401 + + try: + idinfo = client.verify_id_token(token, os.environ['OPENDC_OAUTH_CLIENT_ID']) + + if idinfo['aud'] != os.environ['OPENDC_OAUTH_CLIENT_ID']: + raise crypt.AppIdentityError('Unrecognized client.') + + if idinfo['iss'] not in ['accounts.google.com', 'https://accounts.google.com']: + raise crypt.AppIdentityError('Wrong issuer.') + except ValueError: + url = "https://www.googleapis.com/oauth2/v3/tokeninfo?id_token={}".format(token) + req = urllib.request.Request(url) + response = urllib.request.urlopen(url=req, timeout=30) + res = response.read() + idinfo = json.loads(res) + except crypt.AppIdentityError as e: + return 'Did not successfully authenticate' + + user = User.from_google_id(idinfo['sub']) + + data = {'isNewUser': not user.exists()} + + if user.exists(): + data['userId'] = user.id + + return jsonify(**data) + + +@FLASK_CORE_APP.route('/api/<string:version>/<path:endpoint_path>', methods=['GET', 'POST', 'PUT', 'DELETE']) +def api_call(version, endpoint_path): + """Call an API endpoint directly over HTTP.""" + + # Get path and parameters + (path, path_parameters) = path_parser.parse(version, endpoint_path) + + query_parameters = request.args.to_dict() + for param in query_parameters: + try: + query_parameters[param] = int(query_parameters[param]) + except: + pass + + try: + body_parameters = json.loads(request.get_data()) + except: + body_parameters = {} + + # Create and call request + (req, response) = _process_message({ + 'id': 0, + 'method': request.method, + 'parameters': { + 'body': body_parameters, + 'path': path_parameters, + 'query': query_parameters + }, + 'path': path, + 'token': request.headers.get('auth-token') + }) + + print( + f'HTTP:\t{req.method} to `/{req.path}` resulted in {response.status["code"]}: {response.status["description"]}') + sys.stdout.flush() + + flask_response = jsonify(json.loads(response.to_JSON())) + flask_response.status_code = response.status['code'] + return flask_response + + +@FLASK_CORE_APP.route('/my-auth-token') +def serve_web_server_test(): + """Serve the web server test.""" + return send_from_directory(STATIC_ROOT, 'index.html') + + +@FLASK_CORE_APP.route('/') +@FLASK_CORE_APP.route('/simulations') +@FLASK_CORE_APP.route('/simulations/<path:simulation_id>') +@FLASK_CORE_APP.route('/simulations/<path:simulation_id>/experiments') +@FLASK_CORE_APP.route('/simulations/<path:simulation_id>/experiments/<path:experiment_id>') +@FLASK_CORE_APP.route('/profile') +def serve_index(simulation_id=None, experiment_id=None): + return send_from_directory(STATIC_ROOT, 'index.html') + + +@SOCKET_IO_CORE.on('request') +def receive_message(message): + """"Receive a SocketIO request""" + (req, res) = _process_message(message) + + print(f'Socket:\t{req.method} to `/{req.path}` resulted in {res.status["code"]}: {res.status["description"]}') + sys.stdout.flush() + + flask_socketio.emit('res', res.to_JSON(), json=True) + + +def _process_message(message): + """Process a request message and return the response.""" + + try: + req = rest.Request(message) + res = req.process() + + return req, res + + except AuthorizationTokenError: + res = rest.Response(401, 'Authorization error') + res.id = message['id'] + + except RequestInitializationError as e: + res = rest.Response(400, str(e)) + res.id = message['id'] + + if not 'method' in message: + message['method'] = 'UNSPECIFIED' + if not 'path' in message: + message['path'] = 'UNSPECIFIED' + + except Exception: + res = rest.Response(500, 'Internal server error') + if 'id' in message: + res.id = message['id'] + traceback.print_exc() + + req = rest.Request() + req.method = message['method'] + req.path = message['path'] + + return req, res + + +if __name__ == '__main__': + SOCKET_IO_CORE.run(FLASK_CORE_APP, host='0.0.0.0', port=8081) diff --git a/web-server/opendc/__init__.py b/web-server/opendc/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/web-server/opendc/__init__.py diff --git a/web-server/opendc/api/__init__.py b/web-server/opendc/api/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/web-server/opendc/api/__init__.py diff --git a/web-server/opendc/api/v2/__init__.py b/web-server/opendc/api/v2/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/web-server/opendc/api/v2/__init__.py diff --git a/web-server/opendc/api/v2/experiments/__init__.py b/web-server/opendc/api/v2/experiments/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/web-server/opendc/api/v2/experiments/__init__.py diff --git a/web-server/opendc/api/v2/experiments/experimentId/__init__.py b/web-server/opendc/api/v2/experiments/experimentId/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/web-server/opendc/api/v2/experiments/experimentId/__init__.py diff --git a/web-server/opendc/api/v2/experiments/experimentId/endpoint.py b/web-server/opendc/api/v2/experiments/experimentId/endpoint.py new file mode 100644 index 00000000..bc2b139e --- /dev/null +++ b/web-server/opendc/api/v2/experiments/experimentId/endpoint.py @@ -0,0 +1,113 @@ +from opendc.models_old.experiment import Experiment +from opendc.util import exceptions +from opendc.util.rest import Response + + +def GET(request): + """Get this Experiment.""" + + try: + request.check_required_parameters(path={'experimentId': 'int'}) + + except exceptions.ParameterError as e: + return Response(400, str(e)) + + # Instantiate an Experiment from the database + + experiment = Experiment.from_primary_key((request.params_path['experimentId'], )) + + # Make sure this Experiment exists + + if not experiment.exists(): + return Response(404, '{} not found.'.format(experiment)) + + # Make sure this user is authorized to view this Experiment + + if not experiment.google_id_has_at_least(request.google_id, 'VIEW'): + return Response(403, 'Forbidden from retrieving {}.'.format(experiment)) + + # Return this Experiment + + experiment.read() + + return Response(200, 'Successfully retrieved {}.'.format(experiment), experiment.to_JSON()) + + +def PUT(request): + """Update this Experiment's Path, Trace, Scheduler, and/or name.""" + + # Make sure required parameters are there + + try: + request.check_required_parameters( + path={'experimentId': 'int'}, + body={'experiment': { + 'pathId': 'int', + 'traceId': 'int', + 'schedulerName': 'string', + 'name': 'string' + }}) + + except exceptions.ParameterError as e: + return Response(400, str(e)) + + # Instantiate an Experiment from the database + + experiment = Experiment.from_primary_key((request.params_path['experimentId'], )) + + # Make sure this Experiment exists + + if not experiment.exists(): + return Response(404, '{} not found.'.format(experiment)) + + # Make sure this user is authorized to edit this Experiment + + if not experiment.google_id_has_at_least(request.google_id, 'EDIT'): + return Response(403, 'Forbidden from updating {}.'.format(experiment)) + + # Update this Experiment + + experiment.path_id = request.params_body['experiment']['pathId'] + experiment.trace_id = request.params_body['experiment']['traceId'] + experiment.scheduler_name = request.params_body['experiment']['schedulerName'] + experiment.name = request.params_body['experiment']['name'] + + try: + experiment.update() + + except exceptions.ForeignKeyError: + return Response(400, 'Foreign key error.') + + # Return this Experiment + + return Response(200, 'Successfully updated {}.'.format(experiment), experiment.to_JSON()) + + +def DELETE(request): + """Delete this Experiment.""" + + # Make sure required parameters are there + + try: + request.check_required_parameters(path={'experimentId': 'int'}) + + except exceptions.ParameterError as e: + return Response(400, str(e)) + + # Instantiate an Experiment and make sure it exists + + experiment = Experiment.from_primary_key((request.params_path['experimentId'], )) + + if not experiment.exists(): + return Response(404, '{} not found.'.format(experiment)) + + # Make sure this user is authorized to delete this Experiment + + if not experiment.google_id_has_at_least(request.google_id, 'EDIT'): + return Response(403, 'Forbidden from deleting {}.'.format(experiment)) + + # Delete and return this Experiment + + experiment.delete() + + return Response(200, 'Successfully deleted {}.'.format(experiment), experiment.to_JSON()) diff --git a/web-server/opendc/api/v2/experiments/experimentId/last-simulated-tick/__init__.py b/web-server/opendc/api/v2/experiments/experimentId/last-simulated-tick/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/web-server/opendc/api/v2/experiments/experimentId/last-simulated-tick/__init__.py diff --git a/web-server/opendc/api/v2/experiments/experimentId/last-simulated-tick/endpoint.py b/web-server/opendc/api/v2/experiments/experimentId/last-simulated-tick/endpoint.py new file mode 100644 index 00000000..3309502c --- /dev/null +++ b/web-server/opendc/api/v2/experiments/experimentId/last-simulated-tick/endpoint.py @@ -0,0 +1,32 @@ +from opendc.models_old.experiment import Experiment +from opendc.util import exceptions +from opendc.util.rest import Response + + +def GET(request): + """Get this Experiment's last simulated tick.""" + + # Make sure required parameters are there + + try: + request.check_required_parameters(path={'experimentId': 'int'}) + + except exceptions.ParameterError as e: + return Response(400, str(e)) + + # Instantiate an Experiment from the database + + experiment = Experiment.from_primary_key((request.params_path['experimentId'], )) + + # Make sure this Experiment exists + + if not experiment.exists(): + return Response(404, '{} not found.'.format(experiment)) + + # Make sure this user is authorized to view this Experiment's last simulated tick + + if not experiment.google_id_has_at_least(request.google_id, 'VIEW'): + return Response(403, 'Forbidden from viewing last simulated tick for {}.'.format(experiment)) + + return Response(200, 'Successfully retrieved last simulated tick for {}.'.format(experiment), + {'lastSimulatedTick': experiment.last_simulated_tick}) diff --git a/web-server/opendc/api/v2/experiments/experimentId/machine-states/__init__.py b/web-server/opendc/api/v2/experiments/experimentId/machine-states/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/web-server/opendc/api/v2/experiments/experimentId/machine-states/__init__.py diff --git a/web-server/opendc/api/v2/experiments/experimentId/machine-states/endpoint.py b/web-server/opendc/api/v2/experiments/experimentId/machine-states/endpoint.py new file mode 100644 index 00000000..c7dcad9a --- /dev/null +++ b/web-server/opendc/api/v2/experiments/experimentId/machine-states/endpoint.py @@ -0,0 +1,42 @@ +from opendc.models_old.experiment import Experiment +from opendc.models_old.machine_state import MachineState +from opendc.util import exceptions +from opendc.util.rest import Response + + +def GET(request): + """Get this Experiment's Machine States.""" + + # Make sure required parameters are there + + try: + request.check_required_parameters(path={'experimentId': 'int'}) + + except exceptions.ParameterError as e: + return Response(400, str(e)) + + # Instantiate an Experiment from the database + + experiment = Experiment.from_primary_key((request.params_path['experimentId'], )) + + # Make sure this Experiment exists + + if not experiment.exists(): + return Response(404, '{} not found.'.format(experiment)) + + # Make sure this user is authorized to view this Experiment's Machine States + + if not experiment.google_id_has_at_least(request.google_id, 'VIEW'): + return Response(403, 'Forbidden from viewing Machine States for {}.'.format(experiment)) + + # Get and return the Machine States + + if 'tick' in request.params_query: + machine_states = MachineState.from_experiment_id_and_tick(request.params_path['experimentId'], + request.params_query['tick']) + + else: + machine_states = MachineState.from_experiment_id(request.params_path['experimentId']) + + return Response(200, 'Successfully retrieved Machine States for {}.'.format(experiment), + [x.to_JSON() for x in machine_states]) diff --git a/web-server/opendc/api/v2/experiments/experimentId/rack-states/__init__.py b/web-server/opendc/api/v2/experiments/experimentId/rack-states/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/web-server/opendc/api/v2/experiments/experimentId/rack-states/__init__.py diff --git a/web-server/opendc/api/v2/experiments/experimentId/rack-states/endpoint.py b/web-server/opendc/api/v2/experiments/experimentId/rack-states/endpoint.py new file mode 100644 index 00000000..f3acf56a --- /dev/null +++ b/web-server/opendc/api/v2/experiments/experimentId/rack-states/endpoint.py @@ -0,0 +1,42 @@ +from opendc.models_old.experiment import Experiment +from opendc.models_old.rack_state import RackState +from opendc.util import exceptions +from opendc.util.rest import Response + + +def GET(request): + """Get this Experiment's Tack States.""" + + # Make sure required parameters are there + + try: + request.check_required_parameters(path={'experimentId': 'int'}) + + except exceptions.ParameterError as e: + return Response(400, str(e)) + + # Instantiate an Experiment from the database + + experiment = Experiment.from_primary_key((request.params_path['experimentId'], )) + + # Make sure this Experiment exists + + if not experiment.exists(): + return Response(404, '{} not found.'.format(experiment)) + + # Make sure this user is authorized to view this Experiment's Rack States + + if not experiment.google_id_has_at_least(request.google_id, 'VIEW'): + return Response(403, 'Forbidden from viewing Rack States for {}.'.format(experiment)) + + # Get and return the Rack States + + if 'tick' in request.params_query: + rack_states = RackState.from_experiment_id_and_tick(request.params_path['experimentId'], + request.params_query['tick']) + + else: + rack_states = RackState.from_experiment_id(request.params_path['experimentId']) + + return Response(200, 'Successfully retrieved Rack States for {}.'.format(experiment), + [x.to_JSON() for x in rack_states]) diff --git a/web-server/opendc/api/v2/experiments/experimentId/room-states/__init__.py b/web-server/opendc/api/v2/experiments/experimentId/room-states/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/web-server/opendc/api/v2/experiments/experimentId/room-states/__init__.py diff --git a/web-server/opendc/api/v2/experiments/experimentId/room-states/endpoint.py b/web-server/opendc/api/v2/experiments/experimentId/room-states/endpoint.py new file mode 100644 index 00000000..db3f8b14 --- /dev/null +++ b/web-server/opendc/api/v2/experiments/experimentId/room-states/endpoint.py @@ -0,0 +1,42 @@ +from opendc.models_old.experiment import Experiment +from opendc.models_old.room_state import RoomState +from opendc.util import exceptions +from opendc.util.rest import Response + + +def GET(request): + """Get this Experiment's Room States.""" + + # Make sure required parameters are there + + try: + request.check_required_parameters(path={'experimentId': 'int'}) + + except exceptions.ParameterError as e: + return Response(400, str(e)) + + # Instantiate an Experiment from the database + + experiment = Experiment.from_primary_key((request.params_path['experimentId'], )) + + # Make sure this Experiment exists + + if not experiment.exists(): + return Response(404, '{} not found.'.format(experiment)) + + # Make sure this user is authorized to view this Experiment's Room States + + if not experiment.google_id_has_at_least(request.google_id, 'VIEW'): + return Response(403, 'Forbidden from viewing Room States for {}.'.format(experiment)) + + # Get and return the Room States + + if 'tick' in request.params_query: + room_states = RoomState.from_experiment_id_and_tick(request.params_path['experimentId'], + request.params_query['tick']) + + else: + room_states = RoomState.from_experiment_id(request.params_path['experimentId']) + + return Response(200, 'Successfully retrieved Room States for {}.'.format(experiment), + [x.to_JSON() for x in room_states]) diff --git a/web-server/opendc/api/v2/experiments/experimentId/statistics/__init__.py b/web-server/opendc/api/v2/experiments/experimentId/statistics/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/web-server/opendc/api/v2/experiments/experimentId/statistics/__init__.py diff --git a/web-server/opendc/api/v2/experiments/experimentId/statistics/task-durations/__init__.py b/web-server/opendc/api/v2/experiments/experimentId/statistics/task-durations/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/web-server/opendc/api/v2/experiments/experimentId/statistics/task-durations/__init__.py diff --git a/web-server/opendc/api/v2/experiments/experimentId/statistics/task-durations/endpoint.py b/web-server/opendc/api/v2/experiments/experimentId/statistics/task-durations/endpoint.py new file mode 100644 index 00000000..498db239 --- /dev/null +++ b/web-server/opendc/api/v2/experiments/experimentId/statistics/task-durations/endpoint.py @@ -0,0 +1,37 @@ +from opendc.models_old.experiment import Experiment +from opendc.models_old.task_duration import TaskDuration +from opendc.util import exceptions +from opendc.util.rest import Response + + +def GET(request): + """Get this Experiment's Task Durations.""" + + # Make sure required parameters are there + + try: + request.check_required_parameters(path={'experimentId': 'int'}) + + except exceptions.ParameterError as e: + return Response(400, str(e)) + + # Instantiate an Experiment from the database + + experiment = Experiment.from_primary_key((request.params_path['experimentId'], )) + + # Make sure this Experiment exists + + if not experiment.exists(): + return Response(404, '{} not found.'.format(experiment)) + + # Make sure this user is authorized to view this Experiment's Task Durations + + if not experiment.google_id_has_at_least(request.google_id, 'VIEW'): + return Response(403, 'Forbidden from viewing Task Durations for {}.'.format(experiment)) + + # Get and return the Task Durations + + task_durations = TaskDuration.from_experiment_id(request.params_path['experimentId']) + + return Response(200, 'Successfully retrieved Task Durations for {}.'.format(experiment), + [x.to_JSON() for x in task_durations]) diff --git a/web-server/opendc/api/v2/experiments/experimentId/task-states/__init__.py b/web-server/opendc/api/v2/experiments/experimentId/task-states/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/web-server/opendc/api/v2/experiments/experimentId/task-states/__init__.py diff --git a/web-server/opendc/api/v2/experiments/experimentId/task-states/endpoint.py b/web-server/opendc/api/v2/experiments/experimentId/task-states/endpoint.py new file mode 100644 index 00000000..c0ae47fc --- /dev/null +++ b/web-server/opendc/api/v2/experiments/experimentId/task-states/endpoint.py @@ -0,0 +1,42 @@ +from opendc.models_old.experiment import Experiment +from opendc.models_old.task_state import TaskState +from opendc.util import exceptions +from opendc.util.rest import Response + + +def GET(request): + """Get this Experiment's Task States.""" + + # Make sure required parameters are there + + try: + request.check_required_parameters(path={'experimentId': 'int'}) + + except exceptions.ParameterError as e: + return Response(400, str(e)) + + # Instantiate an Experiment from the database + + experiment = Experiment.from_primary_key((request.params_path['experimentId'], )) + + # Make sure this Experiment exists + + if not experiment.exists(): + return Response(404, '{} not found.'.format(experiment)) + + # Make sure this user is authorized to view Task States for this Experiment + + if not experiment.google_id_has_at_least(request.google_id, 'VIEW'): + return Response(403, 'Forbidden from viewing Task States for {}.'.format(experiment)) + + # Get and return the Task States + + if 'tick' in request.params_query: + task_states = TaskState.from_experiment_id_and_tick(request.params_path['experimentId'], + request.params_query['tick']) + + else: + task_states = TaskState.query('experiment_id', request.params_path['experimentId']) + + return Response(200, 'Successfully retrieved Task States for {}.'.format(experiment), + [x.to_JSON() for x in task_states]) diff --git a/web-server/opendc/api/v2/paths.json b/web-server/opendc/api/v2/paths.json new file mode 100644 index 00000000..ce054a8c --- /dev/null +++ b/web-server/opendc/api/v2/paths.json @@ -0,0 +1,19 @@ +[ + "/users", + "/users/{userId}", + "/simulations", + "/simulations/{simulationId}", + "/simulations/{simulationId}/authorizations", + "/simulations/{simulationId}/topologies", + "/experiments/{experimentId}/last-simulated-tick", + "/experiments/{experimentId}/machine-states", + "/experiments/{experimentId}/rack-states", + "/experiments/{experimentId}/room-states", + "/experiments/{experimentId}/task-states", + "/topologies/{topologyId}", + "/simulations/{simulationId}/experiments", + "/experiments/{experimentId}", + "/schedulers", + "/traces", + "/traces/{traceId}" +] diff --git a/web-server/opendc/api/v2/schedulers/__init__.py b/web-server/opendc/api/v2/schedulers/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/web-server/opendc/api/v2/schedulers/__init__.py diff --git a/web-server/opendc/api/v2/schedulers/endpoint.py b/web-server/opendc/api/v2/schedulers/endpoint.py new file mode 100644 index 00000000..0bbc3322 --- /dev/null +++ b/web-server/opendc/api/v2/schedulers/endpoint.py @@ -0,0 +1,10 @@ +from opendc.util.rest import Response + + +SCHEDULERS = ['DEFAULT'] + + +def GET(_): + """Get all available Schedulers.""" + + return Response(200, 'Successfully retrieved Schedulers.', [{'name': name} for name in SCHEDULERS]) diff --git a/web-server/opendc/api/v2/schedulers/test_endpoint.py b/web-server/opendc/api/v2/schedulers/test_endpoint.py new file mode 100644 index 00000000..a0bd8758 --- /dev/null +++ b/web-server/opendc/api/v2/schedulers/test_endpoint.py @@ -0,0 +1,2 @@ +def test_get_schedulers(client): + assert '200' in client.get('/api/v2/schedulers').status diff --git a/web-server/opendc/api/v2/simulations/__init__.py b/web-server/opendc/api/v2/simulations/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/web-server/opendc/api/v2/simulations/__init__.py diff --git a/web-server/opendc/api/v2/simulations/endpoint.py b/web-server/opendc/api/v2/simulations/endpoint.py new file mode 100644 index 00000000..232df2ff --- /dev/null +++ b/web-server/opendc/api/v2/simulations/endpoint.py @@ -0,0 +1,30 @@ +from datetime import datetime + +from opendc.models.simulation import Simulation +from opendc.models.topology import Topology +from opendc.models.user import User +from opendc.util import exceptions +from opendc.util.database import Database +from opendc.util.rest import Response + + +def POST(request): + """Create a new simulation, and return that new simulation.""" + + request.check_required_parameters(body={'simulation': {'name': 'string'}}) + + topology = Topology({'name': 'Default topology'}) + topology.insert() + + simulation = Simulation({'simulation': request.params_body['simulation']}) + simulation.set_property('datetimeCreated', Database.datetime_to_string(datetime.now())) + simulation.set_property('datetimeLastEdited', Database.datetime_to_string(datetime.now())) + simulation.set_property('topologyIds', [topology.obj['_id']]) + simulation.set_property('experimentIds', []) + simulation.insert() + + user = User.from_google_id(request.google_id) + user.obj['authorizations'].append({'simulationId': simulation.obj['_id'], 'authorizationLevel': 'OWN'}) + user.update() + + return Response(200, 'Successfully created simulation.', simulation.obj) diff --git a/web-server/opendc/api/v2/simulations/simulationId/__init__.py b/web-server/opendc/api/v2/simulations/simulationId/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/web-server/opendc/api/v2/simulations/simulationId/__init__.py diff --git a/web-server/opendc/api/v2/simulations/simulationId/authorizations/__init__.py b/web-server/opendc/api/v2/simulations/simulationId/authorizations/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/web-server/opendc/api/v2/simulations/simulationId/authorizations/__init__.py diff --git a/web-server/opendc/api/v2/simulations/simulationId/authorizations/endpoint.py b/web-server/opendc/api/v2/simulations/simulationId/authorizations/endpoint.py new file mode 100644 index 00000000..df2b5cfd --- /dev/null +++ b/web-server/opendc/api/v2/simulations/simulationId/authorizations/endpoint.py @@ -0,0 +1,37 @@ +from opendc.models_old.authorization import Authorization +from opendc.models_old.simulation import Simulation +from opendc.util import exceptions +from opendc.util.rest import Response + + +def GET(request): + """Find all authorizations for a Simulation.""" + + # Make sure required parameters are there + + try: + request.check_required_parameters(path={'simulationId': 'string'}) + + except exceptions.ParameterError as e: + return Response(400, str(e)) + + # Instantiate a Simulation and make sure it exists + + simulation = Simulation.from_primary_key((request.params_path['simulationId'], )) + + if not simulation.exists(): + return Response(404, '{} not found.'.format(simulation)) + + # Make sure this User is allowed to view this Simulation's Authorizations + + if not simulation.google_id_has_at_least(request.google_id, 'VIEW'): + return Response(403, 'Forbidden from retrieving Authorizations for {}.'.format(simulation)) + + # Get the Authorizations + + authorizations = Authorization.query('simulation_id', request.params_path['simulationId']) + + # Return the Authorizations + + return Response(200, 'Successfully retrieved Authorizations for {}.'.format(simulation), + [x.to_JSON() for x in authorizations]) diff --git a/web-server/opendc/api/v2/simulations/simulationId/authorizations/userId/__init__.py b/web-server/opendc/api/v2/simulations/simulationId/authorizations/userId/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/web-server/opendc/api/v2/simulations/simulationId/authorizations/userId/__init__.py diff --git a/web-server/opendc/api/v2/simulations/simulationId/authorizations/userId/endpoint.py b/web-server/opendc/api/v2/simulations/simulationId/authorizations/userId/endpoint.py new file mode 100644 index 00000000..121530db --- /dev/null +++ b/web-server/opendc/api/v2/simulations/simulationId/authorizations/userId/endpoint.py @@ -0,0 +1,178 @@ +from opendc.models_old.authorization import Authorization +from opendc.models_old.simulation import Simulation +from opendc.models_old.user import User +from opendc.util import exceptions +from opendc.util.rest import Response + + +def DELETE(request): + """Delete a user's authorization level over a simulation.""" + + # Make sure required parameters are there + + try: + request.check_required_parameters(path={'simulationId': 'string', 'userId': 'string'}) + + except exceptions.ParameterError as e: + return Response(400, str(e)) + + # Instantiate an Authorization + + authorization = Authorization.from_primary_key((request.params_path['userId'], request.params_path['simulationId'])) + + # Make sure this Authorization exists in the database + + if not authorization.exists(): + return Response(404, '{} not found.'.format(authorization)) + + # Make sure this User is allowed to delete this Authorization + + if not authorization.google_id_has_at_least(request.google_id, 'OWN'): + return Response(403, 'Forbidden from deleting {}.'.format(authorization)) + + # Delete this Authorization + + authorization.delete() + + return Response(200, 'Successfully deleted {}.'.format(authorization), authorization.to_JSON()) + + +def GET(request): + """Get this User's Authorization over this Simulation.""" + + # Make sure required parameters are there + + try: + request.check_required_parameters(path={'simulationId': 'string', 'userId': 'string'}) + + except exceptions.ParameterError as e: + return Response(400, str(e)) + + # Instantiate an Authorization + + authorization = Authorization.from_primary_key((request.params_path['userId'], request.params_path['simulationId'])) + + # Make sure this Authorization exists in the database + + if not authorization.exists(): + return Response(404, '{} not found.'.format(authorization)) + + # Read this Authorization from the database + + authorization.read() + + # Return this Authorization + + return Response(200, 'Successfully retrieved {}'.format(authorization), authorization.to_JSON()) + + +def POST(request): + """Add an authorization for a user's access to a simulation.""" + + # Make sure required parameters are there + + try: + request.check_required_parameters(path={ + 'userId': 'string', + 'simulationId': 'string' + }, + body={'authorization': { + 'authorizationLevel': 'string' + }}) + + except exceptions.ParameterError as e: + return Response(400, str(e)) + + # Instantiate an Authorization + + authorization = Authorization.from_JSON({ + 'userId': + request.params_path['userId'], + 'simulationId': + request.params_path['simulationId'], + 'authorizationLevel': + request.params_body['authorization']['authorizationLevel'] + }) + + # Make sure the Simulation and User exist + + user = User.from_primary_key((authorization.user_id, )) + if not user.exists(): + return Response(404, '{} not found.'.format(user)) + + simulation = Simulation.from_primary_key((authorization.simulation_id, )) + if not simulation.exists(): + return Response(404, '{} not found.'.format(simulation)) + + # Make sure this User is allowed to add this Authorization + + if not simulation.google_id_has_at_least(request.google_id, 'OWN'): + return Response(403, 'Forbidden from creating {}.'.format(authorization)) + + # Make sure this Authorization does not already exist + + if authorization.exists(): + return Response(409, '{} already exists.'.format(authorization)) + + # Try to insert this Authorization into the database + + try: + authorization.insert() + + except exceptions.ForeignKeyError: + return Response(400, 'Invalid authorizationLevel') + + # Return this Authorization + + return Response(200, 'Successfully added {}'.format(authorization), authorization.to_JSON()) + + +def PUT(request): + """Change a user's authorization level over a simulation.""" + + # Make sure required parameters are there + + try: + request.check_required_parameters(path={ + 'simulationId': 'string', + 'userId': 'string' + }, + body={'authorization': { + 'authorizationLevel': 'string' + }}) + + except exceptions.ParameterError as e: + return Response(400, str(e)) + + # Instantiate and Authorization + + authorization = Authorization.from_JSON({ + 'userId': + request.params_path['userId'], + 'simulationId': + request.params_path['simulationId'], + 'authorizationLevel': + request.params_body['authorization']['authorizationLevel'] + }) + + # Make sure this Authorization exists + + if not authorization.exists(): + return Response(404, '{} not found.'.format(authorization)) + + # Make sure this User is allowed to edit this Authorization + + if not authorization.google_id_has_at_least(request.google_id, 'OWN'): + return Response(403, 'Forbidden from updating {}.'.format(authorization)) + + # Try to update this Authorization + + try: + authorization.update() + + except exceptions.ForeignKeyError as e: + return Response(400, 'Invalid authorization level.') + + # Return this Authorization + + return Response(200, 'Successfully updated {}.'.format(authorization), authorization.to_JSON()) diff --git a/web-server/opendc/api/v2/simulations/simulationId/endpoint.py b/web-server/opendc/api/v2/simulations/simulationId/endpoint.py new file mode 100644 index 00000000..282e3291 --- /dev/null +++ b/web-server/opendc/api/v2/simulations/simulationId/endpoint.py @@ -0,0 +1,57 @@ +from datetime import datetime + +from opendc.models.simulation import Simulation +from opendc.models.topology import Topology +from opendc.util.database import Database +from opendc.util.rest import Response + + +def GET(request): + """Get this Simulation.""" + + request.check_required_parameters(path={'simulationId': 'string'}) + + simulation = Simulation.from_id(request.params_path['simulationId']) + + simulation.check_exists() + simulation.check_user_access(request.google_id, False) + + return Response(200, 'Successfully retrieved simulation', simulation.obj) + + +def PUT(request): + """Update a simulation's name.""" + + request.check_required_parameters(body={'simulation': {'name': 'name'}}, path={'simulationId': 'string'}) + + simulation = Simulation.from_id(request.params_path['simulationId']) + + simulation.check_exists() + simulation.check_user_access(request.google_id, True) + + simulation.set_property('name', request.params_body['simulation']['name']) + simulation.set_property('datetime_last_edited', Database.datetime_to_string(datetime.now())) + simulation.update() + + return Response(200, 'Successfully updated simulation.', simulation.obj) + + +def DELETE(request): + """Delete this Simulation.""" + + request.check_required_parameters(path={'simulationId': 'string'}) + + simulation = Simulation.from_id(request.params_path['simulationId']) + + simulation.check_exists() + simulation.check_user_access(request.google_id, True) + + for topology_id in simulation.obj['topologyIds']: + topology = Topology.from_id(topology_id) + topology.delete() + + # TODO remove all experiments + + simulation.delete() + + return Response(200, f'Successfully deleted simulation.', simulation.obj) diff --git a/web-server/opendc/api/v2/simulations/simulationId/experiments/__init__.py b/web-server/opendc/api/v2/simulations/simulationId/experiments/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/web-server/opendc/api/v2/simulations/simulationId/experiments/__init__.py diff --git a/web-server/opendc/api/v2/simulations/simulationId/experiments/endpoint.py b/web-server/opendc/api/v2/simulations/simulationId/experiments/endpoint.py new file mode 100644 index 00000000..9df84838 --- /dev/null +++ b/web-server/opendc/api/v2/simulations/simulationId/experiments/endpoint.py @@ -0,0 +1,97 @@ +from opendc.models_old.experiment import Experiment +from opendc.models_old.simulation import Simulation +from opendc.util import exceptions +from opendc.util.rest import Response + + +def GET(request): + """Get this Simulation's Experiments.""" + + # Make sure required parameters are there + + try: + request.check_required_parameters(path={'simulationId': 'string'}) + + except exceptions.ParameterError as e: + return Response(400, str(e)) + + # Instantiate a Simulation from the database + + simulation = Simulation.from_primary_key((request.params_path['simulationId'], )) + + # Make sure this Simulation exists + + if not simulation.exists(): + return Response(404, '{} not found.'.format(simulation)) + + # Make sure this user is authorized to view this Simulation's Experiments + + if not simulation.google_id_has_at_least(request.google_id, 'VIEW'): + return Reponse(403, 'Forbidden from viewing Experiments for {}.'.format(simulation)) + + # Get and return the Experiments + + experiments = Experiment.query('simulation_id', request.params_path['simulationId']) + + return Response(200, 'Successfully retrieved Experiments for {}.'.format(simulation), + [x.to_JSON() for x in experiments]) + + +def POST(request): + """Add a new Experiment for this Simulation.""" + + # Make sure required parameters are there + + try: + request.check_required_parameters(path={'simulationId': 'string'}, + body={ + 'experiment': { + 'simulationId': 'string', + 'pathId': 'int', + 'traceId': 'int', + 'schedulerName': 'string', + 'name': 'string' + } + }) + + except exceptions.ParameterError as e: + return Response(400, str(e)) + + # Make sure the passed object's simulation id matches the path simulation id + + if request.params_path['simulationId'] != request.params_body['experiment']['simulationId']: + return Response(403, 'ID mismatch.') + + # Instantiate a Simulation from the database + + simulation = Simulation.from_primary_key((request.params_path['simulationId'], )) + + # Make sure this Simulation exists + + if not simulation.exists(): + return Response(404, '{} not found.'.format(simulation)) + + # Make sure this user is authorized to edit this Simulation's Experiments + + if not simulation.google_id_has_at_least(request.google_id, 'EDIT'): + return Response(403, 'Forbidden from adding an experiment to {}.'.format(simulation)) + + # Instantiate an Experiment + + experiment = Experiment.from_JSON(request.params_body['experiment']) + experiment.state = 'QUEUED' + experiment.last_simulated_tick = 0 + + # Try to insert this Experiment + + try: + experiment.insert() + + except exceptions.ForeignKeyError as e: + return Response(400, 'Foreign key constraint not met.' + e) + + # Return this Experiment + + experiment.read() + + return Response(200, 'Successfully added {}.'.format(experiment), experiment.to_JSON()) diff --git a/web-server/opendc/api/v2/simulations/simulationId/test_endpoint.py b/web-server/opendc/api/v2/simulations/simulationId/test_endpoint.py new file mode 100644 index 00000000..a0586aab --- /dev/null +++ b/web-server/opendc/api/v2/simulations/simulationId/test_endpoint.py @@ -0,0 +1,97 @@ +from opendc.util.database import DB + + +def test_get_simulation_non_existing(client, mocker): + mocker.patch.object(DB, 'fetch_one', return_value=None) + assert '404' in client.get('/api/v2/simulations/1').status + + +def test_get_simulation_no_authorizations(client, mocker): + mocker.patch.object(DB, 'fetch_one', return_value={'authorizations': []}) + res = client.get('/api/v2/simulations/1') + assert '403' in res.status + + +def test_get_simulation_not_authorized(client, mocker): + mocker.patch.object(DB, + 'fetch_one', + return_value={ + '_id': '1', + 'authorizations': [{ + 'simulationId': '2', + 'authorizationLevel': 'OWN' + }] + }) + res = client.get('/api/v2/simulations/1') + assert '403' in res.status + + +def test_get_simulation(client, mocker): + mocker.patch.object(DB, + 'fetch_one', + return_value={ + '_id': '1', + 'authorizations': [{ + 'simulationId': '1', + 'authorizationLevel': 'EDIT' + }] + }) + res = client.get('/api/v2/simulations/1') + assert '200' in res.status + + +def test_update_simulation_missing_parameter(client): + assert '400' in client.put('/api/v2/simulations/1').status + + +def test_update_simulation_non_existing(client, mocker): + mocker.patch.object(DB, 'fetch_one', return_value=None) + assert '404' in client.put('/api/v2/simulations/1', json={'simulation': {'name': 'S'}}).status + + +def test_update_simulation_not_authorized(client, mocker): + mocker.patch.object(DB, + 'fetch_one', + return_value={ + '_id': '1', + 'authorizations': [{ + 'simulationId': '1', + 'authorizationLevel': 'VIEW' + }] + }) + mocker.patch.object(DB, 'update', return_value={}) + assert '403' in client.put('/api/v2/simulations/1', json={'simulation': {'name': 'S'}}).status + + +def test_update_simulation(client, mocker): + mocker.patch.object(DB, + 'fetch_one', + return_value={ + '_id': '1', + 'authorizations': [{ + 'simulationId': '1', + 'authorizationLevel': 'OWN' + }] + }) + mocker.patch.object(DB, 'update', return_value={}) + + res = client.put('/api/v2/simulations/1', json={'simulation': {'name': 'S'}}) + assert '200' in res.status + + +def test_delete_simulation_non_existing(client, mocker): + mocker.patch.object(DB, 'fetch_one', return_value=None) + assert '404' in client.delete('/api/v2/simulations/1').status + + +def test_delete_simulation_different_user(client, mocker): + mocker.patch.object(DB, 'fetch_one', return_value={'_id': '1', 'googleId': 'other_test', 'authorizations': [{'simulationId': '1', 'authorizationLevel': 'VIEW'}], 'topologyIds': []}) + mocker.patch.object(DB, 'delete_one', return_value=None) + assert '403' in client.delete('/api/v2/simulations/1').status + + +def test_delete_simulation(client, mocker): + mocker.patch.object(DB, 'fetch_one', return_value={'_id': '1', 'googleId': 'test', 'authorizations': [{'simulationId': '1', 'authorizationLevel': 'OWN'}], 'topologyIds': []}) + mocker.patch.object(DB, 'delete_one', return_value={'googleId': 'test'}) + res = client.delete('/api/v2/simulations/1') + assert '200' in res.status diff --git a/web-server/opendc/api/v2/simulations/simulationId/topologies/__init__.py b/web-server/opendc/api/v2/simulations/simulationId/topologies/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/web-server/opendc/api/v2/simulations/simulationId/topologies/__init__.py diff --git a/web-server/opendc/api/v2/simulations/simulationId/topologies/endpoint.py b/web-server/opendc/api/v2/simulations/simulationId/topologies/endpoint.py new file mode 100644 index 00000000..ab7b7006 --- /dev/null +++ b/web-server/opendc/api/v2/simulations/simulationId/topologies/endpoint.py @@ -0,0 +1,29 @@ +from datetime import datetime + +from opendc.models.simulation import Simulation +from opendc.models.topology import Topology +from opendc.util import exceptions +from opendc.util.rest import Response +from opendc.util.database import Database + + +def POST(request): + """Add a new Topology to the specified simulation and return it""" + + request.check_required_parameters(path={'simulationId': 'string'}, body={'topology': {'name': 'string'}}) + + simulation = Simulation.from_id(request.params_path['simulationId']) + + simulation.check_exists() + simulation.check_user_access(request.google_id, True) + + topology = Topology({'name': request.params_body['topology']['name']}) + topology.set_property('datetimeCreated', Database.datetime_to_string(datetime.now())) + topology.set_property('datetimeLastEdited', Database.datetime_to_string(datetime.now())) + topology.insert() + + simulation.obj['topologyIds'].append(topology.obj['_id']) + simulation.set_property('datetimeLastEdited', Database.datetime_to_string(datetime.now())) + simulation.update() + + return Response(200, 'Successfully inserted topology.', topology.obj) diff --git a/web-server/opendc/api/v2/simulations/simulationId/topologies/test_endpoint.py b/web-server/opendc/api/v2/simulations/simulationId/topologies/test_endpoint.py new file mode 100644 index 00000000..10b5e3c9 --- /dev/null +++ b/web-server/opendc/api/v2/simulations/simulationId/topologies/test_endpoint.py @@ -0,0 +1,26 @@ +from opendc.util.database import DB + + +def test_add_topology_missing_parameter(client): + assert '400' in client.post('/api/v2/simulations/1/topologies/').status + + +def test_add_topology(client, mocker): + mocker.patch.object(DB, 'fetch_one', return_value={'_id': '1', 'authorizations': [{'simulationId': '1', 'authorizationLevel': 'OWN'}], 'topologyIds': []}) + mocker.patch.object(DB, + 'insert', + return_value={ + '_id': '1', + 'datetimeCreated': '000', + 'datetimeEdit': '000', + 'topologyIds': [] + }) + mocker.patch.object(DB, 'update', return_value={}) + res = client.post('/api/v2/simulations/1/topologies/', json={'topology': {'name': 'test simulation'}}) + assert 'datetimeCreated' in res.json['content'] + assert 'datetimeEdit' in res.json['content'] + assert 'topologyIds' in res.json['content'] + assert '200' in res.status + +def test_add_topology_no_authorizations(client, mocker): + pass
\ No newline at end of file diff --git a/web-server/opendc/api/v2/simulations/test_endpoint.py b/web-server/opendc/api/v2/simulations/test_endpoint.py new file mode 100644 index 00000000..d23df74a --- /dev/null +++ b/web-server/opendc/api/v2/simulations/test_endpoint.py @@ -0,0 +1,23 @@ +from opendc.util.database import DB + + +def test_add_simulation_missing_parameter(client): + assert '400' in client.post('/api/v2/simulations').status + + +def test_add_simulation(client, mocker): + mocker.patch.object(DB, 'fetch_one', return_value={'_id': '1', 'authorizations': []}) + mocker.patch.object(DB, + 'insert', + return_value={ + '_id': '1', + 'datetimeCreated': '000', + 'datetimeEdit': '000', + 'topologyIds': [] + }) + mocker.patch.object(DB, 'update', return_value={}) + res = client.post('/api/v2/simulations', json={'simulation': {'name': 'test simulation'}}) + assert 'datetimeCreated' in res.json['content'] + assert 'datetimeEdit' in res.json['content'] + assert 'topologyIds' in res.json['content'] + assert '200' in res.status diff --git a/web-server/opendc/api/v2/topologies/__init__.py b/web-server/opendc/api/v2/topologies/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/web-server/opendc/api/v2/topologies/__init__.py diff --git a/web-server/opendc/api/v2/topologies/topologyId/__init__.py b/web-server/opendc/api/v2/topologies/topologyId/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/web-server/opendc/api/v2/topologies/topologyId/__init__.py diff --git a/web-server/opendc/api/v2/topologies/topologyId/endpoint.py b/web-server/opendc/api/v2/topologies/topologyId/endpoint.py new file mode 100644 index 00000000..6c6ab9c2 --- /dev/null +++ b/web-server/opendc/api/v2/topologies/topologyId/endpoint.py @@ -0,0 +1,16 @@ +from opendc.models.topology import Topology +from opendc.util import exceptions +from opendc.util.rest import Response + + +def GET(request): + """Get this Topology.""" + + request.check_required_parameters(path={'topologyId': 'int'}) + + topology = Topology.from_id(request.params_path['topologyId']) + + topology.check_exists() + topology.check_user_access(request.google_id, False) + + return Response(200, 'Successfully retrieved topology.', topology.obj) diff --git a/web-server/opendc/api/v2/topologies/topologyId/rooms/__init__.py b/web-server/opendc/api/v2/topologies/topologyId/rooms/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/web-server/opendc/api/v2/topologies/topologyId/rooms/__init__.py diff --git a/web-server/opendc/api/v2/topologies/topologyId/rooms/endpoint.py b/web-server/opendc/api/v2/topologies/topologyId/rooms/endpoint.py new file mode 100644 index 00000000..96ee7028 --- /dev/null +++ b/web-server/opendc/api/v2/topologies/topologyId/rooms/endpoint.py @@ -0,0 +1,93 @@ +from opendc.models_old.datacenter import Datacenter +from opendc.models_old.room import Room +from opendc.util import exceptions +from opendc.util.rest import Response + + +def GET(request): + """Get this Datacenter's Rooms.""" + + # Make sure required parameters are there + + try: + request.check_required_parameters(path={'datacenterId': 'int'}) + except exceptions.ParameterError as e: + return Response(400, str(e)) + + # Instantiate a Datacenter from the database + + datacenter = Datacenter.from_primary_key((request.params_path['datacenterId'], )) + + # Make sure this Datacenter exists + + if not datacenter.exists(): + return Response(404, '{} not found.'.format(datacenter)) + + # Make sure this user is authorized to view this Datacenter's Rooms + + if not datacenter.google_id_has_at_least(request.google_id, 'VIEW'): + return Response(403, 'Forbidden from viewing Rooms for {}.'.format(datacenter)) + + # Get and return the Rooms + + rooms = Room.query('datacenter_id', datacenter.id) + + return Response(200, 'Successfully retrieved Rooms for {}.'.format(datacenter), [x.to_JSON() for x in rooms]) + + +def POST(request): + """Add a Room.""" + + # Make sure required parameters are there + + try: + request.check_required_parameters(path={'datacenterId': 'int'}, + body={'room': { + 'id': 'int', + 'datacenterId': 'int', + 'roomType': 'string' + }}) + except exceptions.ParameterError as e: + return Response(400, str(e)) + + # Make sure the passed object's datacenter id matches the path datacenter id + + if request.params_path['datacenterId'] != request.params_body['room']['datacenterId']: + return Response(400, 'ID mismatch.') + + # Instantiate a Datacenter from the database + + datacenter = Datacenter.from_primary_key((request.params_path['datacenterId'], )) + + # Make sure this Datacenter exists + + if not datacenter.exists(): + return Response(404, '{} not found.'.format(datacenter)) + + # Make sure this user is authorized to edit this Datacenter's Rooms + + if not datacenter.google_id_has_at_least(request.google_id, 'EDIT'): + return Response(403, 'Forbidden from adding a Room to {}.'.format(datacenter)) + + # Add a name if not provided + + if 'name' not in request.params_body['room']: + room_count = len(Room.query('datacenter_id', datacenter.id)) + request.params_body['room']['name'] = 'Room {}'.format(room_count) + + # Instantiate a Room + + room = Room.from_JSON(request.params_body['room']) + + # Try to insert this Room + + try: + room.insert() + except: + return Response(400, 'Invalid `roomType` or existing `name`.') + + # Return this Room + + room.read() + + return Response(200, 'Successfully added {}.'.format(room), room.to_JSON()) diff --git a/web-server/opendc/api/v2/topologies/topologyId/test_endpoint.py b/web-server/opendc/api/v2/topologies/topologyId/test_endpoint.py new file mode 100644 index 00000000..e54052aa --- /dev/null +++ b/web-server/opendc/api/v2/topologies/topologyId/test_endpoint.py @@ -0,0 +1,46 @@ +from opendc.util.database import DB + +''' +GET /topologies/{topologyId} +''' + +def test_get_topology(client, mocker): + mocker.patch.object(DB, 'fetch_one', return_value={ + '_id': '1', + 'authorizations': [{ + 'topologyId': '1', + 'authorizationLevel': 'EDIT' + }] + }) + res = client.get('/api/v2/topologies/1') + assert '200' in res.status + +def test_get_topology_non_existing(client, mocker): + mocker.patch.object(DB, 'fetch_one', return_value=None) + assert '404' in client.get('/api/v2/topologies/1').status + +def test_get_topology_not_authorized(client, mocker): + mocker.patch.object(DB, 'fetch_one', return_value={ + '_id': '1', + 'authorizations': [{ + 'topologyId': '2', + 'authorizationLevel': 'OWN' + }] + }) + res = client.get('/api/v2/topologies/1') + assert '403' in res.status + +def test_get_topology_no_authorizations(client, mocker): + mocker.patch.object(DB, 'fetch_one', return_value={'authorizations': []}) + res = client.get('/api/v2/topologies/1') + assert '403' in res.status + + +''' +PUT /topologies/{topologyId} +''' + + +''' +DELETE /topologies/{topologyId} +'''
\ No newline at end of file diff --git a/web-server/opendc/api/v2/traces/__init__.py b/web-server/opendc/api/v2/traces/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/web-server/opendc/api/v2/traces/__init__.py diff --git a/web-server/opendc/api/v2/traces/endpoint.py b/web-server/opendc/api/v2/traces/endpoint.py new file mode 100644 index 00000000..58cc6153 --- /dev/null +++ b/web-server/opendc/api/v2/traces/endpoint.py @@ -0,0 +1,14 @@ +from opendc.models_old.trace import Trace +from opendc.util.rest import Response + + +def GET(request): + """Get all available Traces.""" + + # Get the Traces + + traces = Trace.query() + + # Return the Traces + + return Response(200, 'Successfully retrieved Traces', [x.to_JSON() for x in traces]) diff --git a/web-server/opendc/api/v2/traces/traceId/__init__.py b/web-server/opendc/api/v2/traces/traceId/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/web-server/opendc/api/v2/traces/traceId/__init__.py diff --git a/web-server/opendc/api/v2/traces/traceId/endpoint.py b/web-server/opendc/api/v2/traces/traceId/endpoint.py new file mode 100644 index 00000000..f6442a31 --- /dev/null +++ b/web-server/opendc/api/v2/traces/traceId/endpoint.py @@ -0,0 +1,26 @@ +from opendc.models_old.trace import Trace +from opendc.util import exceptions +from opendc.util.rest import Response + + +def GET(request): + """Get this Trace.""" + + # Make sure required parameters are there + + try: + request.check_required_parameters(path={'traceId': 'int'}) + + except exceptions.ParameterError as e: + return Response(400, str(e)) + + # Instantiate a Trace and make sure it exists + + trace = Trace.from_primary_key((request.params_path['traceId'], )) + + if not trace.exists(): + return Response(404, '{} not found.'.format(trace)) + + # Return this Trace + + return Response(200, 'Successfully retrieved {}.'.format(trace), trace.to_JSON()) diff --git a/web-server/opendc/api/v2/users/__init__.py b/web-server/opendc/api/v2/users/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/web-server/opendc/api/v2/users/__init__.py diff --git a/web-server/opendc/api/v2/users/endpoint.py b/web-server/opendc/api/v2/users/endpoint.py new file mode 100644 index 00000000..c6041756 --- /dev/null +++ b/web-server/opendc/api/v2/users/endpoint.py @@ -0,0 +1,31 @@ +from opendc.models.user import User +from opendc.util import exceptions +from opendc.util.database import DB +from opendc.util.rest import Response + + +def GET(request): + """Search for a User using their email address.""" + + request.check_required_parameters(query={'email': 'string'}) + + user = User.from_email(request.params_query['email']) + + user.check_exists() + + return Response(200, f'Successfully retrieved user.', user.obj) + + +def POST(request): + """Add a new User.""" + + request.check_required_parameters(body={'user': {'email': 'string'}}) + + user = User(request.params_body['user']) + user.set_property('googleId', request.google_id) + user.set_property('authorizations', []) + + user.check_already_exists() + + user.insert() + return Response(200, f'Successfully created user.', user.obj) diff --git a/web-server/opendc/api/v2/users/test_endpoint.py b/web-server/opendc/api/v2/users/test_endpoint.py new file mode 100644 index 00000000..d60429b3 --- /dev/null +++ b/web-server/opendc/api/v2/users/test_endpoint.py @@ -0,0 +1,34 @@ +from opendc.util.database import DB + + +def test_get_user_by_email_missing_parameter(client): + assert '400' in client.get('/api/v2/users').status + + +def test_get_user_by_email_non_existing(client, mocker): + mocker.patch.object(DB, 'fetch_one', return_value=None) + assert '404' in client.get('/api/v2/users?email=test@test.com').status + + +def test_get_user_by_email(client, mocker): + mocker.patch.object(DB, 'fetch_one', return_value={'email': 'test@test.com'}) + res = client.get('/api/v2/users?email=test@test.com') + assert 'email' in res.json['content'] + assert '200' in res.status + + +def test_add_user_missing_parameter(client): + assert '400' in client.post('/api/v2/users').status + + +def test_add_user_existing(client, mocker): + mocker.patch.object(DB, 'fetch_one', return_value={'email': 'test@test.com'}) + assert '409' in client.post('/api/v2/users', json={'user': {'email': 'test@test.com'}}).status + + +def test_add_user(client, mocker): + mocker.patch.object(DB, 'fetch_one', return_value=None) + mocker.patch.object(DB, 'insert', return_value={'email': 'test@test.com'}) + res = client.post('/api/v2/users', json={'user': {'email': 'test@test.com'}}) + assert 'email' in res.json['content'] + assert '200' in res.status diff --git a/web-server/opendc/api/v2/users/userId/__init__.py b/web-server/opendc/api/v2/users/userId/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/web-server/opendc/api/v2/users/userId/__init__.py diff --git a/web-server/opendc/api/v2/users/userId/endpoint.py b/web-server/opendc/api/v2/users/userId/endpoint.py new file mode 100644 index 00000000..e68a2bb3 --- /dev/null +++ b/web-server/opendc/api/v2/users/userId/endpoint.py @@ -0,0 +1,52 @@ +from opendc.models.user import User +from opendc.util import exceptions +from opendc.util.rest import Response + + +def GET(request): + """Get this User.""" + + request.check_required_parameters(path={'userId': 'string'}) + + user = User.from_id(request.params_path['userId']) + + user.check_exists() + + return Response(200, f'Successfully retrieved user.', user.obj) + + +def PUT(request): + """Update this User's given name and/or family name.""" + + request.check_required_parameters(body={'user': { + 'givenName': 'string', + 'familyName': 'string' + }}, + path={'userId': 'string'}) + + user = User.from_id(request.params_path['userId']) + + user.check_exists() + user.check_correct_user(request.google_id) + + user.set_property('givenName', request.params_body['user']['givenName']) + user.set_property('familyName', request.params_body['user']['familyName']) + + user.update() + + return Response(200, f'Successfully updated user.', user.obj) + + +def DELETE(request): + """Delete this User.""" + + request.check_required_parameters(path={'userId': 'string'}) + + user = User.from_id(request.params_path['userId']) + + user.check_exists() + user.check_correct_user(request.google_id) + + user.delete() + + return Response(200, f'Successfully deleted user.', user.obj) diff --git a/web-server/opendc/api/v2/users/userId/test_endpoint.py b/web-server/opendc/api/v2/users/userId/test_endpoint.py new file mode 100644 index 00000000..0d590129 --- /dev/null +++ b/web-server/opendc/api/v2/users/userId/test_endpoint.py @@ -0,0 +1,53 @@ +from opendc.util.database import DB + + +def test_get_user_non_existing(client, mocker): + mocker.patch.object(DB, 'fetch_one', return_value=None) + assert '404' in client.get('/api/v2/users/1').status + + +def test_get_user(client, mocker): + mocker.patch.object(DB, 'fetch_one', return_value={'email': 'test@test.com'}) + res = client.get('/api/v2/users/1') + assert 'email' in res.json['content'] + assert '200' in res.status + + +def test_update_user_missing_parameter(client): + assert '400' in client.put('/api/v2/users/1').status + + +def test_update_user_non_existing(client, mocker): + mocker.patch.object(DB, 'fetch_one', return_value=None) + assert '404' in client.put('/api/v2/users/1', json={'user': {'givenName': 'A', 'familyName': 'B'}}).status + + +def test_update_user_different_user(client, mocker): + mocker.patch.object(DB, 'fetch_one', return_value={'_id': '1', 'googleId': 'other_test'}) + assert '403' in client.put('/api/v2/users/1', json={'user': {'givenName': 'A', 'familyName': 'B'}}).status + + +def test_update_user(client, mocker): + mocker.patch.object(DB, 'fetch_one', return_value={'_id': '1', 'googleId': 'test'}) + mocker.patch.object(DB, 'update', return_value={'givenName': 'A', 'familyName': 'B'}) + res = client.put('/api/v2/users/1', json={'user': {'givenName': 'A', 'familyName': 'B'}}) + assert 'givenName' in res.json['content'] + assert '200' in res.status + + +def test_delete_user_non_existing(client, mocker): + mocker.patch.object(DB, 'fetch_one', return_value=None) + assert '404' in client.delete('/api/v2/users/1').status + + +def test_delete_user_different_user(client, mocker): + mocker.patch.object(DB, 'fetch_one', return_value={'_id': '1', 'googleId': 'other_test'}) + assert '403' in client.delete('/api/v2/users/1').status + + +def test_delete_user(client, mocker): + mocker.patch.object(DB, 'fetch_one', return_value={'_id': '1', 'googleId': 'test'}) + mocker.patch.object(DB, 'delete_one', return_value={'googleId': 'test'}) + res = client.delete('/api/v2/users/1') + assert 'googleId' in res.json['content'] + assert '200' in res.status diff --git a/web-server/opendc/models/__init__.py b/web-server/opendc/models/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/web-server/opendc/models/__init__.py diff --git a/web-server/opendc/models/model.py b/web-server/opendc/models/model.py new file mode 100644 index 00000000..2505ae61 --- /dev/null +++ b/web-server/opendc/models/model.py @@ -0,0 +1,30 @@ +from opendc.util.database import DB +from opendc.util.exceptions import ClientError +from opendc.util.rest import Response + + +class Model: + collection_name = '<specified in subclasses>' + + @classmethod + def from_id(cls, _id): + return cls(DB.fetch_one({'_id': _id}, Model.collection_name)) + + def __init__(self, obj): + self.obj = obj + + def check_exists(self): + if self.obj is None: + raise ClientError(Response(404, 'Not found.')) + + def set_property(self, key, value): + self.obj[key] = value + + def insert(self): + self.obj = DB.insert(self.obj, self.collection_name) + + def update(self): + self.obj = DB.update(self.obj['_id'], self.obj, self.collection_name) + + def delete(self): + self.obj = DB.delete_one({'_id': self.obj['_id']}, self.collection_name) diff --git a/web-server/opendc/models/simulation.py b/web-server/opendc/models/simulation.py new file mode 100644 index 00000000..5cd3d49e --- /dev/null +++ b/web-server/opendc/models/simulation.py @@ -0,0 +1,15 @@ +from opendc.models.model import Model +from opendc.models.user import User +from opendc.util.exceptions import ClientError +from opendc.util.rest import Response + + +class Simulation(Model): + collection_name = 'simulations' + + def check_user_access(self, google_id, edit_access): + user = User.from_google_id(google_id) + authorizations = list( + filter(lambda x: str(x['simulationId']) == str(self.obj['_id']), user.obj['authorizations'])) + if len(authorizations) == 0 or (edit_access and authorizations[0]['authorizationLevel'] == 'VIEW'): + raise ClientError(Response(403, "Forbidden from retrieving simulation.")) diff --git a/web-server/opendc/models/topology.py b/web-server/opendc/models/topology.py new file mode 100644 index 00000000..6dde3e2a --- /dev/null +++ b/web-server/opendc/models/topology.py @@ -0,0 +1,15 @@ +from opendc.models.model import Model +from opendc.models.user import User +from opendc.util.exceptions import ClientError +from opendc.util.rest import Response + + +class Topology(Model): + collection_name = 'topologies' + + def check_user_access(self, google_id, edit_access): + user = User.from_google_id(google_id) + authorizations = list( + filter(lambda x: str(x['topologyId']) == str(self.obj['_id']), user.obj['authorizations'])) + if len(authorizations) == 0 or (edit_access and authorizations[0]['authorizationLevel'] == 'VIEW'): + raise ClientError(Response(403, "Forbidden from retrieving topology.")) diff --git a/web-server/opendc/models/trace.py b/web-server/opendc/models/trace.py new file mode 100644 index 00000000..916db073 --- /dev/null +++ b/web-server/opendc/models/trace.py @@ -0,0 +1,8 @@ +from opendc.models.model import Model +from opendc.models.user import User +from opendc.util.exceptions import ClientError +from opendc.util.rest import Response + + +class Trace(Model): + collection_name = 'traces' diff --git a/web-server/opendc/models/user.py b/web-server/opendc/models/user.py new file mode 100644 index 00000000..cd314457 --- /dev/null +++ b/web-server/opendc/models/user.py @@ -0,0 +1,26 @@ +from opendc.models.model import Model +from opendc.util.database import DB +from opendc.util.exceptions import ClientError +from opendc.util.rest import Response + + +class User(Model): + collection_name = 'users' + + @classmethod + def from_email(cls, email): + return User(DB.fetch_one({'email': email}, User.collection_name)) + + @classmethod + def from_google_id(cls, google_id): + return User(DB.fetch_one({'googleId': google_id}, User.collection_name)) + + def check_correct_user(self, request_google_id): + if request_google_id is not None and self.obj['googleId'] != request_google_id: + raise ClientError(Response(403, f'Forbidden from editing user with ID {self.obj["_id"]}.')) + + def check_already_exists(self): + existing_user = DB.fetch_one({'googleId': self.obj['googleId']}, self.collection_name) + + if existing_user is not None: + raise ClientError(Response(409, 'User already exists.')) diff --git a/web-server/opendc/models_old/__init__.py b/web-server/opendc/models_old/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/web-server/opendc/models_old/__init__.py diff --git a/web-server/opendc/models_old/allowed_object.py b/web-server/opendc/models_old/allowed_object.py new file mode 100644 index 00000000..bcadf025 --- /dev/null +++ b/web-server/opendc/models_old/allowed_object.py @@ -0,0 +1,22 @@ +from opendc.models_old.model import Model + + +class AllowedObject(Model): + JSON_TO_PYTHON_DICT = {'AllowedObject': {'roomType': 'room_type', 'objectType': 'object_type'}} + + COLLECTION_NAME = 'allowed_objects' + COLUMNS = ['room_type', 'object_type'] + COLUMNS_PRIMARY_KEY = ['room_type', 'object_type'] + + def google_id_has_at_least(self, google_id, authorization_level): + """Return True if the user has at least the given auth level over this AllowedObject.""" + + if authorization_level in ['EDIT', 'OWN']: + return False + + return True + + def to_JSON(self): + """Return a JSON representation of this AllowedObject.""" + + return self.object_type diff --git a/web-server/opendc/models_old/authorization.py b/web-server/opendc/models_old/authorization.py new file mode 100644 index 00000000..43d784e9 --- /dev/null +++ b/web-server/opendc/models_old/authorization.py @@ -0,0 +1,45 @@ +from opendc.models_old.model import Model +from opendc.models_old.user import User + + +class Authorization(Model): + JSON_TO_PYTHON_DICT = { + 'Authorization': { + 'userId': 'user_id', + 'simulationId': 'simulation_id', + 'authorizationLevel': 'authorization_level' + } + } + + COLLECTION_NAME = 'authorizations' + COLUMNS = ['user_id', 'simulation_id', 'authorization_level'] + COLUMNS_PRIMARY_KEY = ['user_id', 'simulation_id'] + + def google_id_has_at_least(self, google_id, authorization_level): + """Return True if the User has at least the given auth level over this Authorization.""" + + authorization = Authorization.from_primary_key((User.from_google_id(google_id).id, self.simulation_id)) + + if authorization is None: + return False + + return authorization.has_at_least(authorization_level) + + def has_at_least(self, required_level): + """Return True if this Authorization has at least the required level.""" + + if not self.exists(): + return False + + authorization_levels = ['VIEW', 'EDIT', 'OWN'] + + try: + index_actual = authorization_levels.index(self.authorization_level) + index_required = authorization_levels.index(required_level) + except: + return False + + if index_actual >= index_required: + return True + else: + return False diff --git a/web-server/opendc/models_old/cpu.py b/web-server/opendc/models_old/cpu.py new file mode 100644 index 00000000..0f50ce1c --- /dev/null +++ b/web-server/opendc/models_old/cpu.py @@ -0,0 +1,34 @@ +from opendc.models_old.model import Model + + +class CPU(Model): + JSON_TO_PYTHON_DICT = { + 'CPU': { + 'id': 'id', + 'manufacturer': 'manufacturer', + 'family': 'family', + 'generation': 'generation', + 'model': 'model', + 'clockRateMhz': 'clock_rate_mhz', + 'numberOfCores': 'number_of_cores', + 'energyConsumptionW': 'energy_consumption_w', + 'failureModelId': 'failure_model_id' + } + } + + COLLECTION_NAME = 'cpus' + + COLUMNS = [ + 'id', 'manufacturer', 'family', 'generation', 'model', 'clock_rate_mhz', 'number_of_cores', + 'energy_consumption_w', 'failure_model_id' + ] + + COLUMNS_PRIMARY_KEY = ['id'] + + def google_id_has_at_least(self, google_id, authorization_level): + """Return True if the User has at least the given auth level over this CPU.""" + + if authorization_level in ['EDIT', 'OWN']: + return False + + return True diff --git a/web-server/opendc/models_old/datacenter.py b/web-server/opendc/models_old/datacenter.py new file mode 100644 index 00000000..b1ed2eee --- /dev/null +++ b/web-server/opendc/models_old/datacenter.py @@ -0,0 +1,27 @@ +from opendc.models_old.model import Model +from opendc.models_old.section import Section + + +class Datacenter(Model): + JSON_TO_PYTHON_DICT = {'datacenter': {'id': 'id', 'starred': 'starred', 'simulationId': 'simulation_id'}} + + PATH = '/v1/simulations/{simulationId}/datacenters' + + COLLECTION_NAME = 'datacenters' + COLUMNS = ['id', 'simulation_id', 'starred'] + COLUMNS_PRIMARY_KEY = ['id'] + + def google_id_has_at_least(self, google_id, authorization_level): + """Return True if the user has at least the given auth level over this Datacenter.""" + + # Get a Section that contains this Datacenter. It doesn't matter which one, since all Sections that have this + # Datacenter belong to the same Simulation, so the User's Authorization is the same for each one. + + try: + section = Section.query('datacenter_id', self.id)[0] + except: + return False + + # Check the Section's Authorization + + return section.google_id_has_at_least(google_id, authorization_level) diff --git a/web-server/opendc/models_old/experiment.py b/web-server/opendc/models_old/experiment.py new file mode 100644 index 00000000..64b99212 --- /dev/null +++ b/web-server/opendc/models_old/experiment.py @@ -0,0 +1,36 @@ +from opendc.models_old.model import Model +from opendc.models_old.simulation import Simulation +from opendc.util import exceptions + + +class Experiment(Model): + JSON_TO_PYTHON_DICT = { + 'Experiment': { + 'id': 'id', + 'simulationId': 'simulation_id', + 'pathId': 'path_id', + 'traceId': 'trace_id', + 'schedulerName': 'scheduler_name', + 'name': 'name', + 'state': 'state', + 'lastSimulatedTick': 'last_simulated_tick' + } + } + + COLLECTION_NAME = 'experiments' + COLUMNS = ['id', 'simulation_id', 'path_id', 'trace_id', 'scheduler_name', 'name', 'state', 'last_simulated_tick'] + COLUMNS_PRIMARY_KEY = ['id'] + + def google_id_has_at_least(self, google_id, authorization_level): + """Return True if the user has at least the given auth level over this Experiment.""" + + # Get the Simulation + + try: + simulation = Simulation.from_primary_key((self.simulation_id, )) + except exceptions.RowNotFoundError: + return False + + # Check the Simulation's Authorization + + return simulation.google_id_has_at_least(google_id, authorization_level) diff --git a/web-server/opendc/models_old/failure_model.py b/web-server/opendc/models_old/failure_model.py new file mode 100644 index 00000000..d1a8c1cc --- /dev/null +++ b/web-server/opendc/models_old/failure_model.py @@ -0,0 +1,17 @@ +from opendc.models_old.model import Model + + +class FailureModel(Model): + JSON_TO_PYTHON_DICT = {'FailureModel': {'id': 'id', 'name': 'name', 'rate': 'rate'}} + + COLLECTION_NAME = 'failure_models' + COLUMNS = ['id', 'name', 'rate'] + COLUMNS_PRIMARY_KEY = ['id'] + + def google_id_has_at_least(self, google_id, authorization_level): + """Return True if the user has at least the given auth level over this FailureModel.""" + + if authorization_level in ['EDIT', 'OWN']: + return False + + return True diff --git a/web-server/opendc/models_old/gpu.py b/web-server/opendc/models_old/gpu.py new file mode 100644 index 00000000..31b3b6b1 --- /dev/null +++ b/web-server/opendc/models_old/gpu.py @@ -0,0 +1,34 @@ +from opendc.models_old.model import Model + + +class GPU(Model): + JSON_TO_PYTHON_DICT = { + 'GPU': { + 'id': 'id', + 'manufacturer': 'manufacturer', + 'family': 'family', + 'generation': 'generation', + 'model': 'model', + 'clockRateMhz': 'clock_rate_mhz', + 'numberOfCores': 'number_of_cores', + 'energyConsumptionW': 'energy_consumption_w', + 'failureModelId': 'failure_model_id' + } + } + + COLLECTION_NAME = 'gpus' + + COLUMNS = [ + 'id', 'manufacturer', 'family', 'generation', 'model', 'clock_rate_mhz', 'number_of_cores', + 'energy_consumption_w', 'failure_model_id' + ] + + COLUMNS_PRIMARY_KEY = ['id'] + + def google_id_has_at_least(self, google_id, authorization_level): + """Return True if the User has at least the given auth level over this GPU.""" + + if authorization_level in ['EDIT', 'OWN']: + return False + + return True diff --git a/web-server/opendc/models_old/job.py b/web-server/opendc/models_old/job.py new file mode 100644 index 00000000..9cb7cd7e --- /dev/null +++ b/web-server/opendc/models_old/job.py @@ -0,0 +1,14 @@ +from opendc.models_old.model import Model + + +class Job(Model): + JSON_TO_PYTHON_DICT = {'Job': {'id': 'id', 'name': 'name'}} + + COLLECTION_NAME = 'jobs' + COLUMNS = ['id', 'name'] + COLUMNS_PRIMARY_KEY = ['id'] + + def google_id_has_at_least(self, google_id, authorization_level): + """Return True if the user has at least the given auth level over this Job.""" + + return authorization_level not in ['EDIT', 'OWN'] diff --git a/web-server/opendc/models_old/machine.py b/web-server/opendc/models_old/machine.py new file mode 100644 index 00000000..8e5ccb44 --- /dev/null +++ b/web-server/opendc/models_old/machine.py @@ -0,0 +1,122 @@ +import copy + +from opendc.models_old.model import Model +from opendc.models_old.rack import Rack +from opendc.util import database, exceptions + + +class Machine(Model): + JSON_TO_PYTHON_DICT = { + 'machine': { + 'id': 'id', + 'rackId': 'rack_id', + 'position': 'position', + 'tags': 'tags', + 'cpuIds': 'cpu_ids', + 'gpuIds': 'gpu_ids', + 'memoryIds': 'memory_ids', + 'storageIds': 'storage_ids', + 'topologyId': 'topology_id' + } + } + + PATH = '/v1/tiles/{tileId}/rack/machines' + + COLLECTION_NAME = 'machines' + COLUMNS = ['id', 'rack_id', 'position', 'topology_id'] + COLUMNS_PRIMARY_KEY = ['id'] + + device_table_to_attribute = { + 'cpus': 'cpu_ids', + 'gpus': 'gpu_ids', + 'memories': 'memory_ids', + 'storages': 'storage_ids' + } + + def _update_devices(self, before_insert): + """Update this Machine's devices in the database.""" + + for device_table in self.device_table_to_attribute.keys(): + + # First, delete current machine-device links + + statement = 'DELETE FROM machine_{} WHERE machine_id = %s'.format(device_table) + database.execute(statement, (before_insert.id, )) + + # Then, add current ones + + for device_id in getattr(before_insert, before_insert.device_table_to_attribute[device_table]): + statement = 'INSERT INTO machine_{} (machine_id, {}) VALUES (%s, %s)'.format( + device_table, before_insert.device_table_to_attribute[device_table][:-1]) + + database.execute(statement, (before_insert.id, device_id)) + + @classmethod + def from_tile_id_and_rack_position(cls, tile_id, position): + """Get a Rack from the ID of the tile its Rack is on, and its position in the Rack.""" + + try: + rack = Rack.from_tile_id(tile_id) + except: + return cls(id=-1) + + try: + statement = 'SELECT id FROM machines WHERE rack_id = %s AND position = %s' + machine_id = database.fetch_one(statement, (rack.id, position))[0] + except: + return cls(id=-1) + + return cls.from_primary_key((machine_id, )) + + def google_id_has_at_least(self, google_id, authorization_level): + """Return True if the user has at least the given auth level over this Machine.""" + + # Get the Rack + + try: + rack = Rack.from_primary_key((self.rack_id, )) + except exceptions.RowNotFoundError: + return False + + # Check the Rack's Authorization + + return rack.google_id_has_at_least(google_id, authorization_level) + + def insert(self): + """Insert this Machine by also updating its devices.""" + + before_insert = copy.deepcopy(self) + + super(Machine, self).insert() + + before_insert.id = self.id + self._update_devices(before_insert) + + def read(self): + """Read this Machine by also getting its CPU, GPU, Memory and Storage IDs.""" + + super(Machine, self).read() + + for device_table in self.device_table_to_attribute.keys(): + + statement = 'SELECT * FROM machine_{} WHERE machine_id = %s'.format(device_table) + results = database.fetch_all(statement, (self.id, )) + + device_ids = [] + + for row in results: + device_ids.append(row[2]) + + setattr(self, self.device_table_to_attribute[device_table], device_ids) + + setattr(self, 'tags', []) + + def update(self): + """Update this Machine by also updating its devices.""" + + before_update = copy.deepcopy(self) + + super(Machine, self).update() + + before_update.id = self.id + self._update_devices(before_update) diff --git a/web-server/opendc/models_old/machine_state.py b/web-server/opendc/models_old/machine_state.py new file mode 100644 index 00000000..0e9f7dad --- /dev/null +++ b/web-server/opendc/models_old/machine_state.py @@ -0,0 +1,71 @@ +from opendc.models_old.model import Model +from opendc.util import database + + +class MachineState(Model): + JSON_TO_PYTHON_DICT = { + 'MachineState': { + 'machineId': 'machine_id', + 'temperatureC': 'temperature_c', + 'inUseMemoryMb': 'in_use_memory_mb', + 'loadFraction': 'load_fraction', + 'tick': 'tick' + } + } + + COLLECTION_NAME = 'machine_states' + COLUMNS = ['id', 'machine_id', 'experiment_id', 'tick', 'temperature_c', 'in_use_memory_mb', 'load_fraction'] + + COLUMNS_PRIMARY_KEY = ['id'] + + @classmethod + def _from_database_row(cls, row): + """Instantiate a MachineState from a database row (including tick from the TaskState).""" + + return cls(machine_id=row[1], temperature_c=row[4], in_use_memory_mb=row[5], load_fraction=row[6], tick=row[3]) + + @classmethod + def from_experiment_id(cls, experiment_id): + """Query MachineStates by their Experiment id.""" + + machine_states = [] + + statement = 'SELECT * FROM machine_states WHERE experiment_id = %s' + results = database.fetch_all(statement, (experiment_id, )) + + for row in results: + machine_states.append(cls._from_database_row(row)) + + return machine_states + + @classmethod + def from_experiment_id_and_tick(cls, experiment_id, tick): + """Query MachineStates by their Experiment id and tick.""" + + machine_states = [] + + statement = 'SELECT * FROM machine_states WHERE experiment_id = %s AND machine_states.tick = %s' + results = database.fetch_all(statement, (experiment_id, tick)) + + for row in results: + machine_states.append(cls._from_database_row(row)) + + return machine_states + + def read(self): + """Read this MachineState by also getting its tick.""" + + super(MachineState, self).read() + + statement = 'SELECT tick FROM task_states WHERE id = %s' + result = database.fetch_one(statement, (self.task_state_id, )) + + self.tick = result[0] + + def google_id_has_at_least(self, google_id, authorization_level): + """Return True if the User has at least the given auth level over this MachineState.""" + + if authorization_level in ['EDIT', 'OWN']: + return False + + return True diff --git a/web-server/opendc/models_old/memory.py b/web-server/opendc/models_old/memory.py new file mode 100644 index 00000000..8edf8f5d --- /dev/null +++ b/web-server/opendc/models_old/memory.py @@ -0,0 +1,34 @@ +from opendc.models_old.model import Model + + +class Memory(Model): + JSON_TO_PYTHON_DICT = { + 'Memory': { + 'id': 'id', + 'manufacturer': 'manufacturer', + 'family': 'family', + 'generation': 'generation', + 'model': 'model', + 'speedMbPerS': 'speed_mb_per_s', + 'sizeMb': 'size_mb', + 'energyConsumptionW': 'energy_consumption_w', + 'failureModelId': 'failure_model_id' + } + } + + COLLECTION_NAME = 'memories' + + COLUMNS = [ + 'id', 'manufacturer', 'family', 'generation', 'model', 'speed_mb_per_s', 'size_mb', 'energy_consumption_w', + 'failure_model_id' + ] + + COLUMNS_PRIMARY_KEY = ['id'] + + def google_id_has_at_least(self, google_id, authorization_level): + """Return True if the User has at least the given auth level over this Memory.""" + + if authorization_level in ['EDIT', 'OWN']: + return False + + return True diff --git a/web-server/opendc/models_old/model.py b/web-server/opendc/models_old/model.py new file mode 100644 index 00000000..73eabd26 --- /dev/null +++ b/web-server/opendc/models_old/model.py @@ -0,0 +1,303 @@ +from opendc.util import database, exceptions + + +class Model(object): + # MUST OVERRIDE IN DERIVED CLASS + + JSON_TO_PYTHON_DICT = {'Model': {'jsonParameterName': 'python_parameter_name'}} + + PATH = '' + PATH_PARAMETERS = {} + + COLLECTION_NAME = '' + COLUMNS = [] + COLUMNS_PRIMARY_KEY = [] + + # INITIALIZATION + + def __init__(self, **kwargs): + """Initialize a model from its keyword arguments.""" + + for name, value in kwargs.items(): + setattr(self, name, value) + + def __repr__(self): + """Return a string representation of this object.""" + + identifiers = [] + + for attribute in self.COLUMNS_PRIMARY_KEY: + identifiers.append('{} = {}'.format(attribute, getattr(self, attribute))) + + return '{} ({})'.format(self.COLLECTION_NAME[:-1].title().replace('_', ''), '; '.join(identifiers)) + + # JSON CONVERSION METHODS + + @classmethod + def from_JSON(cls, json_object): + """Initialize a Model from its JSON object representation.""" + + parameters = {} + parameter_map = cls.JSON_TO_PYTHON_DICT.values()[0] + + for json_name in parameter_map: + + python_name = parameter_map[json_name] + + if json_name in json_object: + parameters[python_name] = json_object.get(json_name) + + else: + parameters[python_name] = None + + return cls(**parameters) + + def to_JSON(self): + """Return a JSON-serializable object representation of this Model.""" + + parameters = {} + parameter_map = self.JSON_TO_PYTHON_DICT.values()[0] + + for json_name in parameter_map: + + python_name = parameter_map[json_name] + + if hasattr(self, python_name): + parameters[json_name] = getattr(self, python_name) + + else: + parameters[json_name] = None + + return parameters + + # API CALL GENERATION + + def generate_api_call(self, path_parameters, token): + """Return a message that can be executed by a Request to recreate this object.""" + + return { + 'id': 0, + 'path': self.PATH, + 'method': 'POST', + 'parameters': { + 'body': { + self.JSON_TO_PYTHON_DICT.keys()[0]: self.to_JSON() + }, + 'path': path_parameters, + 'query': {} + }, + 'token': token + } + + # SQL STATEMENT GENERATION METHODS + + @classmethod + def _generate_insert_columns_string(cls): + """Generate a SQLite insertion columns string for this Model""" + + return ', '.join(cls.COLUMNS) + + @classmethod + def _generate_insert_placeholders_string(cls): + """Generate a SQLite insertion placeholders string for this Model.""" + + return ', '.join(['%s'] * len(cls.COLUMNS)) + + @classmethod + def _generate_primary_key_string(cls): + """Generate the SQLite primary key string for this Model.""" + + return ' AND '.join(['{} = %s'.format(x) for x in cls.COLUMNS_PRIMARY_KEY]) + + @classmethod + def _generate_update_columns_string(cls): + """Generate a SQLite updatable columns string for this Model.""" + + return ', '.join(['{} = %s'.format(x) for x in cls.COLUMNS if x not in cls.COLUMNS_PRIMARY_KEY]) + + # SQL TUPLE GENERATION METHODS + + def _generate_insert_columns_tuple(self): + """Generate the tuple of insertion column values for this object.""" + + value_list = [] + + for column in self.COLUMNS: + value_list.append(getattr(self, column, None)) + + return tuple(value_list) + + def _generate_primary_key_tuple(self): + """Generate the tuple of primary key values for this object.""" + + primary_key_list = [] + + for column in self.COLUMNS_PRIMARY_KEY: + primary_key_list.append(getattr(self, column, None)) + + return tuple(primary_key_list) + + def _generate_update_columns_tuple(self): + """Generate the tuple of updatable column values for this object.""" + + value_list = [] + + for column in [x for x in self.COLUMNS if x not in self.COLUMNS_PRIMARY_KEY]: + value_list.append(getattr(self, column, None)) + + return tuple(value_list) + + # DATABASE HELPER METHODS + + @classmethod + def _from_database(cls, statement, values): + """Initialize a Model by fetching it from the database.""" + + parameters = {} + model_from_database = database.fetch_one(statement, values) + + if model_from_database is None: + return None + + for i in range(len(cls.COLUMNS)): + parameters[cls.COLUMNS[i]] = model_from_database[i] + + return cls(**parameters) + + # PUBLIC DATABASE INTERACTION METHODS + + @classmethod + def from_primary_key(cls, primary_key_tuple): + """Initialize a Model by fetching it by its primary key tuple. + + If the primary key does not exist in the database, return a stub. + """ + + query = 'SELECT * FROM {} WHERE {}'.format(cls.COLLECTION_NAME, cls._generate_primary_key_string()) + + # Return an instantiation of the Model with values from the row if it exists + + model = cls._from_database(query, primary_key_tuple) + if model is not None: + return model + + # Return a stub instantiation of the Model with just the primary key if it does not + + parameters = {} + for i, column in enumerate(cls.COLUMNS_PRIMARY_KEY): + parameters[column] = primary_key_tuple[i] + + return cls(**parameters) + + @classmethod + def query(cls, column_name=None, value=None): + """Return all instances of the Model in the database where column_name = value.""" + + if column_name is not None and value is not None: + statement = 'SELECT * FROM {} WHERE {} = %s'.format(cls.COLLECTION_NAME, column_name) + database_models = database.fetch_all(statement, (value, )) + + else: + statement = 'SELECT * FROM {}'.format(cls.COLLECTION_NAME) + database_models = database.fetch_all(statement) + + models = [] + + for database_model in database_models: + + parameters = {} + for i, parameter in enumerate(cls.COLUMNS): + parameters[parameter] = database_model[i] + + models.append(cls(**parameters)) + + return models + + def delete(self): + """Delete this Model from the database.""" + + self.read() + + statement = 'DELETE FROM {} WHERE {}'.format(self.COLLECTION_NAME, self._generate_primary_key_string()) + + values = self._generate_primary_key_tuple() + + database.execute(statement, values) + + def exists(self, column=None): + """Return True if this Model exists in the database. + + Check the primary key by default, or a column if one is specified. + """ + + query = """ + SELECT EXISTS ( + SELECT 1 FROM {} + WHERE {} + LIMIT 1 + ) + """ + + if column is None: + query = query.format(self.COLLECTION_NAME, self._generate_primary_key_string()) + values = self._generate_primary_key_tuple() + + else: + query = query.format(self.COLLECTION_NAME, '{} = %s'.format(column)) + values = (getattr(self, column), ) + + return database.fetch_one(query, values)[0] == 1 + + def insert(self): + """Insert this Model into the database.""" + + if hasattr(self, 'id'): + self.id = None + + self.insert_with_id() + + def insert_with_id(self, is_auto_generated=True): + """Insert this Model into the database without removing its id.""" + + statement = 'INSERT INTO {} ({}) VALUES ({})'.format(self.COLLECTION_NAME, + self._generate_insert_columns_string(), + self._generate_insert_placeholders_string()) + + values = self._generate_insert_columns_tuple() + + try: + last_row_id = database.execute(statement, values) + except Exception as e: + print(e) + raise exceptions.ForeignKeyError(e) + + if 'id' in self.COLUMNS_PRIMARY_KEY: + if is_auto_generated: + setattr(self, 'id', last_row_id) + self.read() + + def read(self): + """Read this Model's non-primary key attributes from the database.""" + + if not self.exists(): + raise exceptions.RowNotFoundError(self.COLLECTION_NAME) + + database_model = self.from_primary_key(self._generate_primary_key_tuple()) + + for attribute in self.COLUMNS: + setattr(self, attribute, getattr(database_model, attribute)) + + return self + + def update(self): + """Update this Model's non-primary key attributes in the database.""" + + statement = 'UPDATE {} SET {} WHERE {}'.format(self.COLLECTION_NAME, self._generate_update_columns_string(), + self._generate_primary_key_string()) + + values = self._generate_update_columns_tuple() + self._generate_primary_key_tuple() + + try: + database.execute(statement, values) + except Exception as e: + raise exceptions.ForeignKeyError(e) diff --git a/web-server/opendc/models_old/object.py b/web-server/opendc/models_old/object.py new file mode 100644 index 00000000..8f2e308b --- /dev/null +++ b/web-server/opendc/models_old/object.py @@ -0,0 +1,14 @@ +from opendc.models_old.model import Model + + +class Object(Model): + JSON_TO_PYTHON_DICT = {'Object': {'id': 'id', 'type': 'type'}} + + COLLECTION_NAME = 'objects' + COLUMNS = ['id', 'type'] + COLUMNS_PRIMARY_KEY = ['id'] + + def google_id_has_at_least(self, google_id, authorization_level): + """Return True if the user has at least the given auth level over this Tile.""" + + return True diff --git a/web-server/opendc/models_old/path.py b/web-server/opendc/models_old/path.py new file mode 100644 index 00000000..4d07b2d5 --- /dev/null +++ b/web-server/opendc/models_old/path.py @@ -0,0 +1,35 @@ +from opendc.models_old.authorization import Authorization +from opendc.models_old.model import Model +from opendc.models_old.user import User +from opendc.util import exceptions + + +class Path(Model): + JSON_TO_PYTHON_DICT = { + 'Path': { + 'id': 'id', + 'simulationId': 'simulation_id', + 'name': 'name', + 'datetimeCreated': 'datetime_created' + } + } + + COLLECTION_NAME = 'paths' + COLUMNS = ['id', 'simulation_id', 'name', 'datetime_created'] + COLUMNS_PRIMARY_KEY = ['id'] + + def google_id_has_at_least(self, google_id, authorization_level): + """Return True if the user has at least the given auth level over this Path.""" + + # Get the User id + + try: + user_id = User.from_google_id(google_id).read().id + except exceptions.RowNotFoundError: + return False + + # Check the Authorization + + authorization = Authorization.from_primary_key((user_id, self.simulation_id)) + + return authorization.has_at_least(authorization_level) diff --git a/web-server/opendc/models_old/queued_experiment.py b/web-server/opendc/models_old/queued_experiment.py new file mode 100644 index 00000000..b474dc31 --- /dev/null +++ b/web-server/opendc/models_old/queued_experiment.py @@ -0,0 +1,9 @@ +from opendc.models_old.model import Model + + +class QueuedExperiment(Model): + JSON_TO_PYTHON_DICT = {'QueuedExperiment': {'experimentId': 'experiment_id'}} + + COLLECTION_NAME = 'queued_experiments' + COLUMNS = ['experiment_id'] + COLUMNS_PRIMARY_KEY = ['experiment_id'] diff --git a/web-server/opendc/models_old/rack.py b/web-server/opendc/models_old/rack.py new file mode 100644 index 00000000..dc08eb6a --- /dev/null +++ b/web-server/opendc/models_old/rack.py @@ -0,0 +1,61 @@ +from opendc.models_old.model import Model +from opendc.models_old.object import Object +from opendc.models_old.tile import Tile + + +class Rack(Model): + JSON_TO_PYTHON_DICT = { + 'rack': { + 'id': 'id', + 'name': 'name', + 'capacity': 'capacity', + 'powerCapacityW': 'power_capacity_w', + 'topologyId': 'topology_id' + } + } + + PATH = '/v1/tiles/{tileId}/rack' + + COLLECTION_NAME = 'racks' + COLUMNS = ['id', 'name', 'capacity', 'power_capacity_w', 'topology_id'] + COLUMNS_PRIMARY_KEY = ['id'] + + @classmethod + def from_tile_id(cls, tile_id): + """Get a Rack from the ID of the Tile it's on.""" + + tile = Tile.from_primary_key((tile_id, )) + + if not tile.exists(): + return Rack(id=-1) + + return cls.from_primary_key((tile.object_id, )) + + def google_id_has_at_least(self, google_id, authorization_level): + """Return True if the user has at least the given auth level over this Rack.""" + + # Get the Tile + + try: + tile = Tile.query('object_id', self.id)[0] + except: + return False + + # Check the Tile's Authorization + + return tile.google_id_has_at_least(google_id, authorization_level) + + def insert(self): + """Insert a Rack by first inserting an object.""" + + obj = Object(type='RACK') + obj.insert() + + self.id = obj.id + self.insert_with_id(is_auto_generated=False) + + def delete(self): + """Delete a Rack by deleting its associated object.""" + + obj = Object.from_primary_key((self.id, )) + obj.delete() diff --git a/web-server/opendc/models_old/rack_state.py b/web-server/opendc/models_old/rack_state.py new file mode 100644 index 00000000..12e0f931 --- /dev/null +++ b/web-server/opendc/models_old/rack_state.py @@ -0,0 +1,63 @@ +from opendc.models_old.model import Model +from opendc.util import database + + +class RackState(Model): + JSON_TO_PYTHON_DICT = {'RackState': {'rackId': 'rack_id', 'loadFraction': 'load_fraction', 'tick': 'tick'}} + + @classmethod + def _from_database_row(cls, row): + """Instantiate a RackState from a database row.""" + + return cls(rack_id=row[0], load_fraction=row[1], tick=row[2]) + + @classmethod + def from_experiment_id(cls, experiment_id): + """Query RackStates by their Experiment id.""" + + rack_states = [] + + statement = ''' + SELECT racks.id, avg(machine_states.load_fraction), machine_states.tick + FROM racks + JOIN machines ON racks.id = machines.rack_id + JOIN machine_states ON machines.id = machine_states.machine_id + WHERE machine_states.experiment_id = %s + GROUP BY machine_states.tick, racks.id + ''' + results = database.fetch_all(statement, (experiment_id, )) + + for row in results: + rack_states.append(cls._from_database_row(row)) + + return rack_states + + @classmethod + def from_experiment_id_and_tick(cls, experiment_id, tick): + """Query RackStates by their Experiment id.""" + + rack_states = [] + + statement = ''' + SELECT racks.id, avg(machine_states.load_fraction), machine_states.tick + FROM racks + JOIN machines ON racks.id = machines.rack_id + JOIN machine_states ON machines.id = machine_states.machine_id + WHERE machine_states.experiment_id = %s + AND machine_states.tick = %s + GROUP BY machine_states.tick, racks.id + ''' + results = database.fetch_all(statement, (experiment_id, tick)) + + for row in results: + rack_states.append(cls._from_database_row(row)) + + return rack_states + + def google_id_has_at_least(self, google_id, authorization_level): + """Return True if the User has at least the given auth level over this RackState.""" + + if authorization_level in ['EDIT', 'OWN']: + return False + + return True diff --git a/web-server/opendc/models_old/room.py b/web-server/opendc/models_old/room.py new file mode 100644 index 00000000..e0eb7c2f --- /dev/null +++ b/web-server/opendc/models_old/room.py @@ -0,0 +1,35 @@ +from opendc.models_old.datacenter import Datacenter +from opendc.models_old.model import Model +from opendc.util import exceptions + + +class Room(Model): + JSON_TO_PYTHON_DICT = { + 'room': { + 'id': 'id', + 'datacenterId': 'datacenter_id', + 'name': 'name', + 'roomType': 'type', + 'topologyId': 'topology_id' + } + } + + PATH = '/v1/datacenters/{datacenterId}/rooms' + + COLLECTION_NAME = 'rooms' + COLUMNS = ['id', 'name', 'datacenter_id', 'type', 'topology_id'] + COLUMNS_PRIMARY_KEY = ['id'] + + def google_id_has_at_least(self, google_id, authorization_level): + """Return True if the user has at least the given auth level over this Room.""" + + # Get the Datacenter + + try: + datacenter = Datacenter.from_primary_key((self.datacenter_id, )) + except exceptions.RowNotFoundError: + return False + + # Check the Datacenter's Authorization + + return datacenter.google_id_has_at_least(google_id, authorization_level) diff --git a/web-server/opendc/models_old/room_state.py b/web-server/opendc/models_old/room_state.py new file mode 100644 index 00000000..c6635649 --- /dev/null +++ b/web-server/opendc/models_old/room_state.py @@ -0,0 +1,71 @@ +from opendc.models_old.model import Model +from opendc.util import database + + +class RoomState(Model): + JSON_TO_PYTHON_DICT = {'RoomState': {'roomId': 'room_id', 'loadFraction': 'load_fraction', 'tick': 'tick'}} + + @classmethod + def _from_database_row(cls, row): + """Instantiate a RoomState from a database row.""" + + return cls(room_id=row[0], load_fraction=row[1], tick=row[2]) + + @classmethod + def from_experiment_id(cls, experiment_id): + """Query RoomStates by their Experiment id.""" + + room_states = [] + + statement = ''' + SELECT rooms.id, avg(machine_states.load_fraction), machine_states.tick + FROM rooms + JOIN tiles ON rooms.id = tiles.room_id + JOIN objects ON tiles.object_id = objects.id + JOIN racks ON objects.id = racks.id + JOIN machines ON racks.id = machines.rack_id + JOIN machine_states ON machines.id = machine_states.machine_id + WHERE objects.type = "RACK" + AND machine_states.experiment_id = %s + GROUP BY machine_states.tick, rooms.id + ''' + results = database.fetch_all(statement, (experiment_id, )) + + for row in results: + room_states.append(cls._from_database_row(row)) + + return room_states + + @classmethod + def from_experiment_id_and_tick(cls, experiment_id, tick): + """Query RoomStates by their Experiment id.""" + + room_states = [] + + statement = ''' + SELECT rooms.id, avg(machine_states.load_fraction), machine_states.tick + FROM rooms + JOIN tiles ON rooms.id = tiles.room_id + JOIN objects ON tiles.object_id = objects.id + JOIN racks ON objects.id = racks.id + JOIN machines ON racks.id = machines.rack_id + JOIN machine_states ON machines.id = machine_states.machine_id + WHERE objects.type = "RACK" + AND machine_states.experiment_id = %s + AND machine_states.tick = %s + GROUP BY rooms.id + ''' + results = database.fetch_all(statement, (experiment_id, tick)) + + for row in results: + room_states.append(cls._from_database_row(row)) + + return room_states + + def google_id_has_at_least(self, google_id, authorization_level): + """Return True if the User has at least the given auth level over this RackState.""" + + if authorization_level in ['EDIT', 'OWN']: + return False + + return True diff --git a/web-server/opendc/models_old/room_type.py b/web-server/opendc/models_old/room_type.py new file mode 100644 index 00000000..755572f8 --- /dev/null +++ b/web-server/opendc/models_old/room_type.py @@ -0,0 +1,17 @@ +from opendc.models_old.model import Model + + +class RoomType(Model): + JSON_TO_PYTHON_DICT = {'RoomType': {'name': 'name'}} + + COLLECTION_NAME = 'room_types' + COLUMNS = ['name'] + COLUMNS_PRIMARY_KEY = ['name'] + + def google_id_has_at_least(self, google_id, authorization_level): + """Return True if the user has at least the given auth level over this RoomType.""" + + if authorization_level in ['EDIT', 'OWN']: + return False + + return True diff --git a/web-server/opendc/models_old/scheduler.py b/web-server/opendc/models_old/scheduler.py new file mode 100644 index 00000000..b9939321 --- /dev/null +++ b/web-server/opendc/models_old/scheduler.py @@ -0,0 +1,14 @@ +from opendc.models_old.model import Model + + +class Scheduler(Model): + JSON_TO_PYTHON_DICT = {'Scheduler': {'name': 'name'}} + + COLLECTION_NAME = 'schedulers' + COLUMNS = ['name'] + COLUMNS_PRIMARY_KEY = ['name'] + + def google_id_has_at_least(self, google_id, authorization_level): + """Return True if the user has at least the given auth level over this Scheduler.""" + + return authorization_level not in ['EDIT', 'OWN'] diff --git a/web-server/opendc/models_old/section.py b/web-server/opendc/models_old/section.py new file mode 100644 index 00000000..0df4967c --- /dev/null +++ b/web-server/opendc/models_old/section.py @@ -0,0 +1,32 @@ +from opendc.models_old.model import Model +from opendc.models_old.path import Path +from opendc.util import exceptions + + +class Section(Model): + JSON_TO_PYTHON_DICT = { + 'Section': { + 'id': 'id', + 'pathId': 'path_id', + 'datacenterId': 'datacenter_id', + 'startTick': 'start_tick' + } + } + + COLLECTION_NAME = 'sections' + COLUMNS = ['id', 'path_id', 'datacenter_id', 'start_tick'] + COLUMNS_PRIMARY_KEY = ['id'] + + def google_id_has_at_least(self, google_id, authorization_level): + """Return True if the user has at least the given auth level over this Section.""" + + # Get the Path + + try: + path = Path.from_primary_key((self.path_id, )) + except exceptions.RowNotFoundError: + return False + + # Check the Path's Authorization + + return path.google_id_has_at_least(google_id, authorization_level) diff --git a/web-server/opendc/models_old/simulation.py b/web-server/opendc/models_old/simulation.py new file mode 100644 index 00000000..9c1820a3 --- /dev/null +++ b/web-server/opendc/models_old/simulation.py @@ -0,0 +1,39 @@ +from opendc.models_old.authorization import Authorization +from opendc.models_old.model import Model +from opendc.models_old.user import User +from opendc.util import exceptions + + +class Simulation(Model): + JSON_TO_PYTHON_DICT = { + 'Simulation': { + 'id': 'id', + 'name': 'name', + 'datetimeCreated': 'datetime_created', + 'datetimeLastEdited': 'datetime_last_edited' + } + } + + COLLECTION_NAME = 'simulations' + COLUMNS = ['id', 'datetime_created', 'datetime_last_edited', 'name'] + COLUMNS_PRIMARY_KEY = ['id'] + + def google_id_has_at_least(self, google_id, authorization_level): + """Return True if the user has at least the given auth level over this Simulation.""" + + # Get the User id + + try: + user_id = User.from_google_id(google_id).read().id + except exceptions.RowNotFoundError: + return False + + # Get the Simulation id + + simulation_id = self.id + + # Check the Authorization + + authorization = Authorization.from_primary_key((user_id, simulation_id)) + + return authorization.has_at_least(authorization_level) diff --git a/web-server/opendc/models_old/storage.py b/web-server/opendc/models_old/storage.py new file mode 100644 index 00000000..9f0f9215 --- /dev/null +++ b/web-server/opendc/models_old/storage.py @@ -0,0 +1,34 @@ +from opendc.models_old.model import Model + + +class Storage(Model): + JSON_TO_PYTHON_DICT = { + 'Storage': { + 'id': 'id', + 'manufacturer': 'manufacturer', + 'family': 'family', + 'generation': 'generation', + 'model': 'model', + 'speedMbPerS': 'speed_mb_per_s', + 'sizeMb': 'size_mb', + 'energyConsumptionW': 'energy_consumption_w', + 'failureModelId': 'failure_model_id' + } + } + + COLLECTION_NAME = 'storages' + + COLUMNS = [ + 'id', 'manufacturer', 'family', 'generation', 'model', 'speed_mb_per_s', 'size_mb', 'energy_consumption_w', + 'failure_model_id' + ] + + COLUMNS_PRIMARY_KEY = ['id'] + + def google_id_has_at_least(self, google_id, authorization_level): + """Return True if the User has at least the given auth level over this Storage.""" + + if authorization_level in ['EDIT', 'OWN']: + return False + + return True diff --git a/web-server/opendc/models_old/task.py b/web-server/opendc/models_old/task.py new file mode 100644 index 00000000..e6f99014 --- /dev/null +++ b/web-server/opendc/models_old/task.py @@ -0,0 +1,22 @@ +from opendc.models_old.model import Model + + +class Task(Model): + JSON_TO_PYTHON_DICT = { + 'Task': { + 'id': 'id', + 'startTick': 'start_tick', + 'totalFlopCount': 'total_flop_count', + 'coreCount': 'core_count', + 'jobId': 'job_id', + } + } + + COLLECTION_NAME = 'tasks' + COLUMNS = ['id', 'start_tick', 'total_flop_count', 'job_id', 'core_count'] + COLUMNS_PRIMARY_KEY = ['id'] + + def google_id_has_at_least(self, google_id, authorization_level): + """Return True if the user has at least the given auth level over this Task.""" + + return authorization_level not in ['EDIT', 'OWN'] diff --git a/web-server/opendc/models_old/task_duration.py b/web-server/opendc/models_old/task_duration.py new file mode 100644 index 00000000..5058e8de --- /dev/null +++ b/web-server/opendc/models_old/task_duration.py @@ -0,0 +1,39 @@ +from opendc.models_old.model import Model +from opendc.util import database + + +class TaskDuration(Model): + JSON_TO_PYTHON_DICT = {'TaskDuration': {'taskId': 'task_id', 'duration': 'duration'}} + + @classmethod + def _from_database_row(cls, row): + """Instantiate a RoomState from a database row.""" + + return cls(task_id=row[0], duration=row[1]) + + @classmethod + def from_experiment_id(cls, experiment_id): + """Query RoomStates by their Experiment id.""" + + room_states = [] + + statement = ''' + SELECT task_id, MAX(tick) - MIN(tick) as duration FROM task_states + WHERE experiment_id = %s + GROUP BY task_id + ''' + + results = database.fetch_all(statement, (experiment_id, )) + + for row in results: + room_states.append(cls._from_database_row(row)) + + return room_states + + def google_id_has_at_least(self, google_id, authorization_level): + """Return True if the User has at least the given auth level over this TaskDuration.""" + + if authorization_level in ['EDIT', 'OWN']: + return False + + return True diff --git a/web-server/opendc/models_old/task_state.py b/web-server/opendc/models_old/task_state.py new file mode 100644 index 00000000..cc3fdd89 --- /dev/null +++ b/web-server/opendc/models_old/task_state.py @@ -0,0 +1,42 @@ +from opendc.models_old.model import Model +from opendc.util import database + + +class TaskState(Model): + JSON_TO_PYTHON_DICT = { + 'TaskState': { + 'id': 'id', + 'taskId': 'task_id', + 'experimentId': 'experiment_id', + 'tick': 'tick', + 'flopsLeft': 'flops_left', + 'coresUsed': 'cores_used' + } + } + + COLLECTION_NAME = 'task_states' + + COLUMNS = ['id', 'task_id', 'experiment_id', 'tick', 'flops_left', 'cores_used'] + COLUMNS_PRIMARY_KEY = ['id'] + + @classmethod + def from_experiment_id_and_tick(cls, experiment_id, tick): + """Query Task States by their Experiment id and tick.""" + + task_states = [] + + statement = 'SELECT * FROM task_states WHERE experiment_id = %s AND tick = %s' + results = database.fetch_all(statement, (experiment_id, tick)) + + for row in results: + task_states.append(cls(id=row[0], task_id=row[1], experiment_id=row[2], tick=row[3], flops_left=row[4])) + + return task_states + + def google_id_has_at_least(self, google_id, authorization_level): + """Return True if the User has at least the given auth level over this TaskState.""" + + if authorization_level in ['EDIT', 'OWN']: + return False + + return True diff --git a/web-server/opendc/models_old/tile.py b/web-server/opendc/models_old/tile.py new file mode 100644 index 00000000..e46b29a6 --- /dev/null +++ b/web-server/opendc/models_old/tile.py @@ -0,0 +1,47 @@ +from opendc.models_old.model import Model +from opendc.models_old.object import Object +from opendc.models_old.room import Room +from opendc.util import exceptions + + +class Tile(Model): + JSON_TO_PYTHON_DICT = { + 'tile': { + 'id': 'id', + 'roomId': 'room_id', + 'objectId': 'object_id', + 'objectType': 'object_type', + 'positionX': 'position_x', + 'positionY': 'position_y', + 'topologyId': 'topology_id' + } + } + + PATH = '/v1/rooms/{roomId}/tiles' + + COLLECTION_NAME = 'tiles' + COLUMNS = ['id', 'position_x', 'position_y', 'room_id', 'object_id', 'topology_id'] + COLUMNS_PRIMARY_KEY = ['id'] + + def google_id_has_at_least(self, google_id, authorization_level): + """Return True if the user has at least the given auth level over this Tile.""" + + # Get the Room + + try: + room = Room.from_primary_key((self.room_id, )) + except exceptions.RowNotFoundError: + return False + + # Check the Room's Authorization + + return room.google_id_has_at_least(google_id, authorization_level) + + def read(self): + """Read this Tile by also getting its associated object type.""" + + super(Tile, self).read() + + if self.object_id is not None: + obj = Object.from_primary_key((self.object_id, )) + self.object_type = obj.type diff --git a/web-server/opendc/models_old/trace.py b/web-server/opendc/models_old/trace.py new file mode 100644 index 00000000..58abe058 --- /dev/null +++ b/web-server/opendc/models_old/trace.py @@ -0,0 +1,14 @@ +from opendc.models_old.model import Model + + +class Trace(Model): + JSON_TO_PYTHON_DICT = {'Trace': {'id': 'id', 'name': 'name'}} + + COLLECTION_NAME = 'traces' + COLUMNS = ['id', 'name'] + COLUMNS_PRIMARY_KEY = ['id'] + + def google_id_has_at_least(self, google_id, authorization_level): + """Return True if the user has at least the given auth level over this Trace.""" + + return authorization_level not in ['EDIT', 'OWN'] diff --git a/web-server/opendc/models_old/user.py b/web-server/opendc/models_old/user.py new file mode 100644 index 00000000..657d5019 --- /dev/null +++ b/web-server/opendc/models_old/user.py @@ -0,0 +1,47 @@ +from opendc.models_old.model import Model + + +class User(Model): + JSON_TO_PYTHON_DICT = { + 'User': { + 'id': 'id', + 'googleId': 'google_id', + 'email': 'email', + 'givenName': 'given_name', + 'familyName': 'family_name' + } + } + + COLLECTION_NAME = 'users' + COLUMNS = ['id', 'google_id', 'email', 'given_name', 'family_name'] + COLUMNS_PRIMARY_KEY = ['id'] + + @classmethod + def from_google_id(cls, google_id): + """Initialize a User by fetching them by their google id.""" + + user = cls._from_database('SELECT * FROM users WHERE google_id = %s', (google_id, )) + + if user is not None: + return user + + return User() + + @classmethod + def from_email(cls, email): + """Initialize a User by fetching them by their email.""" + + user = cls._from_database('SELECT * FROM users WHERE email = %s', (email, )) + + if user is not None: + return user + + return User() + + def google_id_has_at_least(self, google_id, authorization_level): + """Return True if the User has at least the given auth level over this User.""" + + if authorization_level in ['EDIT', 'OWN']: + return google_id == self.google_id + + return True diff --git a/web-server/opendc/util/__init__.py b/web-server/opendc/util/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/web-server/opendc/util/__init__.py diff --git a/web-server/opendc/util/database.py b/web-server/opendc/util/database.py new file mode 100644 index 00000000..50bc93a8 --- /dev/null +++ b/web-server/opendc/util/database.py @@ -0,0 +1,93 @@ +import json +import urllib.parse +from datetime import datetime + +from bson.json_util import dumps +from pymongo import MongoClient + +DATETIME_STRING_FORMAT = '%Y-%m-%dT%H:%M:%S' +CONNECTION_POOL = None + + +class Database: + def __init__(self): + self.opendc_db = None + + def init_database(self, user, password, database, host): + user = urllib.parse.quote_plus(user) # TODO: replace this with environment variable + password = urllib.parse.quote_plus(password) # TODO: same as above + database = urllib.parse.quote_plus(database) + host = urllib.parse.quote_plus(host) + + client = MongoClient('mongodb://%s:%s@%s/default_db?authSource=%s' % (user, password, host, database)) + self.opendc_db = client.opendc + + def fetch_one(self, query, collection): + """Uses existing mongo connection to return a single (the first) document in a collection matching the given + query as a JSON object. + + The query needs to be in json format, i.e.: `{'name': prefab_name}`. + """ + bson = getattr(self.opendc_db, collection).find_one(query) + + return self.convert_bson_to_json(bson) + + def fetch_all(self, query, collection): + """Uses existing mongo connection to return all documents matching a given query, as a list of JSON objects. + + The query needs to be in json format, i.e.: `{'name': prefab_name}`. + """ + results = [] + cursor = getattr(self.opendc_db, collection).find(query) + for doc in cursor: + results.append(self.convert_bson_to_json(doc)) + return results + + def insert(self, obj, collection): + """Updates an existing object.""" + bson = getattr(self.opendc_db, collection).insert(obj) + + return self.convert_bson_to_json(bson) + + def update(self, _id, obj, collection): + """Updates an existing object.""" + bson = getattr(self.opendc_db, collection).update({'_id': _id}, obj) + + return self.convert_bson_to_json(bson) + + def delete_one(self, query, collection): + """Deletes one object matching the given query. + + The query needs to be in json format, i.e.: `{'name': prefab_name}`. + """ + bson = getattr(self.opendc_db, collection).delete_one(query) + + return self.convert_bson_to_json(bson) + + def delete_all(self, query, collection): + """Deletes all objects matching the given query. + + The query needs to be in json format, i.e.: `{'name': prefab_name}`. + """ + bson = getattr(self.opendc_db, collection).delete_many(query) + + return self.convert_bson_to_json(bson) + + @staticmethod + def convert_bson_to_json(bson): + """Converts a BSON representation to JSON and returns the JSON representation.""" + json_string = dumps(bson) + return json.loads(json_string) + + @staticmethod + def datetime_to_string(datetime_to_convert): + """Return a database-compatible string representation of the given datetime object.""" + return datetime_to_convert.strftime(DATETIME_STRING_FORMAT) + + @staticmethod + def string_to_datetime(string_to_convert): + """Return a datetime corresponding to the given string representation.""" + return datetime.strptime(string_to_convert, DATETIME_STRING_FORMAT) + + +DB = Database() diff --git a/web-server/opendc/util/exceptions.py b/web-server/opendc/util/exceptions.py new file mode 100644 index 00000000..2563c419 --- /dev/null +++ b/web-server/opendc/util/exceptions.py @@ -0,0 +1,65 @@ +class RequestInitializationError(Exception): + """Raised when a Request cannot successfully be initialized""" + + +class UnimplementedEndpointError(RequestInitializationError): + """Raised when a Request path does not point to a module.""" + + +class MissingRequestParameterError(RequestInitializationError): + """Raised when a Request does not contain one or more required parameters.""" + + +class UnsupportedMethodError(RequestInitializationError): + """Raised when a Request does not use a supported REST method. + + The method must be in all-caps, supported by REST, and implemented by the module. + """ + + +class AuthorizationTokenError(RequestInitializationError): + """Raised when an authorization token is not correctly verified.""" + + +class ForeignKeyError(Exception): + """Raised when a foreign key constraint is not met.""" + + +class RowNotFoundError(Exception): + """Raised when a database row is not found.""" + def __init__(self, table_name): + super(RowNotFoundError, self).__init__('Row in `{}` table not found.'.format(table_name)) + + self.table_name = table_name + + +class ParameterError(Exception): + """Raised when a parameter is either missing or incorrectly typed.""" + + +class IncorrectParameterError(ParameterError): + """Raised when a parameter is of the wrong type.""" + def __init__(self, parameter_name, parameter_location): + super(IncorrectParameterError, + self).__init__('Incorrectly typed `{}` {} parameter.'.format(parameter_name, parameter_location)) + + self.parameter_name = parameter_name + self.parameter_location = parameter_location + + +class MissingParameterError(ParameterError): + """Raised when a parameter is missing.""" + def __init__(self, parameter_name, parameter_location): + super(MissingParameterError, + self).__init__('Missing required `{}` {} parameter.'.format(parameter_name, parameter_location)) + + self.parameter_name = parameter_name + self.parameter_location = parameter_location + + +class ClientError(Exception): + """Raised when a 4xx response is to be returned.""" + + def __init__(self, response): + super(ClientError, self).__init__(str(response)) + self.response = response diff --git a/web-server/opendc/util/parameter_checker.py b/web-server/opendc/util/parameter_checker.py new file mode 100644 index 00000000..f55e780e --- /dev/null +++ b/web-server/opendc/util/parameter_checker.py @@ -0,0 +1,78 @@ +from opendc.util import database, exceptions + + +def _missing_parameter(params_required, params_actual, parent=''): + """Recursively search for the first missing parameter.""" + + for param_name in params_required: + + if param_name not in params_actual: + return '{}.{}'.format(parent, param_name) + + param_required = params_required.get(param_name) + param_actual = params_actual.get(param_name) + + if isinstance(param_required, dict): + + param_missing = _missing_parameter(param_required, param_actual, param_name) + + if param_missing is not None: + return '{}.{}'.format(parent, param_missing) + + return None + + +def _incorrect_parameter(params_required, params_actual, parent=''): + """Recursively make sure each parameter is of the correct type.""" + + for param_name in params_required: + + param_required = params_required.get(param_name) + param_actual = params_actual.get(param_name) + + if isinstance(param_required, dict): + + param_incorrect = _incorrect_parameter(param_required, param_actual, param_name) + + if param_incorrect is not None: + return '{}.{}'.format(parent, param_incorrect) + + else: + + if param_required == 'datetime': + try: + database.string_to_datetime(param_actual) + except: + return '{}.{}'.format(parent, param_name) + + if param_required == 'int' and not isinstance(param_actual, int): + return '{}.{}'.format(parent, param_name) + + if param_required == 'string' and not isinstance(param_actual, str) and not isinstance(param_actual, int): + return '{}.{}'.format(parent, param_name) + + if param_required.startswith('list') and not isinstance(param_actual, list): + return '{}.{}'.format(parent, param_name) + + +def _format_parameter(parameter): + """Format the output of a parameter check.""" + + parts = parameter.split('.') + inner = ['["{}"]'.format(x) for x in parts[2:]] + return parts[1] + ''.join(inner) + + +def check(request, **kwargs): + """Return True if all required parameters are there.""" + + for location, params_required in kwargs.items(): + params_actual = getattr(request, 'params_{}'.format(location)) + + missing_parameter = _missing_parameter(params_required, params_actual) + if missing_parameter is not None: + raise exceptions.MissingParameterError(_format_parameter(missing_parameter), location) + + incorrect_parameter = _incorrect_parameter(params_required, params_actual) + if incorrect_parameter is not None: + raise exceptions.IncorrectParameterError(_format_parameter(incorrect_parameter), location) diff --git a/web-server/opendc/util/path_parser.py b/web-server/opendc/util/path_parser.py new file mode 100644 index 00000000..a8bbdeba --- /dev/null +++ b/web-server/opendc/util/path_parser.py @@ -0,0 +1,39 @@ +import json +import os + + +def parse(version, endpoint_path): + """Map an HTTP endpoint path to an API path""" + + # Get possible paths + with open(os.path.join(os.path.dirname(__file__), '..', 'api', '{}', 'paths.json').format(version)) as paths_file: + paths = json.load(paths_file) + + # Find API path that matches endpoint_path + endpoint_path_parts = endpoint_path.strip('/').split('/') + paths_parts = [x.strip('/').split('/') for x in paths if len(x.strip('/').split('/')) == len(endpoint_path_parts)] + path = None + + for path_parts in paths_parts: + found = True + for (endpoint_part, part) in zip(endpoint_path_parts, path_parts): + if not part.startswith('{') and endpoint_part != part: + found = False + break + if found: + path = path_parts + + if path is None: + return None + + # Extract path parameters + parameters = {} + + for (name, value) in zip(path, endpoint_path_parts): + if name.startswith('{'): + try: + parameters[name.strip('{}')] = int(value) + except: + parameters[name.strip('{}')] = value + + return '{}/{}'.format(version, '/'.join(path)), parameters diff --git a/web-server/opendc/util/rest.py b/web-server/opendc/util/rest.py new file mode 100644 index 00000000..dc5478de --- /dev/null +++ b/web-server/opendc/util/rest.py @@ -0,0 +1,141 @@ +import importlib +import json +import os +import sys + +from oauth2client import client, crypt + +from opendc.util import exceptions, parameter_checker +from opendc.util.exceptions import ClientError + + +class Request(object): + """WebSocket message to REST request mapping.""" + def __init__(self, message=None): + """"Initialize a Request from a socket message.""" + + # Get the Request parameters from the message + + if message is None: + return + + try: + self.message = message + + self.id = message['id'] + + self.path = message['path'] + self.method = message['method'] + + self.params_body = message['parameters']['body'] + self.params_path = message['parameters']['path'] + self.params_query = message['parameters']['query'] + + self.token = message['token'] + + except KeyError as exception: + raise exceptions.MissingRequestParameterError(exception) + + # Parse the path and import the appropriate module + + try: + self.path = message['path'].strip('/') + + module_base = 'opendc.api.{}.endpoint' + module_path = self.path.replace('{', '').replace('}', '').replace('/', '.') + + self.module = importlib.import_module(module_base.format(module_path)) + except ImportError as e: + print(e) + raise exceptions.UnimplementedEndpointError('Unimplemented endpoint: {}.'.format(self.path)) + + # Check the method + + if self.method not in ['POST', 'GET', 'PUT', 'PATCH', 'DELETE']: + raise exceptions.UnsupportedMethodError('Non-rest method: {}'.format(self.method)) + + if not hasattr(self.module, self.method): + raise exceptions.UnsupportedMethodError('Unimplemented method at endpoint {}: {}'.format( + self.path, self.method)) + + # Verify the user + + if "OPENDC_FLASK_TESTING" in os.environ: + self.google_id = 'test' + return + + try: + self.google_id = self._verify_token(self.token) + except crypt.AppIdentityError as e: + raise exceptions.AuthorizationTokenError(e) + + def check_required_parameters(self, **kwargs): + """Raise an error if a parameter is missing or of the wrong type.""" + + try: + parameter_checker.check(self, **kwargs) + except exceptions.ParameterError as e: + raise ClientError(Response(400, str(e))) + + def process(self): + """Process the Request and return a Response.""" + + method = getattr(self.module, self.method) + + try: + response = method(self) + except ClientError as e: + e.response.id = self.id + return e.response + + response.id = self.id + + return response + + def to_JSON(self): + """Return a JSON representation of this Request""" + + self.message['id'] = 0 + self.message['token'] = None + + return json.dumps(self.message) + + @staticmethod + def _verify_token(token): + """Return the ID of the signed-in user. + + Or throw an Exception if the token is invalid. + """ + + try: + id_info = client.verify_id_token(token, os.environ['OPENDC_OAUTH_CLIENT_ID']) + except Exception as e: + print(e) + raise crypt.AppIdentityError('Exception caught trying to verify ID token: {}'.format(e)) + + if id_info['aud'] != os.environ['OPENDC_OAUTH_CLIENT_ID']: + raise crypt.AppIdentityError('Unrecognized client.') + + if id_info['iss'] not in ['accounts.google.com', 'https://accounts.google.com']: + raise crypt.AppIdentityError('Wrong issuer.') + + return id_info['sub'] + + +class Response(object): + """Response to websocket mapping""" + def __init__(self, status_code, status_description, content=None): + """Initialize a new Response.""" + + self.status = {'code': status_code, 'description': status_description} + self.content = content + + def to_JSON(self): + """"Return a JSON representation of this Response""" + + data = {'id': self.id, 'status': self.status} + + if self.content is not None: + data['content'] = self.content + + return json.dumps(data) diff --git a/web-server/pytest.ini b/web-server/pytest.ini new file mode 100644 index 00000000..775a8ff4 --- /dev/null +++ b/web-server/pytest.ini @@ -0,0 +1,5 @@ +[pytest] +env = + OPENDC_FLASK_TESTING=True + OPENDC_FLASK_SECRET=Secret + OPENDC_SERVER_BASE_URL=localhost diff --git a/web-server/requirements.txt b/web-server/requirements.txt new file mode 100644 index 00000000..b95d3145 --- /dev/null +++ b/web-server/requirements.txt @@ -0,0 +1,14 @@ +flask==1.0.2 +flask-socketio==3.0.2 +oauth2client==4.1.3 +eventlet==0.24.1 +flask-compress==1.4.0 +flask-cors==3.0.8 +pyasn1-modules==0.2.2 +six==1.15.0 +pymongo==3.10.1 +yapf==0.30.0 +pytest==5.4.3 +pytest-mock==3.1.1 +pytest-env==0.6.2 +pylint==2.5.3 diff --git a/web-server/static/index.html b/web-server/static/index.html new file mode 100644 index 00000000..ac78cbfb --- /dev/null +++ b/web-server/static/index.html @@ -0,0 +1,22 @@ +<script src="https://apis.google.com/js/platform.js" async defer></script> +<meta name="google-signin-client_id" content="561588943542-fq7065hk47qdf3lfsc50ebll4spi6u76.apps.googleusercontent.com"> +<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/socket.io/1.3.6/socket.io.min.js"></script> +<script> + function onSignIn(googleUser) { + document.getElementById('token').innerText = googleUser.getAuthResponse().id_token; + } +</script> +<script> + function signOut() { + var auth2 = gapi.auth2.getAuthInstance(); + auth2.signOut().then(function () { + console.log('User signed out.'); + }); + } +</script> + +<div class="g-signin2" data-onsuccess="onSignIn"></div> +<a href="#" onclick="signOut();">Sign out</a> + +<p>Your auth token:</p> +<p id="token" style="word-wrap:break-word;">Loading...</p>
\ No newline at end of file diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 00000000..328d1164 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,12239 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/code-frame@7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.8.3.tgz#33e25903d7481181534e12ec0a25f16b6fcf419e" + integrity sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g== + dependencies: + "@babel/highlight" "^7.8.3" + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.1", "@babel/code-frame@^7.8.3": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.1.tgz#d5481c5095daa1c57e16e54c6f9198443afb49ff" + integrity sha512-IGhtTmpjGbYzcEDOw7DcQtbQSXcG9ftmAXtWTu9V936vDye4xjjekktFAtgZsWpzTj/X01jocB46mTywm/4SZw== + dependencies: + "@babel/highlight" "^7.10.1" + +"@babel/compat-data@^7.10.1", "@babel/compat-data@^7.9.0": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.10.1.tgz#b1085ffe72cd17bf2c0ee790fc09f9626011b2db" + integrity sha512-CHvCj7So7iCkGKPRFUfryXIkU2gSBw7VSZFYLsqVhrS47269VK2Hfi9S/YcublPMW8k1u2bQBlbDruoQEm4fgw== + dependencies: + browserslist "^4.12.0" + invariant "^2.2.4" + semver "^5.5.0" + +"@babel/core@7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.9.0.tgz#ac977b538b77e132ff706f3b8a4dbad09c03c56e" + integrity sha512-kWc7L0fw1xwvI0zi8OKVBuxRVefwGOrKSQMvrQ3dW+bIIavBY3/NpXmpjMy7bQnLgwgzWQZ8TlM57YHpHNHz4w== + dependencies: + "@babel/code-frame" "^7.8.3" + "@babel/generator" "^7.9.0" + "@babel/helper-module-transforms" "^7.9.0" + "@babel/helpers" "^7.9.0" + "@babel/parser" "^7.9.0" + "@babel/template" "^7.8.6" + "@babel/traverse" "^7.9.0" + "@babel/types" "^7.9.0" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.1" + json5 "^2.1.2" + lodash "^4.17.13" + resolve "^1.3.2" + semver "^5.4.1" + source-map "^0.5.0" + +"@babel/core@^7.1.0", "@babel/core@^7.4.5": + version "7.10.2" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.10.2.tgz#bd6786046668a925ac2bd2fd95b579b92a23b36a" + integrity sha512-KQmV9yguEjQsXqyOUGKjS4+3K8/DlOCE2pZcq4augdQmtTy5iv5EHtmMSJ7V4c1BIPjuwtZYqYLCq9Ga+hGBRQ== + dependencies: + "@babel/code-frame" "^7.10.1" + "@babel/generator" "^7.10.2" + "@babel/helper-module-transforms" "^7.10.1" + "@babel/helpers" "^7.10.1" + "@babel/parser" "^7.10.2" + "@babel/template" "^7.10.1" + "@babel/traverse" "^7.10.1" + "@babel/types" "^7.10.2" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.1" + json5 "^2.1.2" + lodash "^4.17.13" + resolve "^1.3.2" + semver "^5.4.1" + source-map "^0.5.0" + +"@babel/generator@^7.10.1", "@babel/generator@^7.10.2", "@babel/generator@^7.4.0", "@babel/generator@^7.9.0": + version "7.10.2" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.10.2.tgz#0fa5b5b2389db8bfdfcc3492b551ee20f5dd69a9" + integrity sha512-AxfBNHNu99DTMvlUPlt1h2+Hn7knPpH5ayJ8OqDWSeLld+Fi2AYBTC/IejWDM9Edcii4UzZRCsbUt0WlSDsDsA== + dependencies: + "@babel/types" "^7.10.2" + jsesc "^2.5.1" + lodash "^4.17.13" + source-map "^0.5.0" + +"@babel/helper-annotate-as-pure@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.1.tgz#f6d08acc6f70bbd59b436262553fb2e259a1a268" + integrity sha512-ewp3rvJEwLaHgyWGe4wQssC2vjks3E80WiUe2BpMb0KhreTjMROCbxXcEovTrbeGVdQct5VjQfrv9EgC+xMzCw== + dependencies: + "@babel/types" "^7.10.1" + +"@babel/helper-builder-binary-assignment-operator-visitor@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.1.tgz#0ec7d9be8174934532661f87783eb18d72290059" + integrity sha512-cQpVq48EkYxUU0xozpGCLla3wlkdRRqLWu1ksFMXA9CM5KQmyyRpSEsYXbao7JUkOw/tAaYKCaYyZq6HOFYtyw== + dependencies: + "@babel/helper-explode-assignable-expression" "^7.10.1" + "@babel/types" "^7.10.1" + +"@babel/helper-builder-react-jsx-experimental@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-react-jsx-experimental/-/helper-builder-react-jsx-experimental-7.10.1.tgz#9a7d58ad184d3ac3bafb1a452cec2bad7e4a0bc8" + integrity sha512-irQJ8kpQUV3JasXPSFQ+LCCtJSc5ceZrPFVj6TElR6XCHssi3jV8ch3odIrNtjJFRZZVbrOEfJMI79TPU/h1pQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.10.1" + "@babel/helper-module-imports" "^7.10.1" + "@babel/types" "^7.10.1" + +"@babel/helper-builder-react-jsx@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-react-jsx/-/helper-builder-react-jsx-7.10.1.tgz#a327f0cf983af5554701b1215de54a019f09b532" + integrity sha512-KXzzpyWhXgzjXIlJU1ZjIXzUPdej1suE6vzqgImZ/cpAsR/CC8gUcX4EWRmDfWz/cs6HOCPMBIJ3nKoXt3BFuw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.10.1" + "@babel/types" "^7.10.1" + +"@babel/helper-compilation-targets@^7.10.2", "@babel/helper-compilation-targets@^7.8.7": + version "7.10.2" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.10.2.tgz#a17d9723b6e2c750299d2a14d4637c76936d8285" + integrity sha512-hYgOhF4To2UTB4LTaZepN/4Pl9LD4gfbJx8A34mqoluT8TLbof1mhUlYuNWTEebONa8+UlCC4X0TEXu7AOUyGA== + dependencies: + "@babel/compat-data" "^7.10.1" + browserslist "^4.12.0" + invariant "^2.2.4" + levenary "^1.1.1" + semver "^5.5.0" + +"@babel/helper-create-class-features-plugin@^7.10.1", "@babel/helper-create-class-features-plugin@^7.8.3": + version "7.10.2" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.10.2.tgz#7474295770f217dbcf288bf7572eb213db46ee67" + integrity sha512-5C/QhkGFh1vqcziq1vAL6SI9ymzUp8BCYjFpvYVhWP4DlATIb3u5q3iUd35mvlyGs8fO7hckkW7i0tmH+5+bvQ== + dependencies: + "@babel/helper-function-name" "^7.10.1" + "@babel/helper-member-expression-to-functions" "^7.10.1" + "@babel/helper-optimise-call-expression" "^7.10.1" + "@babel/helper-plugin-utils" "^7.10.1" + "@babel/helper-replace-supers" "^7.10.1" + "@babel/helper-split-export-declaration" "^7.10.1" + +"@babel/helper-create-regexp-features-plugin@^7.10.1", "@babel/helper-create-regexp-features-plugin@^7.8.3": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.10.1.tgz#1b8feeab1594cbcfbf3ab5a3bbcabac0468efdbd" + integrity sha512-Rx4rHS0pVuJn5pJOqaqcZR4XSgeF9G/pO/79t+4r7380tXFJdzImFnxMU19f83wjSrmKHq6myrM10pFHTGzkUA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.10.1" + "@babel/helper-regex" "^7.10.1" + regexpu-core "^4.7.0" + +"@babel/helper-define-map@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.10.1.tgz#5e69ee8308648470dd7900d159c044c10285221d" + integrity sha512-+5odWpX+OnvkD0Zmq7panrMuAGQBu6aPUgvMzuMGo4R+jUOvealEj2hiqI6WhxgKrTpFoFj0+VdsuA8KDxHBDg== + dependencies: + "@babel/helper-function-name" "^7.10.1" + "@babel/types" "^7.10.1" + lodash "^4.17.13" + +"@babel/helper-explode-assignable-expression@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.10.1.tgz#e9d76305ee1162ca467357ae25df94f179af2b7e" + integrity sha512-vcUJ3cDjLjvkKzt6rHrl767FeE7pMEYfPanq5L16GRtrXIoznc0HykNW2aEYkcnP76P0isoqJ34dDMFZwzEpJg== + dependencies: + "@babel/traverse" "^7.10.1" + "@babel/types" "^7.10.1" + +"@babel/helper-function-name@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.10.1.tgz#92bd63829bfc9215aca9d9defa85f56b539454f4" + integrity sha512-fcpumwhs3YyZ/ttd5Rz0xn0TpIwVkN7X0V38B9TWNfVF42KEkhkAAuPCQ3oXmtTRtiPJrmZ0TrfS0GKF0eMaRQ== + dependencies: + "@babel/helper-get-function-arity" "^7.10.1" + "@babel/template" "^7.10.1" + "@babel/types" "^7.10.1" + +"@babel/helper-get-function-arity@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.1.tgz#7303390a81ba7cb59613895a192b93850e373f7d" + integrity sha512-F5qdXkYGOQUb0hpRaPoetF9AnsXknKjWMZ+wmsIRsp5ge5sFh4c3h1eH2pRTTuy9KKAA2+TTYomGXAtEL2fQEw== + dependencies: + "@babel/types" "^7.10.1" + +"@babel/helper-hoist-variables@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.1.tgz#7e77c82e5dcae1ebf123174c385aaadbf787d077" + integrity sha512-vLm5srkU8rI6X3+aQ1rQJyfjvCBLXP8cAGeuw04zeAM2ItKb1e7pmVmLyHb4sDaAYnLL13RHOZPLEtcGZ5xvjg== + dependencies: + "@babel/types" "^7.10.1" + +"@babel/helper-member-expression-to-functions@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.10.1.tgz#432967fd7e12a4afef66c4687d4ca22bc0456f15" + integrity sha512-u7XLXeM2n50gb6PWJ9hoO5oO7JFPaZtrh35t8RqKLT1jFKj9IWeD1zrcrYp1q1qiZTdEarfDWfTIP8nGsu0h5g== + dependencies: + "@babel/types" "^7.10.1" + +"@babel/helper-module-imports@^7.10.1", "@babel/helper-module-imports@^7.8.3": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.10.1.tgz#dd331bd45bccc566ce77004e9d05fe17add13876" + integrity sha512-SFxgwYmZ3HZPyZwJRiVNLRHWuW2OgE5k2nrVs6D9Iv4PPnXVffuEHy83Sfx/l4SqF+5kyJXjAyUmrG7tNm+qVg== + dependencies: + "@babel/types" "^7.10.1" + +"@babel/helper-module-transforms@^7.10.1", "@babel/helper-module-transforms@^7.9.0": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.10.1.tgz#24e2f08ee6832c60b157bb0936c86bef7210c622" + integrity sha512-RLHRCAzyJe7Q7sF4oy2cB+kRnU4wDZY/H2xJFGof+M+SJEGhZsb+GFj5j1AD8NiSaVBJ+Pf0/WObiXu/zxWpFg== + dependencies: + "@babel/helper-module-imports" "^7.10.1" + "@babel/helper-replace-supers" "^7.10.1" + "@babel/helper-simple-access" "^7.10.1" + "@babel/helper-split-export-declaration" "^7.10.1" + "@babel/template" "^7.10.1" + "@babel/types" "^7.10.1" + lodash "^4.17.13" + +"@babel/helper-optimise-call-expression@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.1.tgz#b4a1f2561870ce1247ceddb02a3860fa96d72543" + integrity sha512-a0DjNS1prnBsoKx83dP2falChcs7p3i8VMzdrSbfLhuQra/2ENC4sbri34dz/rWmDADsmF1q5GbfaXydh0Jbjg== + dependencies: + "@babel/types" "^7.10.1" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.1", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz#ec5a5cf0eec925b66c60580328b122c01230a127" + integrity sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA== + +"@babel/helper-regex@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.10.1.tgz#021cf1a7ba99822f993222a001cc3fec83255b96" + integrity sha512-7isHr19RsIJWWLLFn21ubFt223PjQyg1HY7CZEMRr820HttHPpVvrsIN3bUOo44DEfFV4kBXO7Abbn9KTUZV7g== + dependencies: + lodash "^4.17.13" + +"@babel/helper-remap-async-to-generator@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.10.1.tgz#bad6aaa4ff39ce8d4b82ccaae0bfe0f7dbb5f432" + integrity sha512-RfX1P8HqsfgmJ6CwaXGKMAqbYdlleqglvVtht0HGPMSsy2V6MqLlOJVF/0Qyb/m2ZCi2z3q3+s6Pv7R/dQuZ6A== + dependencies: + "@babel/helper-annotate-as-pure" "^7.10.1" + "@babel/helper-wrap-function" "^7.10.1" + "@babel/template" "^7.10.1" + "@babel/traverse" "^7.10.1" + "@babel/types" "^7.10.1" + +"@babel/helper-replace-supers@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.10.1.tgz#ec6859d20c5d8087f6a2dc4e014db7228975f13d" + integrity sha512-SOwJzEfpuQwInzzQJGjGaiG578UYmyi2Xw668klPWV5n07B73S0a9btjLk/52Mlcxa+5AdIYqws1KyXRfMoB7A== + dependencies: + "@babel/helper-member-expression-to-functions" "^7.10.1" + "@babel/helper-optimise-call-expression" "^7.10.1" + "@babel/traverse" "^7.10.1" + "@babel/types" "^7.10.1" + +"@babel/helper-simple-access@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.10.1.tgz#08fb7e22ace9eb8326f7e3920a1c2052f13d851e" + integrity sha512-VSWpWzRzn9VtgMJBIWTZ+GP107kZdQ4YplJlCmIrjoLVSi/0upixezHCDG8kpPVTBJpKfxTH01wDhh+jS2zKbw== + dependencies: + "@babel/template" "^7.10.1" + "@babel/types" "^7.10.1" + +"@babel/helper-split-export-declaration@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.10.1.tgz#c6f4be1cbc15e3a868e4c64a17d5d31d754da35f" + integrity sha512-UQ1LVBPrYdbchNhLwj6fetj46BcFwfS4NllJo/1aJsT+1dLTEnXJL0qHqtY7gPzF8S2fXBJamf1biAXV3X077g== + dependencies: + "@babel/types" "^7.10.1" + +"@babel/helper-validator-identifier@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.1.tgz#5770b0c1a826c4f53f5ede5e153163e0318e94b5" + integrity sha512-5vW/JXLALhczRCWP0PnFDMCJAchlBvM7f4uk/jXritBnIa6E1KmqmtrS3yn1LAnxFBypQ3eneLuXjsnfQsgILw== + +"@babel/helper-wrap-function@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.10.1.tgz#956d1310d6696257a7afd47e4c42dfda5dfcedc9" + integrity sha512-C0MzRGteVDn+H32/ZgbAv5r56f2o1fZSA/rj/TYo8JEJNHg+9BdSmKBUND0shxWRztWhjlT2cvHYuynpPsVJwQ== + dependencies: + "@babel/helper-function-name" "^7.10.1" + "@babel/template" "^7.10.1" + "@babel/traverse" "^7.10.1" + "@babel/types" "^7.10.1" + +"@babel/helpers@^7.10.1", "@babel/helpers@^7.9.0": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.10.1.tgz#a6827b7cb975c9d9cef5fd61d919f60d8844a973" + integrity sha512-muQNHF+IdU6wGgkaJyhhEmI54MOZBKsFfsXFhboz1ybwJ1Kl7IHlbm2a++4jwrmY5UYsgitt5lfqo1wMFcHmyw== + dependencies: + "@babel/template" "^7.10.1" + "@babel/traverse" "^7.10.1" + "@babel/types" "^7.10.1" + +"@babel/highlight@^7.10.1", "@babel/highlight@^7.8.3": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.10.1.tgz#841d098ba613ba1a427a2b383d79e35552c38ae0" + integrity sha512-8rMof+gVP8mxYZApLF/JgNDAkdKa+aJt3ZYxF8z6+j/hpeXL7iMsKCPHa2jNMHu/qqBwzQF4OHNoYi8dMA/rYg== + dependencies: + "@babel/helper-validator-identifier" "^7.10.1" + chalk "^2.0.0" + js-tokens "^4.0.0" + +"@babel/parser@^7.1.0", "@babel/parser@^7.10.1", "@babel/parser@^7.10.2", "@babel/parser@^7.4.3", "@babel/parser@^7.7.0", "@babel/parser@^7.9.0": + version "7.10.2" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.10.2.tgz#871807f10442b92ff97e4783b9b54f6a0ca812d0" + integrity sha512-PApSXlNMJyB4JiGVhCOlzKIif+TKFTvu0aQAhnTvfP/z3vVSN6ZypH5bfUNwFXXjRQtUEBNFd2PtmCmG2Py3qQ== + +"@babel/plugin-proposal-async-generator-functions@^7.10.1", "@babel/plugin-proposal-async-generator-functions@^7.8.3": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.10.1.tgz#6911af5ba2e615c4ff3c497fe2f47b35bf6d7e55" + integrity sha512-vzZE12ZTdB336POZjmpblWfNNRpMSua45EYnRigE2XsZxcXcIyly2ixnTJasJE4Zq3U7t2d8rRF7XRUuzHxbOw== + dependencies: + "@babel/helper-plugin-utils" "^7.10.1" + "@babel/helper-remap-async-to-generator" "^7.10.1" + "@babel/plugin-syntax-async-generators" "^7.8.0" + +"@babel/plugin-proposal-class-properties@7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.8.3.tgz#5e06654af5cd04b608915aada9b2a6788004464e" + integrity sha512-EqFhbo7IosdgPgZggHaNObkmO1kNUe3slaKu54d5OWvy+p9QIKOzK1GAEpAIsZtWVtPXUHSMcT4smvDrCfY4AA== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-proposal-class-properties@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.10.1.tgz#046bc7f6550bb08d9bd1d4f060f5f5a4f1087e01" + integrity sha512-sqdGWgoXlnOdgMXU+9MbhzwFRgxVLeiGBqTrnuS7LC2IBU31wSsESbTUreT2O418obpfPdGUR2GbEufZF1bpqw== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.10.1" + "@babel/helper-plugin-utils" "^7.10.1" + +"@babel/plugin-proposal-decorators@7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.8.3.tgz#2156860ab65c5abf068c3f67042184041066543e" + integrity sha512-e3RvdvS4qPJVTe288DlXjwKflpfy1hr0j5dz5WpIYYeP7vQZg2WfAEIp8k5/Lwis/m5REXEteIz6rrcDtXXG7w== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-syntax-decorators" "^7.8.3" + +"@babel/plugin-proposal-dynamic-import@^7.10.1", "@babel/plugin-proposal-dynamic-import@^7.8.3": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.10.1.tgz#e36979dc1dc3b73f6d6816fc4951da2363488ef0" + integrity sha512-Cpc2yUVHTEGPlmiQzXj026kqwjEQAD9I4ZC16uzdbgWgitg/UHKHLffKNCQZ5+y8jpIZPJcKcwsr2HwPh+w3XA== + dependencies: + "@babel/helper-plugin-utils" "^7.10.1" + "@babel/plugin-syntax-dynamic-import" "^7.8.0" + +"@babel/plugin-proposal-json-strings@^7.10.1", "@babel/plugin-proposal-json-strings@^7.8.3": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.10.1.tgz#b1e691ee24c651b5a5e32213222b2379734aff09" + integrity sha512-m8r5BmV+ZLpWPtMY2mOKN7wre6HIO4gfIiV+eOmsnZABNenrt/kzYBwrh+KOfgumSWpnlGs5F70J8afYMSJMBg== + dependencies: + "@babel/helper-plugin-utils" "^7.10.1" + "@babel/plugin-syntax-json-strings" "^7.8.0" + +"@babel/plugin-proposal-nullish-coalescing-operator@7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.8.3.tgz#e4572253fdeed65cddeecfdab3f928afeb2fd5d2" + integrity sha512-TS9MlfzXpXKt6YYomudb/KU7nQI6/xnapG6in1uZxoxDghuSMZsPb6D2fyUwNYSAp4l1iR7QtFOjkqcRYcUsfw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" + +"@babel/plugin-proposal-nullish-coalescing-operator@^7.10.1", "@babel/plugin-proposal-nullish-coalescing-operator@^7.8.3": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.10.1.tgz#02dca21673842ff2fe763ac253777f235e9bbf78" + integrity sha512-56cI/uHYgL2C8HVuHOuvVowihhX0sxb3nnfVRzUeVHTWmRHTZrKuAh/OBIMggGU/S1g/1D2CRCXqP+3u7vX7iA== + dependencies: + "@babel/helper-plugin-utils" "^7.10.1" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" + +"@babel/plugin-proposal-numeric-separator@7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.8.3.tgz#5d6769409699ec9b3b68684cd8116cedff93bad8" + integrity sha512-jWioO1s6R/R+wEHizfaScNsAx+xKgwTLNXSh7tTC4Usj3ItsPEhYkEpU4h+lpnBwq7NBVOJXfO6cRFYcX69JUQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.8.3" + +"@babel/plugin-proposal-numeric-separator@^7.10.1", "@babel/plugin-proposal-numeric-separator@^7.8.3": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.10.1.tgz#a9a38bc34f78bdfd981e791c27c6fdcec478c123" + integrity sha512-jjfym4N9HtCiNfyyLAVD8WqPYeHUrw4ihxuAynWj6zzp2gf9Ey2f7ImhFm6ikB3CLf5Z/zmcJDri6B4+9j9RsA== + dependencies: + "@babel/helper-plugin-utils" "^7.10.1" + "@babel/plugin-syntax-numeric-separator" "^7.10.1" + +"@babel/plugin-proposal-object-rest-spread@^7.10.1", "@babel/plugin-proposal-object-rest-spread@^7.9.0": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.10.1.tgz#cba44908ac9f142650b4a65b8aa06bf3478d5fb6" + integrity sha512-Z+Qri55KiQkHh7Fc4BW6o+QBuTagbOp9txE+4U1i79u9oWlf2npkiDx+Rf3iK3lbcHBuNy9UOkwuR5wOMH3LIQ== + dependencies: + "@babel/helper-plugin-utils" "^7.10.1" + "@babel/plugin-syntax-object-rest-spread" "^7.8.0" + "@babel/plugin-transform-parameters" "^7.10.1" + +"@babel/plugin-proposal-optional-catch-binding@^7.10.1", "@babel/plugin-proposal-optional-catch-binding@^7.8.3": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.10.1.tgz#c9f86d99305f9fa531b568ff5ab8c964b8b223d2" + integrity sha512-VqExgeE62YBqI3ogkGoOJp1R6u12DFZjqwJhqtKc2o5m1YTUuUWnos7bZQFBhwkxIFpWYJ7uB75U7VAPPiKETA== + dependencies: + "@babel/helper-plugin-utils" "^7.10.1" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" + +"@babel/plugin-proposal-optional-chaining@7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.9.0.tgz#31db16b154c39d6b8a645292472b98394c292a58" + integrity sha512-NDn5tu3tcv4W30jNhmc2hyD5c56G6cXx4TesJubhxrJeCvuuMpttxr0OnNCqbZGhFjLrg+NIhxxC+BK5F6yS3w== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.0" + +"@babel/plugin-proposal-optional-chaining@^7.10.1", "@babel/plugin-proposal-optional-chaining@^7.9.0": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.10.1.tgz#15f5d6d22708629451a91be28f8facc55b0e818c" + integrity sha512-dqQj475q8+/avvok72CF3AOSV/SGEcH29zT5hhohqqvvZ2+boQoOr7iGldBG5YXTO2qgCgc2B3WvVLUdbeMlGA== + dependencies: + "@babel/helper-plugin-utils" "^7.10.1" + "@babel/plugin-syntax-optional-chaining" "^7.8.0" + +"@babel/plugin-proposal-private-methods@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.10.1.tgz#ed85e8058ab0fe309c3f448e5e1b73ca89cdb598" + integrity sha512-RZecFFJjDiQ2z6maFprLgrdnm0OzoC23Mx89xf1CcEsxmHuzuXOdniEuI+S3v7vjQG4F5sa6YtUp+19sZuSxHg== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.10.1" + "@babel/helper-plugin-utils" "^7.10.1" + +"@babel/plugin-proposal-unicode-property-regex@^7.10.1", "@babel/plugin-proposal-unicode-property-regex@^7.4.4", "@babel/plugin-proposal-unicode-property-regex@^7.8.3": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.10.1.tgz#dc04feb25e2dd70c12b05d680190e138fa2c0c6f" + integrity sha512-JjfngYRvwmPwmnbRZyNiPFI8zxCZb8euzbCG/LxyKdeTb59tVciKo9GK9bi6JYKInk1H11Dq9j/zRqIH4KigfQ== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.10.1" + "@babel/helper-plugin-utils" "^7.10.1" + +"@babel/plugin-syntax-async-generators@^7.8.0": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" + integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-class-properties@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.10.1.tgz#d5bc0645913df5b17ad7eda0fa2308330bde34c5" + integrity sha512-Gf2Yx/iRs1JREDtVZ56OrjjgFHCaldpTnuy9BHla10qyVT3YkIIGEtoDWhyop0ksu1GvNjHIoYRBqm3zoR1jyQ== + dependencies: + "@babel/helper-plugin-utils" "^7.10.1" + +"@babel/plugin-syntax-decorators@^7.8.3": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.10.1.tgz#16b869c4beafc9a442565147bda7ce0967bd4f13" + integrity sha512-a9OAbQhKOwSle1Vr0NJu/ISg1sPfdEkfRKWpgPuzhnWWzForou2gIeUIIwjAMHRekhhpJ7eulZlYs0H14Cbi+g== + dependencies: + "@babel/helper-plugin-utils" "^7.10.1" + +"@babel/plugin-syntax-dynamic-import@^7.8.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3" + integrity sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-flow@^7.8.3": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.10.1.tgz#cd4bbca62fb402babacb174f64f8734310d742f0" + integrity sha512-b3pWVncLBYoPP60UOTc7NMlbtsHQ6ITim78KQejNHK6WJ2mzV5kCcg4mIWpasAfJEgwVTibwo2e+FU7UEIKQUg== + dependencies: + "@babel/helper-plugin-utils" "^7.10.1" + +"@babel/plugin-syntax-json-strings@^7.8.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" + integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-jsx@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.10.1.tgz#0ae371134a42b91d5418feb3c8c8d43e1565d2da" + integrity sha512-+OxyOArpVFXQeXKLO9o+r2I4dIoVoy6+Uu0vKELrlweDM3QJADZj+Z+5ERansZqIZBcLj42vHnDI8Rz9BnRIuQ== + dependencies: + "@babel/helper-plugin-utils" "^7.10.1" + +"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" + integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-numeric-separator@^7.10.1", "@babel/plugin-syntax-numeric-separator@^7.8.0", "@babel/plugin-syntax-numeric-separator@^7.8.3": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.1.tgz#25761ee7410bc8cf97327ba741ee94e4a61b7d99" + integrity sha512-uTd0OsHrpe3tH5gRPTxG8Voh99/WCU78vIm5NMRYPAqC8lR4vajt6KkCAknCHrx24vkPdd/05yfdGSB4EIY2mg== + dependencies: + "@babel/helper-plugin-utils" "^7.10.1" + +"@babel/plugin-syntax-object-rest-spread@^7.0.0", "@babel/plugin-syntax-object-rest-spread@^7.8.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" + integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-catch-binding@^7.8.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" + integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-chaining@^7.8.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" + integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-top-level-await@^7.10.1", "@babel/plugin-syntax-top-level-await@^7.8.3": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.10.1.tgz#8b8733f8c57397b3eaa47ddba8841586dcaef362" + integrity sha512-hgA5RYkmZm8FTFT3yu2N9Bx7yVVOKYT6yEdXXo6j2JTm0wNxgqaGeQVaSHRjhfnQbX91DtjFB6McRFSlcJH3xQ== + dependencies: + "@babel/helper-plugin-utils" "^7.10.1" + +"@babel/plugin-syntax-typescript@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.10.1.tgz#5e82bc27bb4202b93b949b029e699db536733810" + integrity sha512-X/d8glkrAtra7CaQGMiGs/OGa6XgUzqPcBXCIGFCpCqnfGlT0Wfbzo/B89xHhnInTaItPK8LALblVXcUOEh95Q== + dependencies: + "@babel/helper-plugin-utils" "^7.10.1" + +"@babel/plugin-transform-arrow-functions@^7.10.1", "@babel/plugin-transform-arrow-functions@^7.8.3": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.10.1.tgz#cb5ee3a36f0863c06ead0b409b4cc43a889b295b" + integrity sha512-6AZHgFJKP3DJX0eCNJj01RpytUa3SOGawIxweHkNX2L6PYikOZmoh5B0d7hIHaIgveMjX990IAa/xK7jRTN8OA== + dependencies: + "@babel/helper-plugin-utils" "^7.10.1" + +"@babel/plugin-transform-async-to-generator@^7.10.1", "@babel/plugin-transform-async-to-generator@^7.8.3": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.10.1.tgz#e5153eb1a3e028f79194ed8a7a4bf55f862b2062" + integrity sha512-XCgYjJ8TY2slj6SReBUyamJn3k2JLUIiiR5b6t1mNCMSvv7yx+jJpaewakikp0uWFQSF7ChPPoe3dHmXLpISkg== + dependencies: + "@babel/helper-module-imports" "^7.10.1" + "@babel/helper-plugin-utils" "^7.10.1" + "@babel/helper-remap-async-to-generator" "^7.10.1" + +"@babel/plugin-transform-block-scoped-functions@^7.10.1", "@babel/plugin-transform-block-scoped-functions@^7.8.3": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.10.1.tgz#146856e756d54b20fff14b819456b3e01820b85d" + integrity sha512-B7K15Xp8lv0sOJrdVAoukKlxP9N59HS48V1J3U/JGj+Ad+MHq+am6xJVs85AgXrQn4LV8vaYFOB+pr/yIuzW8Q== + dependencies: + "@babel/helper-plugin-utils" "^7.10.1" + +"@babel/plugin-transform-block-scoping@^7.10.1", "@babel/plugin-transform-block-scoping@^7.8.3": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.10.1.tgz#47092d89ca345811451cd0dc5d91605982705d5e" + integrity sha512-8bpWG6TtF5akdhIm/uWTyjHqENpy13Fx8chg7pFH875aNLwX8JxIxqm08gmAT+Whe6AOmaTeLPe7dpLbXt+xUw== + dependencies: + "@babel/helper-plugin-utils" "^7.10.1" + lodash "^4.17.13" + +"@babel/plugin-transform-classes@^7.10.1", "@babel/plugin-transform-classes@^7.9.0": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.10.1.tgz#6e11dd6c4dfae70f540480a4702477ed766d733f" + integrity sha512-P9V0YIh+ln/B3RStPoXpEQ/CoAxQIhRSUn7aXqQ+FZJ2u8+oCtjIXR3+X0vsSD8zv+mb56K7wZW1XiDTDGiDRQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.10.1" + "@babel/helper-define-map" "^7.10.1" + "@babel/helper-function-name" "^7.10.1" + "@babel/helper-optimise-call-expression" "^7.10.1" + "@babel/helper-plugin-utils" "^7.10.1" + "@babel/helper-replace-supers" "^7.10.1" + "@babel/helper-split-export-declaration" "^7.10.1" + globals "^11.1.0" + +"@babel/plugin-transform-computed-properties@^7.10.1", "@babel/plugin-transform-computed-properties@^7.8.3": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.10.1.tgz#59aa399064429d64dce5cf76ef9b90b7245ebd07" + integrity sha512-mqSrGjp3IefMsXIenBfGcPXxJxweQe2hEIwMQvjtiDQ9b1IBvDUjkAtV/HMXX47/vXf14qDNedXsIiNd1FmkaQ== + dependencies: + "@babel/helper-plugin-utils" "^7.10.1" + +"@babel/plugin-transform-destructuring@^7.10.1", "@babel/plugin-transform-destructuring@^7.8.3": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.10.1.tgz#abd58e51337815ca3a22a336b85f62b998e71907" + integrity sha512-V/nUc4yGWG71OhaTH705pU8ZSdM6c1KmmLP8ys59oOYbT7RpMYAR3MsVOt6OHL0WzG7BlTU076va9fjJyYzJMA== + dependencies: + "@babel/helper-plugin-utils" "^7.10.1" + +"@babel/plugin-transform-dotall-regex@^7.10.1", "@babel/plugin-transform-dotall-regex@^7.4.4", "@babel/plugin-transform-dotall-regex@^7.8.3": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.10.1.tgz#920b9fec2d78bb57ebb64a644d5c2ba67cc104ee" + integrity sha512-19VIMsD1dp02RvduFUmfzj8uknaO3uiHHF0s3E1OHnVsNj8oge8EQ5RzHRbJjGSetRnkEuBYO7TG1M5kKjGLOA== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.10.1" + "@babel/helper-plugin-utils" "^7.10.1" + +"@babel/plugin-transform-duplicate-keys@^7.10.1", "@babel/plugin-transform-duplicate-keys@^7.8.3": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.10.1.tgz#c900a793beb096bc9d4d0a9d0cde19518ffc83b9" + integrity sha512-wIEpkX4QvX8Mo9W6XF3EdGttrIPZWozHfEaDTU0WJD/TDnXMvdDh30mzUl/9qWhnf7naicYartcEfUghTCSNpA== + dependencies: + "@babel/helper-plugin-utils" "^7.10.1" + +"@babel/plugin-transform-exponentiation-operator@^7.10.1", "@babel/plugin-transform-exponentiation-operator@^7.8.3": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.10.1.tgz#279c3116756a60dd6e6f5e488ba7957db9c59eb3" + integrity sha512-lr/przdAbpEA2BUzRvjXdEDLrArGRRPwbaF9rvayuHRvdQ7lUTTkZnhZrJ4LE2jvgMRFF4f0YuPQ20vhiPYxtA== + dependencies: + "@babel/helper-builder-binary-assignment-operator-visitor" "^7.10.1" + "@babel/helper-plugin-utils" "^7.10.1" + +"@babel/plugin-transform-flow-strip-types@7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.9.0.tgz#8a3538aa40434e000b8f44a3c5c9ac7229bd2392" + integrity sha512-7Qfg0lKQhEHs93FChxVLAvhBshOPQDtJUTVHr/ZwQNRccCm4O9D79r9tVSoV8iNwjP1YgfD+e/fgHcPkN1qEQg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-syntax-flow" "^7.8.3" + +"@babel/plugin-transform-for-of@^7.10.1", "@babel/plugin-transform-for-of@^7.9.0": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.10.1.tgz#ff01119784eb0ee32258e8646157ba2501fcfda5" + integrity sha512-US8KCuxfQcn0LwSCMWMma8M2R5mAjJGsmoCBVwlMygvmDUMkTCykc84IqN1M7t+agSfOmLYTInLCHJM+RUoz+w== + dependencies: + "@babel/helper-plugin-utils" "^7.10.1" + +"@babel/plugin-transform-function-name@^7.10.1", "@babel/plugin-transform-function-name@^7.8.3": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.10.1.tgz#4ed46fd6e1d8fde2a2ec7b03c66d853d2c92427d" + integrity sha512-//bsKsKFBJfGd65qSNNh1exBy5Y9gD9ZN+DvrJ8f7HXr4avE5POW6zB7Rj6VnqHV33+0vXWUwJT0wSHubiAQkw== + dependencies: + "@babel/helper-function-name" "^7.10.1" + "@babel/helper-plugin-utils" "^7.10.1" + +"@babel/plugin-transform-literals@^7.10.1", "@babel/plugin-transform-literals@^7.8.3": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.10.1.tgz#5794f8da82846b22e4e6631ea1658bce708eb46a" + integrity sha512-qi0+5qgevz1NHLZroObRm5A+8JJtibb7vdcPQF1KQE12+Y/xxl8coJ+TpPW9iRq+Mhw/NKLjm+5SHtAHCC7lAw== + dependencies: + "@babel/helper-plugin-utils" "^7.10.1" + +"@babel/plugin-transform-member-expression-literals@^7.10.1", "@babel/plugin-transform-member-expression-literals@^7.8.3": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.10.1.tgz#90347cba31bca6f394b3f7bd95d2bbfd9fce2f39" + integrity sha512-UmaWhDokOFT2GcgU6MkHC11i0NQcL63iqeufXWfRy6pUOGYeCGEKhvfFO6Vz70UfYJYHwveg62GS83Rvpxn+NA== + dependencies: + "@babel/helper-plugin-utils" "^7.10.1" + +"@babel/plugin-transform-modules-amd@^7.10.1", "@babel/plugin-transform-modules-amd@^7.9.0": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.10.1.tgz#65950e8e05797ebd2fe532b96e19fc5482a1d52a" + integrity sha512-31+hnWSFRI4/ACFr1qkboBbrTxoBIzj7qA69qlq8HY8p7+YCzkCT6/TvQ1a4B0z27VeWtAeJd6pr5G04dc1iHw== + dependencies: + "@babel/helper-module-transforms" "^7.10.1" + "@babel/helper-plugin-utils" "^7.10.1" + babel-plugin-dynamic-import-node "^2.3.3" + +"@babel/plugin-transform-modules-commonjs@^7.10.1", "@babel/plugin-transform-modules-commonjs@^7.9.0": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.10.1.tgz#d5ff4b4413ed97ffded99961056e1fb980fb9301" + integrity sha512-AQG4fc3KOah0vdITwt7Gi6hD9BtQP/8bhem7OjbaMoRNCH5Djx42O2vYMfau7QnAzQCa+RJnhJBmFFMGpQEzrg== + dependencies: + "@babel/helper-module-transforms" "^7.10.1" + "@babel/helper-plugin-utils" "^7.10.1" + "@babel/helper-simple-access" "^7.10.1" + babel-plugin-dynamic-import-node "^2.3.3" + +"@babel/plugin-transform-modules-systemjs@^7.10.1", "@babel/plugin-transform-modules-systemjs@^7.9.0": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.10.1.tgz#9962e4b0ac6aaf2e20431ada3d8ec72082cbffb6" + integrity sha512-ewNKcj1TQZDL3YnO85qh9zo1YF1CHgmSTlRQgHqe63oTrMI85cthKtZjAiZSsSNjPQ5NCaYo5QkbYqEw1ZBgZA== + dependencies: + "@babel/helper-hoist-variables" "^7.10.1" + "@babel/helper-module-transforms" "^7.10.1" + "@babel/helper-plugin-utils" "^7.10.1" + babel-plugin-dynamic-import-node "^2.3.3" + +"@babel/plugin-transform-modules-umd@^7.10.1", "@babel/plugin-transform-modules-umd@^7.9.0": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.10.1.tgz#ea080911ffc6eb21840a5197a39ede4ee67b1595" + integrity sha512-EIuiRNMd6GB6ulcYlETnYYfgv4AxqrswghmBRQbWLHZxN4s7mupxzglnHqk9ZiUpDI4eRWewedJJNj67PWOXKA== + dependencies: + "@babel/helper-module-transforms" "^7.10.1" + "@babel/helper-plugin-utils" "^7.10.1" + +"@babel/plugin-transform-named-capturing-groups-regex@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.8.3.tgz#a2a72bffa202ac0e2d0506afd0939c5ecbc48c6c" + integrity sha512-f+tF/8UVPU86TrCb06JoPWIdDpTNSGGcAtaD9mLP0aYGA0OS0j7j7DHJR0GTFrUZPUU6loZhbsVZgTh0N+Qdnw== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.8.3" + +"@babel/plugin-transform-new-target@^7.10.1", "@babel/plugin-transform-new-target@^7.8.3": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.10.1.tgz#6ee41a5e648da7632e22b6fb54012e87f612f324" + integrity sha512-MBlzPc1nJvbmO9rPr1fQwXOM2iGut+JC92ku6PbiJMMK7SnQc1rytgpopveE3Evn47gzvGYeCdgfCDbZo0ecUw== + dependencies: + "@babel/helper-plugin-utils" "^7.10.1" + +"@babel/plugin-transform-object-super@^7.10.1", "@babel/plugin-transform-object-super@^7.8.3": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.10.1.tgz#2e3016b0adbf262983bf0d5121d676a5ed9c4fde" + integrity sha512-WnnStUDN5GL+wGQrJylrnnVlFhFmeArINIR9gjhSeYyvroGhBrSAXYg/RHsnfzmsa+onJrTJrEClPzgNmmQ4Gw== + dependencies: + "@babel/helper-plugin-utils" "^7.10.1" + "@babel/helper-replace-supers" "^7.10.1" + +"@babel/plugin-transform-parameters@^7.10.1", "@babel/plugin-transform-parameters@^7.8.7": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.10.1.tgz#b25938a3c5fae0354144a720b07b32766f683ddd" + integrity sha512-tJ1T0n6g4dXMsL45YsSzzSDZCxiHXAQp/qHrucOq5gEHncTA3xDxnd5+sZcoQp+N1ZbieAaB8r/VUCG0gqseOg== + dependencies: + "@babel/helper-get-function-arity" "^7.10.1" + "@babel/helper-plugin-utils" "^7.10.1" + +"@babel/plugin-transform-property-literals@^7.10.1", "@babel/plugin-transform-property-literals@^7.8.3": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.10.1.tgz#cffc7315219230ed81dc53e4625bf86815b6050d" + integrity sha512-Kr6+mgag8auNrgEpbfIWzdXYOvqDHZOF0+Bx2xh4H2EDNwcbRb9lY6nkZg8oSjsX+DH9Ebxm9hOqtKW+gRDeNA== + dependencies: + "@babel/helper-plugin-utils" "^7.10.1" + +"@babel/plugin-transform-react-constant-elements@^7.0.0": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.10.1.tgz#c7f117a54657cba3f9d32012e050fc89982df9e1" + integrity sha512-V4os6bkWt/jbrzfyVcZn2ZpuHZkvj3vyBU0U/dtS8SZuMS7Rfx5oknTrtfyXJ2/QZk8gX7Yls5Z921ItNpE30Q== + dependencies: + "@babel/helper-plugin-utils" "^7.10.1" + +"@babel/plugin-transform-react-display-name@7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.8.3.tgz#70ded987c91609f78353dd76d2fb2a0bb991e8e5" + integrity sha512-3Jy/PCw8Fe6uBKtEgz3M82ljt+lTg+xJaM4og+eyu83qLT87ZUSckn0wy7r31jflURWLO83TW6Ylf7lyXj3m5A== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-react-display-name@^7.10.1", "@babel/plugin-transform-react-display-name@^7.8.3": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.10.1.tgz#e6a33f6d48dfb213dda5e007d0c7ff82b6a3d8ef" + integrity sha512-rBjKcVwjk26H3VX8pavMxGf33LNlbocMHdSeldIEswtQ/hrjyTG8fKKILW1cSkODyRovckN/uZlGb2+sAV9JUQ== + dependencies: + "@babel/helper-plugin-utils" "^7.10.1" + +"@babel/plugin-transform-react-jsx-development@^7.10.1", "@babel/plugin-transform-react-jsx-development@^7.9.0": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.10.1.tgz#1ac6300d8b28ef381ee48e6fec430cc38047b7f3" + integrity sha512-XwDy/FFoCfw9wGFtdn5Z+dHh6HXKHkC6DwKNWpN74VWinUagZfDcEJc3Y8Dn5B3WMVnAllX8Kviaw7MtC5Epwg== + dependencies: + "@babel/helper-builder-react-jsx-experimental" "^7.10.1" + "@babel/helper-plugin-utils" "^7.10.1" + "@babel/plugin-syntax-jsx" "^7.10.1" + +"@babel/plugin-transform-react-jsx-self@^7.10.1", "@babel/plugin-transform-react-jsx-self@^7.9.0": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.10.1.tgz#22143e14388d72eb88649606bb9e46f421bc3821" + integrity sha512-4p+RBw9d1qV4S749J42ZooeQaBomFPrSxa9JONLHJ1TxCBo3TzJ79vtmG2S2erUT8PDDrPdw4ZbXGr2/1+dILA== + dependencies: + "@babel/helper-plugin-utils" "^7.10.1" + "@babel/plugin-syntax-jsx" "^7.10.1" + +"@babel/plugin-transform-react-jsx-source@^7.10.1", "@babel/plugin-transform-react-jsx-source@^7.9.0": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.10.1.tgz#30db3d4ee3cdebbb26a82a9703673714777a4273" + integrity sha512-neAbaKkoiL+LXYbGDvh6PjPG+YeA67OsZlE78u50xbWh2L1/C81uHiNP5d1fw+uqUIoiNdCC8ZB+G4Zh3hShJA== + dependencies: + "@babel/helper-plugin-utils" "^7.10.1" + "@babel/plugin-syntax-jsx" "^7.10.1" + +"@babel/plugin-transform-react-jsx@^7.10.1", "@babel/plugin-transform-react-jsx@^7.9.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.10.1.tgz#91f544248ba131486decb5d9806da6a6e19a2896" + integrity sha512-MBVworWiSRBap3Vs39eHt+6pJuLUAaK4oxGc8g+wY+vuSJvLiEQjW1LSTqKb8OUPtDvHCkdPhk7d6sjC19xyFw== + dependencies: + "@babel/helper-builder-react-jsx" "^7.10.1" + "@babel/helper-builder-react-jsx-experimental" "^7.10.1" + "@babel/helper-plugin-utils" "^7.10.1" + "@babel/plugin-syntax-jsx" "^7.10.1" + +"@babel/plugin-transform-react-pure-annotations@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.10.1.tgz#f5e7c755d3e7614d4c926e144f501648a5277b70" + integrity sha512-mfhoiai083AkeewsBHUpaS/FM1dmUENHBMpS/tugSJ7VXqXO5dCN1Gkint2YvM1Cdv1uhmAKt1ZOuAjceKmlLA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.10.1" + "@babel/helper-plugin-utils" "^7.10.1" + +"@babel/plugin-transform-regenerator@^7.10.1", "@babel/plugin-transform-regenerator@^7.8.7": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.10.1.tgz#10e175cbe7bdb63cc9b39f9b3f823c5c7c5c5490" + integrity sha512-B3+Y2prScgJ2Bh/2l9LJxKbb8C8kRfsG4AdPT+n7ixBHIxJaIG8bi8tgjxUMege1+WqSJ+7gu1YeoMVO3gPWzw== + dependencies: + regenerator-transform "^0.14.2" + +"@babel/plugin-transform-reserved-words@^7.10.1", "@babel/plugin-transform-reserved-words@^7.8.3": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.10.1.tgz#0fc1027312b4d1c3276a57890c8ae3bcc0b64a86" + integrity sha512-qN1OMoE2nuqSPmpTqEM7OvJ1FkMEV+BjVeZZm9V9mq/x1JLKQ4pcv8riZJMNN3u2AUGl0ouOMjRr2siecvHqUQ== + dependencies: + "@babel/helper-plugin-utils" "^7.10.1" + +"@babel/plugin-transform-runtime@7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.9.0.tgz#45468c0ae74cc13204e1d3b1f4ce6ee83258af0b" + integrity sha512-pUu9VSf3kI1OqbWINQ7MaugnitRss1z533436waNXp+0N3ur3zfut37sXiQMxkuCF4VUjwZucen/quskCh7NHw== + dependencies: + "@babel/helper-module-imports" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" + resolve "^1.8.1" + semver "^5.5.1" + +"@babel/plugin-transform-shorthand-properties@^7.10.1", "@babel/plugin-transform-shorthand-properties@^7.8.3": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.10.1.tgz#e8b54f238a1ccbae482c4dce946180ae7b3143f3" + integrity sha512-AR0E/lZMfLstScFwztApGeyTHJ5u3JUKMjneqRItWeEqDdHWZwAOKycvQNCasCK/3r5YXsuNG25funcJDu7Y2g== + dependencies: + "@babel/helper-plugin-utils" "^7.10.1" + +"@babel/plugin-transform-spread@^7.10.1", "@babel/plugin-transform-spread@^7.8.3": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.10.1.tgz#0c6d618a0c4461a274418460a28c9ccf5239a7c8" + integrity sha512-8wTPym6edIrClW8FI2IoaePB91ETOtg36dOkj3bYcNe7aDMN2FXEoUa+WrmPc4xa1u2PQK46fUX2aCb+zo9rfw== + dependencies: + "@babel/helper-plugin-utils" "^7.10.1" + +"@babel/plugin-transform-sticky-regex@^7.10.1", "@babel/plugin-transform-sticky-regex@^7.8.3": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.10.1.tgz#90fc89b7526228bed9842cff3588270a7a393b00" + integrity sha512-j17ojftKjrL7ufX8ajKvwRilwqTok4q+BjkknmQw9VNHnItTyMP5anPFzxFJdCQs7clLcWpCV3ma+6qZWLnGMA== + dependencies: + "@babel/helper-plugin-utils" "^7.10.1" + "@babel/helper-regex" "^7.10.1" + +"@babel/plugin-transform-template-literals@^7.10.1", "@babel/plugin-transform-template-literals@^7.8.3": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.10.1.tgz#914c7b7f4752c570ea00553b4284dad8070e8628" + integrity sha512-t7B/3MQf5M1T9hPCRG28DNGZUuxAuDqLYS03rJrIk2prj/UV7Z6FOneijhQhnv/Xa039vidXeVbvjK2SK5f7Gg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.10.1" + "@babel/helper-plugin-utils" "^7.10.1" + +"@babel/plugin-transform-typeof-symbol@^7.10.1", "@babel/plugin-transform-typeof-symbol@^7.8.4": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.10.1.tgz#60c0239b69965d166b80a84de7315c1bc7e0bb0e" + integrity sha512-qX8KZcmbvA23zDi+lk9s6hC1FM7jgLHYIjuLgULgc8QtYnmB3tAVIYkNoKRQ75qWBeyzcoMoK8ZQmogGtC/w0g== + dependencies: + "@babel/helper-plugin-utils" "^7.10.1" + +"@babel/plugin-transform-typescript@^7.9.0": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.10.1.tgz#2c54daea231f602468686d9faa76f182a94507a6" + integrity sha512-v+QWKlmCnsaimLeqq9vyCsVRMViZG1k2SZTlcZvB+TqyH570Zsij8nvVUZzOASCRiQFUxkLrn9Wg/kH0zgy5OQ== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.10.1" + "@babel/helper-plugin-utils" "^7.10.1" + "@babel/plugin-syntax-typescript" "^7.10.1" + +"@babel/plugin-transform-unicode-escapes@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.10.1.tgz#add0f8483dab60570d9e03cecef6c023aa8c9940" + integrity sha512-zZ0Poh/yy1d4jeDWpx/mNwbKJVwUYJX73q+gyh4bwtG0/iUlzdEu0sLMda8yuDFS6LBQlT/ST1SJAR6zYwXWgw== + dependencies: + "@babel/helper-plugin-utils" "^7.10.1" + +"@babel/plugin-transform-unicode-regex@^7.10.1", "@babel/plugin-transform-unicode-regex@^7.8.3": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.10.1.tgz#6b58f2aea7b68df37ac5025d9c88752443a6b43f" + integrity sha512-Y/2a2W299k0VIUdbqYm9X2qS6fE0CUBhhiPpimK6byy7OJ/kORLlIX+J6UrjgNu5awvs62k+6RSslxhcvVw2Tw== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.10.1" + "@babel/helper-plugin-utils" "^7.10.1" + +"@babel/preset-env@7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.9.0.tgz#a5fc42480e950ae8f5d9f8f2bbc03f52722df3a8" + integrity sha512-712DeRXT6dyKAM/FMbQTV/FvRCms2hPCx+3weRjZ8iQVQWZejWWk1wwG6ViWMyqb/ouBbGOl5b6aCk0+j1NmsQ== + dependencies: + "@babel/compat-data" "^7.9.0" + "@babel/helper-compilation-targets" "^7.8.7" + "@babel/helper-module-imports" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-proposal-async-generator-functions" "^7.8.3" + "@babel/plugin-proposal-dynamic-import" "^7.8.3" + "@babel/plugin-proposal-json-strings" "^7.8.3" + "@babel/plugin-proposal-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-proposal-numeric-separator" "^7.8.3" + "@babel/plugin-proposal-object-rest-spread" "^7.9.0" + "@babel/plugin-proposal-optional-catch-binding" "^7.8.3" + "@babel/plugin-proposal-optional-chaining" "^7.9.0" + "@babel/plugin-proposal-unicode-property-regex" "^7.8.3" + "@babel/plugin-syntax-async-generators" "^7.8.0" + "@babel/plugin-syntax-dynamic-import" "^7.8.0" + "@babel/plugin-syntax-json-strings" "^7.8.0" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" + "@babel/plugin-syntax-numeric-separator" "^7.8.0" + "@babel/plugin-syntax-object-rest-spread" "^7.8.0" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" + "@babel/plugin-syntax-optional-chaining" "^7.8.0" + "@babel/plugin-syntax-top-level-await" "^7.8.3" + "@babel/plugin-transform-arrow-functions" "^7.8.3" + "@babel/plugin-transform-async-to-generator" "^7.8.3" + "@babel/plugin-transform-block-scoped-functions" "^7.8.3" + "@babel/plugin-transform-block-scoping" "^7.8.3" + "@babel/plugin-transform-classes" "^7.9.0" + "@babel/plugin-transform-computed-properties" "^7.8.3" + "@babel/plugin-transform-destructuring" "^7.8.3" + "@babel/plugin-transform-dotall-regex" "^7.8.3" + "@babel/plugin-transform-duplicate-keys" "^7.8.3" + "@babel/plugin-transform-exponentiation-operator" "^7.8.3" + "@babel/plugin-transform-for-of" "^7.9.0" + "@babel/plugin-transform-function-name" "^7.8.3" + "@babel/plugin-transform-literals" "^7.8.3" + "@babel/plugin-transform-member-expression-literals" "^7.8.3" + "@babel/plugin-transform-modules-amd" "^7.9.0" + "@babel/plugin-transform-modules-commonjs" "^7.9.0" + "@babel/plugin-transform-modules-systemjs" "^7.9.0" + "@babel/plugin-transform-modules-umd" "^7.9.0" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.8.3" + "@babel/plugin-transform-new-target" "^7.8.3" + "@babel/plugin-transform-object-super" "^7.8.3" + "@babel/plugin-transform-parameters" "^7.8.7" + "@babel/plugin-transform-property-literals" "^7.8.3" + "@babel/plugin-transform-regenerator" "^7.8.7" + "@babel/plugin-transform-reserved-words" "^7.8.3" + "@babel/plugin-transform-shorthand-properties" "^7.8.3" + "@babel/plugin-transform-spread" "^7.8.3" + "@babel/plugin-transform-sticky-regex" "^7.8.3" + "@babel/plugin-transform-template-literals" "^7.8.3" + "@babel/plugin-transform-typeof-symbol" "^7.8.4" + "@babel/plugin-transform-unicode-regex" "^7.8.3" + "@babel/preset-modules" "^0.1.3" + "@babel/types" "^7.9.0" + browserslist "^4.9.1" + core-js-compat "^3.6.2" + invariant "^2.2.2" + levenary "^1.1.1" + semver "^5.5.0" + +"@babel/preset-env@^7.4.5": + version "7.10.2" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.10.2.tgz#715930f2cf8573b0928005ee562bed52fb65fdfb" + integrity sha512-MjqhX0RZaEgK/KueRzh+3yPSk30oqDKJ5HP5tqTSB1e2gzGS3PLy7K0BIpnp78+0anFuSwOeuCf1zZO7RzRvEA== + dependencies: + "@babel/compat-data" "^7.10.1" + "@babel/helper-compilation-targets" "^7.10.2" + "@babel/helper-module-imports" "^7.10.1" + "@babel/helper-plugin-utils" "^7.10.1" + "@babel/plugin-proposal-async-generator-functions" "^7.10.1" + "@babel/plugin-proposal-class-properties" "^7.10.1" + "@babel/plugin-proposal-dynamic-import" "^7.10.1" + "@babel/plugin-proposal-json-strings" "^7.10.1" + "@babel/plugin-proposal-nullish-coalescing-operator" "^7.10.1" + "@babel/plugin-proposal-numeric-separator" "^7.10.1" + "@babel/plugin-proposal-object-rest-spread" "^7.10.1" + "@babel/plugin-proposal-optional-catch-binding" "^7.10.1" + "@babel/plugin-proposal-optional-chaining" "^7.10.1" + "@babel/plugin-proposal-private-methods" "^7.10.1" + "@babel/plugin-proposal-unicode-property-regex" "^7.10.1" + "@babel/plugin-syntax-async-generators" "^7.8.0" + "@babel/plugin-syntax-class-properties" "^7.10.1" + "@babel/plugin-syntax-dynamic-import" "^7.8.0" + "@babel/plugin-syntax-json-strings" "^7.8.0" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" + "@babel/plugin-syntax-numeric-separator" "^7.10.1" + "@babel/plugin-syntax-object-rest-spread" "^7.8.0" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" + "@babel/plugin-syntax-optional-chaining" "^7.8.0" + "@babel/plugin-syntax-top-level-await" "^7.10.1" + "@babel/plugin-transform-arrow-functions" "^7.10.1" + "@babel/plugin-transform-async-to-generator" "^7.10.1" + "@babel/plugin-transform-block-scoped-functions" "^7.10.1" + "@babel/plugin-transform-block-scoping" "^7.10.1" + "@babel/plugin-transform-classes" "^7.10.1" + "@babel/plugin-transform-computed-properties" "^7.10.1" + "@babel/plugin-transform-destructuring" "^7.10.1" + "@babel/plugin-transform-dotall-regex" "^7.10.1" + "@babel/plugin-transform-duplicate-keys" "^7.10.1" + "@babel/plugin-transform-exponentiation-operator" "^7.10.1" + "@babel/plugin-transform-for-of" "^7.10.1" + "@babel/plugin-transform-function-name" "^7.10.1" + "@babel/plugin-transform-literals" "^7.10.1" + "@babel/plugin-transform-member-expression-literals" "^7.10.1" + "@babel/plugin-transform-modules-amd" "^7.10.1" + "@babel/plugin-transform-modules-commonjs" "^7.10.1" + "@babel/plugin-transform-modules-systemjs" "^7.10.1" + "@babel/plugin-transform-modules-umd" "^7.10.1" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.8.3" + "@babel/plugin-transform-new-target" "^7.10.1" + "@babel/plugin-transform-object-super" "^7.10.1" + "@babel/plugin-transform-parameters" "^7.10.1" + "@babel/plugin-transform-property-literals" "^7.10.1" + "@babel/plugin-transform-regenerator" "^7.10.1" + "@babel/plugin-transform-reserved-words" "^7.10.1" + "@babel/plugin-transform-shorthand-properties" "^7.10.1" + "@babel/plugin-transform-spread" "^7.10.1" + "@babel/plugin-transform-sticky-regex" "^7.10.1" + "@babel/plugin-transform-template-literals" "^7.10.1" + "@babel/plugin-transform-typeof-symbol" "^7.10.1" + "@babel/plugin-transform-unicode-escapes" "^7.10.1" + "@babel/plugin-transform-unicode-regex" "^7.10.1" + "@babel/preset-modules" "^0.1.3" + "@babel/types" "^7.10.2" + browserslist "^4.12.0" + core-js-compat "^3.6.2" + invariant "^2.2.2" + levenary "^1.1.1" + semver "^5.5.0" + +"@babel/preset-modules@^0.1.3": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.3.tgz#13242b53b5ef8c883c3cf7dddd55b36ce80fbc72" + integrity sha512-Ra3JXOHBq2xd56xSF7lMKXdjBn3T772Y1Wet3yWnkDly9zHvJki029tAFzvAAK5cf4YV3yoxuP61crYRol6SVg== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-proposal-unicode-property-regex" "^7.4.4" + "@babel/plugin-transform-dotall-regex" "^7.4.4" + "@babel/types" "^7.4.4" + esutils "^2.0.2" + +"@babel/preset-react@7.9.1": + version "7.9.1" + resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.9.1.tgz#b346403c36d58c3bb544148272a0cefd9c28677a" + integrity sha512-aJBYF23MPj0RNdp/4bHnAP0NVqqZRr9kl0NAOP4nJCex6OYVio59+dnQzsAWFuogdLyeaKA1hmfUIVZkY5J+TQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-transform-react-display-name" "^7.8.3" + "@babel/plugin-transform-react-jsx" "^7.9.1" + "@babel/plugin-transform-react-jsx-development" "^7.9.0" + "@babel/plugin-transform-react-jsx-self" "^7.9.0" + "@babel/plugin-transform-react-jsx-source" "^7.9.0" + +"@babel/preset-react@^7.0.0": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.10.1.tgz#e2ab8ae9a363ec307b936589f07ed753192de041" + integrity sha512-Rw0SxQ7VKhObmFjD/cUcKhPTtzpeviEFX1E6PgP+cYOhQ98icNqtINNFANlsdbQHrmeWnqdxA4Tmnl1jy5tp3Q== + dependencies: + "@babel/helper-plugin-utils" "^7.10.1" + "@babel/plugin-transform-react-display-name" "^7.10.1" + "@babel/plugin-transform-react-jsx" "^7.10.1" + "@babel/plugin-transform-react-jsx-development" "^7.10.1" + "@babel/plugin-transform-react-jsx-self" "^7.10.1" + "@babel/plugin-transform-react-jsx-source" "^7.10.1" + "@babel/plugin-transform-react-pure-annotations" "^7.10.1" + +"@babel/preset-typescript@7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.9.0.tgz#87705a72b1f0d59df21c179f7c3d2ef4b16ce192" + integrity sha512-S4cueFnGrIbvYJgwsVFKdvOmpiL0XGw9MFW9D0vgRys5g36PBhZRL8NX8Gr2akz8XRtzq6HuDXPD/1nniagNUg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-transform-typescript" "^7.9.0" + +"@babel/runtime-corejs3@^7.8.3": + version "7.10.2" + resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.10.2.tgz#3511797ddf9a3d6f3ce46b99cc835184817eaa4e" + integrity sha512-+a2M/u7r15o3dV1NEizr9bRi+KUVnrs/qYxF0Z06DAPx/4VCWaz1WA7EcbE+uqGgt39lp5akWGmHsTseIkHkHg== + dependencies: + core-js-pure "^3.0.0" + regenerator-runtime "^0.13.4" + +"@babel/runtime@7.9.0": + version "7.9.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.9.0.tgz#337eda67401f5b066a6f205a3113d4ac18ba495b" + integrity sha512-cTIudHnzuWLS56ik4DnRnqqNf8MkdUzV4iFFI1h7Jo9xvrpQROYaAnaSd2mHLQAzzZAPfATynX5ord6YlNYNMA== + dependencies: + regenerator-runtime "^0.13.4" + +"@babel/runtime@^7.0.0", "@babel/runtime@^7.3.4", "@babel/runtime@^7.4.0", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4": + version "7.10.2" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.10.2.tgz#d103f21f2602497d38348a32e008637d506db839" + integrity sha512-6sF3uQw2ivImfVIl62RZ7MXhO2tap69WeWK57vAaimT6AZbE4FbqjdEJIN1UqoD6wI6B+1n9UiagafH1sxjOtg== + dependencies: + regenerator-runtime "^0.13.4" + +"@babel/runtime@^7.1.2": + version "7.3.1" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.3.1.tgz#574b03e8e8a9898eaf4a872a92ea20b7846f6f2a" + dependencies: + regenerator-runtime "^0.12.0" + +"@babel/template@^7.10.1", "@babel/template@^7.4.0", "@babel/template@^7.8.6": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.1.tgz#e167154a94cb5f14b28dc58f5356d2162f539811" + integrity sha512-OQDg6SqvFSsc9A0ej6SKINWrpJiNonRIniYondK2ViKhB06i3c0s+76XUft71iqBEe9S1OKsHwPAjfHnuvnCig== + dependencies: + "@babel/code-frame" "^7.10.1" + "@babel/parser" "^7.10.1" + "@babel/types" "^7.10.1" + +"@babel/traverse@^7.1.0", "@babel/traverse@^7.10.1", "@babel/traverse@^7.4.3", "@babel/traverse@^7.7.0", "@babel/traverse@^7.9.0": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.10.1.tgz#bbcef3031e4152a6c0b50147f4958df54ca0dd27" + integrity sha512-C/cTuXeKt85K+p08jN6vMDz8vSV0vZcI0wmQ36o6mjbuo++kPMdpOYw23W2XH04dbRt9/nMEfA4W3eR21CD+TQ== + dependencies: + "@babel/code-frame" "^7.10.1" + "@babel/generator" "^7.10.1" + "@babel/helper-function-name" "^7.10.1" + "@babel/helper-split-export-declaration" "^7.10.1" + "@babel/parser" "^7.10.1" + "@babel/types" "^7.10.1" + debug "^4.1.0" + globals "^11.1.0" + lodash "^4.17.13" + +"@babel/types@^7.0.0", "@babel/types@^7.10.1", "@babel/types@^7.10.2", "@babel/types@^7.3.0", "@babel/types@^7.4.0", "@babel/types@^7.4.4", "@babel/types@^7.7.0", "@babel/types@^7.9.0": + version "7.10.2" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.10.2.tgz#30283be31cad0dbf6fb00bd40641ca0ea675172d" + integrity sha512-AD3AwWBSz0AWF0AkCN9VPiWrvldXq+/e3cHa4J89vo4ymjz1XwrBFFVZmkJTsQIPNk+ZVomPSXUJqq8yyjZsng== + dependencies: + "@babel/helper-validator-identifier" "^7.10.1" + lodash "^4.17.13" + to-fast-properties "^2.0.0" + +"@cnakazawa/watch@^1.0.3": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.4.tgz#f864ae85004d0fcab6f50be9141c4da368d1656a" + integrity sha512-v9kIhKwjeZThiWrLmj0y17CWoyddASLj9O2yvbZkbvw/N3rWOYy9zkV66ursAoVr0mV15bL8g0c4QZUE6cdDoQ== + dependencies: + exec-sh "^0.3.2" + minimist "^1.2.0" + +"@csstools/convert-colors@^1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@csstools/convert-colors/-/convert-colors-1.4.0.tgz#ad495dc41b12e75d588c6db8b9834f08fa131eb7" + integrity sha512-5a6wqoJV/xEdbRNKVo6I4hO3VjyDq//8q2f9I6PBAvMesJHFauXDorcNCsr9RzvsZnaWi5NYCcfyqP1QeFHFbw== + +"@csstools/normalize.css@^10.1.0": + version "10.1.0" + resolved "https://registry.yarnpkg.com/@csstools/normalize.css/-/normalize.css-10.1.0.tgz#f0950bba18819512d42f7197e56c518aa491cf18" + integrity sha512-ij4wRiunFfaJxjB0BdrYHIH8FxBJpOwNPhhAcunlmPdXudL1WQV1qoP9un6JsEBAgQH+7UXyyjh0g7jTxXK6tg== + +"@hapi/address@2.x.x": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@hapi/address/-/address-2.1.4.tgz#5d67ed43f3fd41a69d4b9ff7b56e7c0d1d0a81e5" + integrity sha512-QD1PhQk+s31P1ixsX0H0Suoupp3VMXzIVMSwobR3F3MSUO2YCV0B7xqLcUw/Bh8yuvd3LhpyqLQWTNcRmp6IdQ== + +"@hapi/bourne@1.x.x": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@hapi/bourne/-/bourne-1.3.2.tgz#0a7095adea067243ce3283e1b56b8a8f453b242a" + integrity sha512-1dVNHT76Uu5N3eJNTYcvxee+jzX4Z9lfciqRRHCU27ihbUcYi+iSc2iml5Ke1LXe1SyJCLA0+14Jh4tXJgOppA== + +"@hapi/hoek@8.x.x", "@hapi/hoek@^8.3.0": + version "8.5.1" + resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-8.5.1.tgz#fde96064ca446dec8c55a8c2f130957b070c6e06" + integrity sha512-yN7kbciD87WzLGc5539Tn0sApjyiGHAJgKvG9W8C7O+6c7qmoQMfVs0W4bX17eqz6C78QJqqFrtgdK5EWf6Qow== + +"@hapi/joi@^15.0.0": + version "15.1.1" + resolved "https://registry.yarnpkg.com/@hapi/joi/-/joi-15.1.1.tgz#c675b8a71296f02833f8d6d243b34c57b8ce19d7" + integrity sha512-entf8ZMOK8sc+8YfeOlM8pCfg3b5+WZIKBfUaaJT8UsjAAPjartzxIYm3TIbjvA4u+u++KbcXD38k682nVHDAQ== + dependencies: + "@hapi/address" "2.x.x" + "@hapi/bourne" "1.x.x" + "@hapi/hoek" "8.x.x" + "@hapi/topo" "3.x.x" + +"@hapi/topo@3.x.x": + version "3.1.6" + resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-3.1.6.tgz#68d935fa3eae7fdd5ab0d7f953f3205d8b2bfc29" + integrity sha512-tAag0jEcjwH+P2quUfipd7liWCNX2F8NvYjQp2wtInsZxnMlypdw0FtAOLxtvvkO+GSRRbmNi8m/5y42PQJYCQ== + dependencies: + "@hapi/hoek" "^8.3.0" + +"@jest/console@^24.7.1", "@jest/console@^24.9.0": + version "24.9.0" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-24.9.0.tgz#79b1bc06fb74a8cfb01cbdedf945584b1b9707f0" + integrity sha512-Zuj6b8TnKXi3q4ymac8EQfc3ea/uhLeCGThFqXeC8H9/raaH8ARPUTdId+XyGd03Z4In0/VjD2OYFcBF09fNLQ== + dependencies: + "@jest/source-map" "^24.9.0" + chalk "^2.0.1" + slash "^2.0.0" + +"@jest/core@^24.9.0": + version "24.9.0" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-24.9.0.tgz#2ceccd0b93181f9c4850e74f2a9ad43d351369c4" + integrity sha512-Fogg3s4wlAr1VX7q+rhV9RVnUv5tD7VuWfYy1+whMiWUrvl7U3QJSJyWcDio9Lq2prqYsZaeTv2Rz24pWGkJ2A== + dependencies: + "@jest/console" "^24.7.1" + "@jest/reporters" "^24.9.0" + "@jest/test-result" "^24.9.0" + "@jest/transform" "^24.9.0" + "@jest/types" "^24.9.0" + ansi-escapes "^3.0.0" + chalk "^2.0.1" + exit "^0.1.2" + graceful-fs "^4.1.15" + jest-changed-files "^24.9.0" + jest-config "^24.9.0" + jest-haste-map "^24.9.0" + jest-message-util "^24.9.0" + jest-regex-util "^24.3.0" + jest-resolve "^24.9.0" + jest-resolve-dependencies "^24.9.0" + jest-runner "^24.9.0" + jest-runtime "^24.9.0" + jest-snapshot "^24.9.0" + jest-util "^24.9.0" + jest-validate "^24.9.0" + jest-watcher "^24.9.0" + micromatch "^3.1.10" + p-each-series "^1.0.0" + realpath-native "^1.1.0" + rimraf "^2.5.4" + slash "^2.0.0" + strip-ansi "^5.0.0" + +"@jest/environment@^24.3.0", "@jest/environment@^24.9.0": + version "24.9.0" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-24.9.0.tgz#21e3afa2d65c0586cbd6cbefe208bafade44ab18" + integrity sha512-5A1QluTPhvdIPFYnO3sZC3smkNeXPVELz7ikPbhUj0bQjB07EoE9qtLrem14ZUYWdVayYbsjVwIiL4WBIMV4aQ== + dependencies: + "@jest/fake-timers" "^24.9.0" + "@jest/transform" "^24.9.0" + "@jest/types" "^24.9.0" + jest-mock "^24.9.0" + +"@jest/fake-timers@^24.3.0", "@jest/fake-timers@^24.9.0": + version "24.9.0" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-24.9.0.tgz#ba3e6bf0eecd09a636049896434d306636540c93" + integrity sha512-eWQcNa2YSwzXWIMC5KufBh3oWRIijrQFROsIqt6v/NS9Io/gknw1jsAC9c+ih/RQX4A3O7SeWAhQeN0goKhT9A== + dependencies: + "@jest/types" "^24.9.0" + jest-message-util "^24.9.0" + jest-mock "^24.9.0" + +"@jest/reporters@^24.9.0": + version "24.9.0" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-24.9.0.tgz#86660eff8e2b9661d042a8e98a028b8d631a5b43" + integrity sha512-mu4X0yjaHrffOsWmVLzitKmmmWSQ3GGuefgNscUSWNiUNcEOSEQk9k3pERKEQVBb0Cnn88+UESIsZEMH3o88Gw== + dependencies: + "@jest/environment" "^24.9.0" + "@jest/test-result" "^24.9.0" + "@jest/transform" "^24.9.0" + "@jest/types" "^24.9.0" + chalk "^2.0.1" + exit "^0.1.2" + glob "^7.1.2" + istanbul-lib-coverage "^2.0.2" + istanbul-lib-instrument "^3.0.1" + istanbul-lib-report "^2.0.4" + istanbul-lib-source-maps "^3.0.1" + istanbul-reports "^2.2.6" + jest-haste-map "^24.9.0" + jest-resolve "^24.9.0" + jest-runtime "^24.9.0" + jest-util "^24.9.0" + jest-worker "^24.6.0" + node-notifier "^5.4.2" + slash "^2.0.0" + source-map "^0.6.0" + string-length "^2.0.0" + +"@jest/source-map@^24.3.0", "@jest/source-map@^24.9.0": + version "24.9.0" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-24.9.0.tgz#0e263a94430be4b41da683ccc1e6bffe2a191714" + integrity sha512-/Xw7xGlsZb4MJzNDgB7PW5crou5JqWiBQaz6xyPd3ArOg2nfn/PunV8+olXbbEZzNl591o5rWKE9BRDaFAuIBg== + dependencies: + callsites "^3.0.0" + graceful-fs "^4.1.15" + source-map "^0.6.0" + +"@jest/test-result@^24.9.0": + version "24.9.0" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-24.9.0.tgz#11796e8aa9dbf88ea025757b3152595ad06ba0ca" + integrity sha512-XEFrHbBonBJ8dGp2JmF8kP/nQI/ImPpygKHwQ/SY+es59Z3L5PI4Qb9TQQMAEeYsThG1xF0k6tmG0tIKATNiiA== + dependencies: + "@jest/console" "^24.9.0" + "@jest/types" "^24.9.0" + "@types/istanbul-lib-coverage" "^2.0.0" + +"@jest/test-sequencer@^24.9.0": + version "24.9.0" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-24.9.0.tgz#f8f334f35b625a4f2f355f2fe7e6036dad2e6b31" + integrity sha512-6qqsU4o0kW1dvA95qfNog8v8gkRN9ph6Lz7r96IvZpHdNipP2cBcb07J1Z45mz/VIS01OHJ3pY8T5fUY38tg4A== + dependencies: + "@jest/test-result" "^24.9.0" + jest-haste-map "^24.9.0" + jest-runner "^24.9.0" + jest-runtime "^24.9.0" + +"@jest/transform@^24.9.0": + version "24.9.0" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-24.9.0.tgz#4ae2768b296553fadab09e9ec119543c90b16c56" + integrity sha512-TcQUmyNRxV94S0QpMOnZl0++6RMiqpbH/ZMccFB/amku6Uwvyb1cjYX7xkp5nGNkbX4QPH/FcB6q1HBTHynLmQ== + dependencies: + "@babel/core" "^7.1.0" + "@jest/types" "^24.9.0" + babel-plugin-istanbul "^5.1.0" + chalk "^2.0.1" + convert-source-map "^1.4.0" + fast-json-stable-stringify "^2.0.0" + graceful-fs "^4.1.15" + jest-haste-map "^24.9.0" + jest-regex-util "^24.9.0" + jest-util "^24.9.0" + micromatch "^3.1.10" + pirates "^4.0.1" + realpath-native "^1.1.0" + slash "^2.0.0" + source-map "^0.6.1" + write-file-atomic "2.4.1" + +"@jest/types@^24.3.0", "@jest/types@^24.9.0": + version "24.9.0" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-24.9.0.tgz#63cb26cb7500d069e5a389441a7c6ab5e909fc59" + integrity sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^1.1.1" + "@types/yargs" "^13.0.0" + +"@mrmlnc/readdir-enhanced@^2.2.1": + version "2.2.1" + resolved "https://registry.yarnpkg.com/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde" + integrity sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g== + dependencies: + call-me-maybe "^1.0.1" + glob-to-regexp "^0.3.0" + +"@nodelib/fs.stat@^1.1.2": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz#2b5a3ab3f918cca48a8c754c08168e3f03eba61b" + integrity sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw== + +"@redux-saga/core@^1.1.3": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@redux-saga/core/-/core-1.1.3.tgz#3085097b57a4ea8db5528d58673f20ce0950f6a4" + integrity sha512-8tInBftak8TPzE6X13ABmEtRJGjtK17w7VUs7qV17S8hCO5S3+aUTWZ/DBsBJPdE8Z5jOPwYALyvofgq1Ws+kg== + dependencies: + "@babel/runtime" "^7.6.3" + "@redux-saga/deferred" "^1.1.2" + "@redux-saga/delay-p" "^1.1.2" + "@redux-saga/is" "^1.1.2" + "@redux-saga/symbols" "^1.1.2" + "@redux-saga/types" "^1.1.0" + redux "^4.0.4" + typescript-tuple "^2.2.1" + +"@redux-saga/deferred@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@redux-saga/deferred/-/deferred-1.1.2.tgz#59937a0eba71fff289f1310233bc518117a71888" + integrity sha512-908rDLHFN2UUzt2jb4uOzj6afpjgJe3MjICaUNO3bvkV/kN/cNeI9PMr8BsFXB/MR8WTAZQq/PlTq8Kww3TBSQ== + +"@redux-saga/delay-p@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@redux-saga/delay-p/-/delay-p-1.1.2.tgz#8f515f4b009b05b02a37a7c3d0ca9ddc157bb355" + integrity sha512-ojc+1IoC6OP65Ts5+ZHbEYdrohmIw1j9P7HS9MOJezqMYtCDgpkoqB5enAAZrNtnbSL6gVCWPHaoaTY5KeO0/g== + dependencies: + "@redux-saga/symbols" "^1.1.2" + +"@redux-saga/is@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@redux-saga/is/-/is-1.1.2.tgz#ae6c8421f58fcba80faf7cadb7d65b303b97e58e" + integrity sha512-OLbunKVsCVNTKEf2cH4TYyNbbPgvmZ52iaxBD4I1fTif4+MTXMa4/Z07L83zW/hTCXwpSZvXogqMqLfex2Tg6w== + dependencies: + "@redux-saga/symbols" "^1.1.2" + "@redux-saga/types" "^1.1.0" + +"@redux-saga/symbols@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@redux-saga/symbols/-/symbols-1.1.2.tgz#216a672a487fc256872b8034835afc22a2d0595d" + integrity sha512-EfdGnF423glv3uMwLsGAtE6bg+R9MdqlHEzExnfagXPrIiuxwr3bdiAwz3gi+PsrQ3yBlaBpfGLtDG8rf3LgQQ== + +"@redux-saga/types@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@redux-saga/types/-/types-1.1.0.tgz#0e81ce56b4883b4b2a3001ebe1ab298b84237204" + integrity sha512-afmTuJrylUU/0OtqzaRkbyYFFNgCF73Bvel/sw90pvGrWIZ+vyoIJqA6eMSoA6+nb443kTmulmBtC9NerXboNg== + +"@svgr/babel-plugin-add-jsx-attribute@^4.2.0": + version "4.2.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-4.2.0.tgz#dadcb6218503532d6884b210e7f3c502caaa44b1" + integrity sha512-j7KnilGyZzYr/jhcrSYS3FGWMZVaqyCG0vzMCwzvei0coIkczuYMcniK07nI0aHJINciujjH11T72ICW5eL5Ig== + +"@svgr/babel-plugin-remove-jsx-attribute@^4.2.0": + version "4.2.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-4.2.0.tgz#297550b9a8c0c7337bea12bdfc8a80bb66f85abc" + integrity sha512-3XHLtJ+HbRCH4n28S7y/yZoEQnRpl0tvTZQsHqvaeNXPra+6vE5tbRliH3ox1yZYPCxrlqaJT/Mg+75GpDKlvQ== + +"@svgr/babel-plugin-remove-jsx-empty-expression@^4.2.0": + version "4.2.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-4.2.0.tgz#c196302f3e68eab6a05e98af9ca8570bc13131c7" + integrity sha512-yTr2iLdf6oEuUE9MsRdvt0NmdpMBAkgK8Bjhl6epb+eQWk6abBaX3d65UZ3E3FWaOwePyUgNyNCMVG61gGCQ7w== + +"@svgr/babel-plugin-replace-jsx-attribute-value@^4.2.0": + version "4.2.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-4.2.0.tgz#310ec0775de808a6a2e4fd4268c245fd734c1165" + integrity sha512-U9m870Kqm0ko8beHawRXLGLvSi/ZMrl89gJ5BNcT452fAjtF2p4uRzXkdzvGJJJYBgx7BmqlDjBN/eCp5AAX2w== + +"@svgr/babel-plugin-svg-dynamic-title@^4.3.3": + version "4.3.3" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-4.3.3.tgz#2cdedd747e5b1b29ed4c241e46256aac8110dd93" + integrity sha512-w3Be6xUNdwgParsvxkkeZb545VhXEwjGMwExMVBIdPQJeyMQHqm9Msnb2a1teHBqUYL66qtwfhNkbj1iarCG7w== + +"@svgr/babel-plugin-svg-em-dimensions@^4.2.0": + version "4.2.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-4.2.0.tgz#9a94791c9a288108d20a9d2cc64cac820f141391" + integrity sha512-C0Uy+BHolCHGOZ8Dnr1zXy/KgpBOkEUYY9kI/HseHVPeMbluaX3CijJr7D4C5uR8zrc1T64nnq/k63ydQuGt4w== + +"@svgr/babel-plugin-transform-react-native-svg@^4.2.0": + version "4.2.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-4.2.0.tgz#151487322843359a1ca86b21a3815fd21a88b717" + integrity sha512-7YvynOpZDpCOUoIVlaaOUU87J4Z6RdD6spYN4eUb5tfPoKGSF9OG2NuhgYnq4jSkAxcpMaXWPf1cePkzmqTPNw== + +"@svgr/babel-plugin-transform-svg-component@^4.2.0": + version "4.2.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-4.2.0.tgz#5f1e2f886b2c85c67e76da42f0f6be1b1767b697" + integrity sha512-hYfYuZhQPCBVotABsXKSCfel2slf/yvJY8heTVX1PCTaq/IgASq1IyxPPKJ0chWREEKewIU/JMSsIGBtK1KKxw== + +"@svgr/babel-preset@^4.3.3": + version "4.3.3" + resolved "https://registry.yarnpkg.com/@svgr/babel-preset/-/babel-preset-4.3.3.tgz#a75d8c2f202ac0e5774e6bfc165d028b39a1316c" + integrity sha512-6PG80tdz4eAlYUN3g5GZiUjg2FMcp+Wn6rtnz5WJG9ITGEF1pmFdzq02597Hn0OmnQuCVaBYQE1OVFAnwOl+0A== + dependencies: + "@svgr/babel-plugin-add-jsx-attribute" "^4.2.0" + "@svgr/babel-plugin-remove-jsx-attribute" "^4.2.0" + "@svgr/babel-plugin-remove-jsx-empty-expression" "^4.2.0" + "@svgr/babel-plugin-replace-jsx-attribute-value" "^4.2.0" + "@svgr/babel-plugin-svg-dynamic-title" "^4.3.3" + "@svgr/babel-plugin-svg-em-dimensions" "^4.2.0" + "@svgr/babel-plugin-transform-react-native-svg" "^4.2.0" + "@svgr/babel-plugin-transform-svg-component" "^4.2.0" + +"@svgr/core@^4.3.3": + version "4.3.3" + resolved "https://registry.yarnpkg.com/@svgr/core/-/core-4.3.3.tgz#b37b89d5b757dc66e8c74156d00c368338d24293" + integrity sha512-qNuGF1QON1626UCaZamWt5yedpgOytvLj5BQZe2j1k1B8DUG4OyugZyfEwBeXozCUwhLEpsrgPrE+eCu4fY17w== + dependencies: + "@svgr/plugin-jsx" "^4.3.3" + camelcase "^5.3.1" + cosmiconfig "^5.2.1" + +"@svgr/hast-util-to-babel-ast@^4.3.2": + version "4.3.2" + resolved "https://registry.yarnpkg.com/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-4.3.2.tgz#1d5a082f7b929ef8f1f578950238f630e14532b8" + integrity sha512-JioXclZGhFIDL3ddn4Kiq8qEqYM2PyDKV0aYno8+IXTLuYt6TOgHUbUAAFvqtb0Xn37NwP0BTHglejFoYr8RZg== + dependencies: + "@babel/types" "^7.4.4" + +"@svgr/plugin-jsx@^4.3.3": + version "4.3.3" + resolved "https://registry.yarnpkg.com/@svgr/plugin-jsx/-/plugin-jsx-4.3.3.tgz#e2ba913dbdfbe85252a34db101abc7ebd50992fa" + integrity sha512-cLOCSpNWQnDB1/v+SUENHH7a0XY09bfuMKdq9+gYvtuwzC2rU4I0wKGFEp1i24holdQdwodCtDQdFtJiTCWc+w== + dependencies: + "@babel/core" "^7.4.5" + "@svgr/babel-preset" "^4.3.3" + "@svgr/hast-util-to-babel-ast" "^4.3.2" + svg-parser "^2.0.0" + +"@svgr/plugin-svgo@^4.3.1": + version "4.3.1" + resolved "https://registry.yarnpkg.com/@svgr/plugin-svgo/-/plugin-svgo-4.3.1.tgz#daac0a3d872e3f55935c6588dd370336865e9e32" + integrity sha512-PrMtEDUWjX3Ea65JsVCwTIXuSqa3CG9px+DluF1/eo9mlDrgrtFE7NE/DjdhjJgSM9wenlVBzkzneSIUgfUI/w== + dependencies: + cosmiconfig "^5.2.1" + merge-deep "^3.0.2" + svgo "^1.2.2" + +"@svgr/webpack@4.3.3": + version "4.3.3" + resolved "https://registry.yarnpkg.com/@svgr/webpack/-/webpack-4.3.3.tgz#13cc2423bf3dff2d494f16b17eb7eacb86895017" + integrity sha512-bjnWolZ6KVsHhgyCoYRFmbd26p8XVbulCzSG53BDQqAr+JOAderYK7CuYrB3bDjHJuF6LJ7Wrr42+goLRV9qIg== + dependencies: + "@babel/core" "^7.4.5" + "@babel/plugin-transform-react-constant-elements" "^7.0.0" + "@babel/preset-env" "^7.4.5" + "@babel/preset-react" "^7.0.0" + "@svgr/core" "^4.3.3" + "@svgr/plugin-jsx" "^4.3.3" + "@svgr/plugin-svgo" "^4.3.1" + loader-utils "^1.2.3" + +"@types/babel__core@^7.1.0": + version "7.1.8" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.8.tgz#057f725aca3641f49fc11c7a87a9de5ec588a5d7" + integrity sha512-KXBiQG2OXvaPWFPDS1rD8yV9vO0OuWIqAEqLsbfX0oU2REN5KuoMnZ1gClWcBhO5I3n6oTVAmrMufOvRqdmFTQ== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + +"@types/babel__generator@*": + version "7.6.1" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.1.tgz#4901767b397e8711aeb99df8d396d7ba7b7f0e04" + integrity sha512-bBKm+2VPJcMRVwNhxKu8W+5/zT7pwNEqeokFOmbvVSqGzFneNxYcEBro9Ac7/N9tlsaPYnZLK8J1LWKkMsLAew== + dependencies: + "@babel/types" "^7.0.0" + +"@types/babel__template@*": + version "7.0.2" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.0.2.tgz#4ff63d6b52eddac1de7b975a5223ed32ecea9307" + integrity sha512-/K6zCpeW7Imzgab2bLkLEbz0+1JlFSrUMdw7KoIIu+IUdu51GWaBZpd3y1VXGVXzynvGa4DaIaxNZHiON3GXUg== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + +"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": + version "7.0.12" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.0.12.tgz#22f49a028e69465390f87bb103ebd61bd086b8f5" + integrity sha512-t4CoEokHTfcyfb4hUaF9oOHu9RmmNWnm1CP0YmMqOOfClKascOmvlEM736vlqeScuGvBDsHkf8R2INd4DWreQA== + dependencies: + "@babel/types" "^7.3.0" + +"@types/color-name@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" + integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ== + +"@types/eslint-visitor-keys@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d" + integrity sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag== + +"@types/glob@^7.1.1": + version "7.1.2" + resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.2.tgz#06ca26521353a545d94a0adc74f38a59d232c987" + integrity sha512-VgNIkxK+j7Nz5P7jvUZlRvhuPSmsEfS03b0alKcq5V/STUKAa3Plemsn5mrQUO7am6OErJ4rhGEGJbACclrtRA== + dependencies: + "@types/minimatch" "*" + "@types/node" "*" + +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.2.tgz#79d7a78bad4219f4c03d6557a1c72d9ca6ba62d5" + integrity sha512-rsZg7eL+Xcxsxk2XlBt9KcG8nOp9iYdKCOikY9x2RFJCyOdNj4MKPQty0e8oZr29vVAzKXr1BmR+kZauti3o1w== + +"@types/istanbul-lib-report@*": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#c14c24f18ea8190c118ee7562b7ff99a36552686" + integrity sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg== + dependencies: + "@types/istanbul-lib-coverage" "*" + +"@types/istanbul-reports@^1.1.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-1.1.2.tgz#e875cc689e47bce549ec81f3df5e6f6f11cfaeb2" + integrity sha512-P/W9yOX/3oPZSpaYOCQzGqgCQRXn0FFO/V8bWrCQs+wLmvVVxk6CRBXALEvNs9OHIatlnlFokfhuDo2ug01ciw== + dependencies: + "@types/istanbul-lib-coverage" "*" + "@types/istanbul-lib-report" "*" + +"@types/json-schema@^7.0.3", "@types/json-schema@^7.0.4": + version "7.0.4" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.4.tgz#38fd73ddfd9b55abb1e1b2ed578cb55bd7b7d339" + integrity sha512-8+KAKzEvSUdeo+kmqnKrqgeE+LcA0tjYWFY7RPProVYwnqDjukzO+3b6dLD56rYX5TdWejnEOLJYOIeh4CXKuA== + +"@types/minimatch@*": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" + integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== + +"@types/node@*": + version "14.0.11" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.11.tgz#61d4886e2424da73b7b25547f59fdcb534c165a3" + integrity sha512-lCvvI24L21ZVeIiyIUHZ5Oflv1hhHQ5E1S25IRlKIXaRkVgmXpJMI3wUJkmym2bTbCe+WoIibQnMVAU3FguaOg== + +"@types/parse-json@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" + integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== + +"@types/prop-types@*": + version "15.5.8" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.5.8.tgz#8ae4e0ea205fe95c3901a5a1df7f66495e3a56ce" + +"@types/q@^1.5.1": + version "1.5.4" + resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.4.tgz#15925414e0ad2cd765bfef58842f7e26a7accb24" + integrity sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug== + +"@types/react@*": + version "16.8.2" + resolved "https://registry.yarnpkg.com/@types/react/-/react-16.8.2.tgz#3b7a7f7ea89d1c7d68b00849fb5de839011c077b" + dependencies: + "@types/prop-types" "*" + csstype "^2.2.0" + +"@types/stack-utils@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e" + integrity sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw== + +"@types/yargs-parser@*": + version "15.0.0" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-15.0.0.tgz#cb3f9f741869e20cce330ffbeb9271590483882d" + integrity sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw== + +"@types/yargs@^13.0.0": + version "13.0.9" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-13.0.9.tgz#44028e974343c7afcf3960f1a2b1099c39a7b5e1" + integrity sha512-xrvhZ4DZewMDhoH1utLtOAwYQy60eYFoXeje30TzM3VOvQlBwQaEpKFq5m34k1wOw2AKIi2pwtiAjdmhvlBUzg== + dependencies: + "@types/yargs-parser" "*" + +"@typescript-eslint/eslint-plugin@^2.10.0": + version "2.34.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.34.0.tgz#6f8ce8a46c7dea4a6f1d171d2bb8fbae6dac2be9" + integrity sha512-4zY3Z88rEE99+CNvTbXSyovv2z9PNOVffTWD2W8QF5s2prBQtwN2zadqERcrHpcR7O/+KMI3fcTAmUUhK/iQcQ== + dependencies: + "@typescript-eslint/experimental-utils" "2.34.0" + functional-red-black-tree "^1.0.1" + regexpp "^3.0.0" + tsutils "^3.17.1" + +"@typescript-eslint/experimental-utils@2.34.0": + version "2.34.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.34.0.tgz#d3524b644cdb40eebceca67f8cf3e4cc9c8f980f" + integrity sha512-eS6FTkq+wuMJ+sgtuNTtcqavWXqsflWcfBnlYhg/nS4aZ1leewkXGbvBhaapn1q6qf4M71bsR1tez5JTRMuqwA== + dependencies: + "@types/json-schema" "^7.0.3" + "@typescript-eslint/typescript-estree" "2.34.0" + eslint-scope "^5.0.0" + eslint-utils "^2.0.0" + +"@typescript-eslint/parser@^2.10.0": + version "2.34.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.34.0.tgz#50252630ca319685420e9a39ca05fe185a256bc8" + integrity sha512-03ilO0ucSD0EPTw2X4PntSIRFtDPWjrVq7C3/Z3VQHRC7+13YB55rcJI3Jt+YgeHbjUdJPcPa7b23rXCBokuyA== + dependencies: + "@types/eslint-visitor-keys" "^1.0.0" + "@typescript-eslint/experimental-utils" "2.34.0" + "@typescript-eslint/typescript-estree" "2.34.0" + eslint-visitor-keys "^1.1.0" + +"@typescript-eslint/typescript-estree@2.34.0": + version "2.34.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.34.0.tgz#14aeb6353b39ef0732cc7f1b8285294937cf37d5" + integrity sha512-OMAr+nJWKdlVM9LOqCqh3pQQPwxHAN7Du8DR6dmwCrAmxtiXQnhHJ6tBNtf+cggqfo51SG/FCwnKhXCIM7hnVg== + dependencies: + debug "^4.1.1" + eslint-visitor-keys "^1.1.0" + glob "^7.1.6" + is-glob "^4.0.1" + lodash "^4.17.15" + semver "^7.3.2" + tsutils "^3.17.1" + +"@webassemblyjs/ast@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.8.5.tgz#51b1c5fe6576a34953bf4b253df9f0d490d9e359" + integrity sha512-aJMfngIZ65+t71C3y2nBBg5FFG0Okt9m0XEgWZ7Ywgn1oMAT8cNwx00Uv1cQyHtidq0Xn94R4TAywO+LCQ+ZAQ== + dependencies: + "@webassemblyjs/helper-module-context" "1.8.5" + "@webassemblyjs/helper-wasm-bytecode" "1.8.5" + "@webassemblyjs/wast-parser" "1.8.5" + +"@webassemblyjs/floating-point-hex-parser@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.8.5.tgz#1ba926a2923613edce496fd5b02e8ce8a5f49721" + integrity sha512-9p+79WHru1oqBh9ewP9zW95E3XAo+90oth7S5Re3eQnECGq59ly1Ri5tsIipKGpiStHsUYmY3zMLqtk3gTcOtQ== + +"@webassemblyjs/helper-api-error@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.8.5.tgz#c49dad22f645227c5edb610bdb9697f1aab721f7" + integrity sha512-Za/tnzsvnqdaSPOUXHyKJ2XI7PDX64kWtURyGiJJZKVEdFOsdKUCPTNEVFZq3zJ2R0G5wc2PZ5gvdTRFgm81zA== + +"@webassemblyjs/helper-buffer@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.8.5.tgz#fea93e429863dd5e4338555f42292385a653f204" + integrity sha512-Ri2R8nOS0U6G49Q86goFIPNgjyl6+oE1abW1pS84BuhP1Qcr5JqMwRFT3Ah3ADDDYGEgGs1iyb1DGX+kAi/c/Q== + +"@webassemblyjs/helper-code-frame@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.8.5.tgz#9a740ff48e3faa3022b1dff54423df9aa293c25e" + integrity sha512-VQAadSubZIhNpH46IR3yWO4kZZjMxN1opDrzePLdVKAZ+DFjkGD/rf4v1jap744uPVU6yjL/smZbRIIJTOUnKQ== + dependencies: + "@webassemblyjs/wast-printer" "1.8.5" + +"@webassemblyjs/helper-fsm@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-fsm/-/helper-fsm-1.8.5.tgz#ba0b7d3b3f7e4733da6059c9332275d860702452" + integrity sha512-kRuX/saORcg8se/ft6Q2UbRpZwP4y7YrWsLXPbbmtepKr22i8Z4O3V5QE9DbZK908dh5Xya4Un57SDIKwB9eow== + +"@webassemblyjs/helper-module-context@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-module-context/-/helper-module-context-1.8.5.tgz#def4b9927b0101dc8cbbd8d1edb5b7b9c82eb245" + integrity sha512-/O1B236mN7UNEU4t9X7Pj38i4VoU8CcMHyy3l2cV/kIF4U5KoHXDVqcDuOs1ltkac90IM4vZdHc52t1x8Yfs3g== + dependencies: + "@webassemblyjs/ast" "1.8.5" + mamacro "^0.0.3" + +"@webassemblyjs/helper-wasm-bytecode@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.8.5.tgz#537a750eddf5c1e932f3744206551c91c1b93e61" + integrity sha512-Cu4YMYG3Ddl72CbmpjU/wbP6SACcOPVbHN1dI4VJNJVgFwaKf1ppeFJrwydOG3NDHxVGuCfPlLZNyEdIYlQ6QQ== + +"@webassemblyjs/helper-wasm-section@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.8.5.tgz#74ca6a6bcbe19e50a3b6b462847e69503e6bfcbf" + integrity sha512-VV083zwR+VTrIWWtgIUpqfvVdK4ff38loRmrdDBgBT8ADXYsEZ5mPQ4Nde90N3UYatHdYoDIFb7oHzMncI02tA== + dependencies: + "@webassemblyjs/ast" "1.8.5" + "@webassemblyjs/helper-buffer" "1.8.5" + "@webassemblyjs/helper-wasm-bytecode" "1.8.5" + "@webassemblyjs/wasm-gen" "1.8.5" + +"@webassemblyjs/ieee754@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.8.5.tgz#712329dbef240f36bf57bd2f7b8fb9bf4154421e" + integrity sha512-aaCvQYrvKbY/n6wKHb/ylAJr27GglahUO89CcGXMItrOBqRarUMxWLJgxm9PJNuKULwN5n1csT9bYoMeZOGF3g== + dependencies: + "@xtuc/ieee754" "^1.2.0" + +"@webassemblyjs/leb128@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.8.5.tgz#044edeb34ea679f3e04cd4fd9824d5e35767ae10" + integrity sha512-plYUuUwleLIziknvlP8VpTgO4kqNaH57Y3JnNa6DLpu/sGcP6hbVdfdX5aHAV716pQBKrfuU26BJK29qY37J7A== + dependencies: + "@xtuc/long" "4.2.2" + +"@webassemblyjs/utf8@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.8.5.tgz#a8bf3b5d8ffe986c7c1e373ccbdc2a0915f0cedc" + integrity sha512-U7zgftmQriw37tfD934UNInokz6yTmn29inT2cAetAsaU9YeVCveWEwhKL1Mg4yS7q//NGdzy79nlXh3bT8Kjw== + +"@webassemblyjs/wasm-edit@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.8.5.tgz#962da12aa5acc1c131c81c4232991c82ce56e01a" + integrity sha512-A41EMy8MWw5yvqj7MQzkDjU29K7UJq1VrX2vWLzfpRHt3ISftOXqrtojn7nlPsZ9Ijhp5NwuODuycSvfAO/26Q== + dependencies: + "@webassemblyjs/ast" "1.8.5" + "@webassemblyjs/helper-buffer" "1.8.5" + "@webassemblyjs/helper-wasm-bytecode" "1.8.5" + "@webassemblyjs/helper-wasm-section" "1.8.5" + "@webassemblyjs/wasm-gen" "1.8.5" + "@webassemblyjs/wasm-opt" "1.8.5" + "@webassemblyjs/wasm-parser" "1.8.5" + "@webassemblyjs/wast-printer" "1.8.5" + +"@webassemblyjs/wasm-gen@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.8.5.tgz#54840766c2c1002eb64ed1abe720aded714f98bc" + integrity sha512-BCZBT0LURC0CXDzj5FXSc2FPTsxwp3nWcqXQdOZE4U7h7i8FqtFK5Egia6f9raQLpEKT1VL7zr4r3+QX6zArWg== + dependencies: + "@webassemblyjs/ast" "1.8.5" + "@webassemblyjs/helper-wasm-bytecode" "1.8.5" + "@webassemblyjs/ieee754" "1.8.5" + "@webassemblyjs/leb128" "1.8.5" + "@webassemblyjs/utf8" "1.8.5" + +"@webassemblyjs/wasm-opt@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.8.5.tgz#b24d9f6ba50394af1349f510afa8ffcb8a63d264" + integrity sha512-HKo2mO/Uh9A6ojzu7cjslGaHaUU14LdLbGEKqTR7PBKwT6LdPtLLh9fPY33rmr5wcOMrsWDbbdCHq4hQUdd37Q== + dependencies: + "@webassemblyjs/ast" "1.8.5" + "@webassemblyjs/helper-buffer" "1.8.5" + "@webassemblyjs/wasm-gen" "1.8.5" + "@webassemblyjs/wasm-parser" "1.8.5" + +"@webassemblyjs/wasm-parser@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.8.5.tgz#21576f0ec88b91427357b8536383668ef7c66b8d" + integrity sha512-pi0SYE9T6tfcMkthwcgCpL0cM9nRYr6/6fjgDtL6q/ZqKHdMWvxitRi5JcZ7RI4SNJJYnYNaWy5UUrHQy998lw== + dependencies: + "@webassemblyjs/ast" "1.8.5" + "@webassemblyjs/helper-api-error" "1.8.5" + "@webassemblyjs/helper-wasm-bytecode" "1.8.5" + "@webassemblyjs/ieee754" "1.8.5" + "@webassemblyjs/leb128" "1.8.5" + "@webassemblyjs/utf8" "1.8.5" + +"@webassemblyjs/wast-parser@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-parser/-/wast-parser-1.8.5.tgz#e10eecd542d0e7bd394f6827c49f3df6d4eefb8c" + integrity sha512-daXC1FyKWHF1i11obK086QRlsMsY4+tIOKgBqI1lxAnkp9xe9YMcgOxm9kLe+ttjs5aWV2KKE1TWJCN57/Btsg== + dependencies: + "@webassemblyjs/ast" "1.8.5" + "@webassemblyjs/floating-point-hex-parser" "1.8.5" + "@webassemblyjs/helper-api-error" "1.8.5" + "@webassemblyjs/helper-code-frame" "1.8.5" + "@webassemblyjs/helper-fsm" "1.8.5" + "@xtuc/long" "4.2.2" + +"@webassemblyjs/wast-printer@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.8.5.tgz#114bbc481fd10ca0e23b3560fa812748b0bae5bc" + integrity sha512-w0U0pD4EhlnvRyeJzBqaVSJAo9w/ce7/WPogeXLzGkO6hzhr4GnQIZ4W4uUt5b9ooAaXPtnXlj0gzsXEOUNYMg== + dependencies: + "@webassemblyjs/ast" "1.8.5" + "@webassemblyjs/wast-parser" "1.8.5" + "@xtuc/long" "4.2.2" + +"@xtuc/ieee754@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" + integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== + +"@xtuc/long@4.2.2": + version "4.2.2" + resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" + integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== + +abab@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.3.tgz#623e2075e02eb2d3f2475e49f99c91846467907a" + integrity sha512-tsFzPpcttalNjFBCFMqsKYQcWxxen1pgJR56by//QwvJc4/OUS3kPOOttx2tSIfjsylB0pYu7f5D3K1RCxUnUg== + +abbrev@1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" + +accepts@~1.3.4, accepts@~1.3.5: + version "1.3.5" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.5.tgz#eb777df6011723a3b14e8a72c0805c8e86746bd2" + dependencies: + mime-types "~2.1.18" + negotiator "0.6.1" + +accepts@~1.3.7: + version "1.3.7" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" + integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA== + dependencies: + mime-types "~2.1.24" + negotiator "0.6.2" + +acorn-globals@^4.1.0, acorn-globals@^4.3.0: + version "4.3.4" + resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-4.3.4.tgz#9fa1926addc11c97308c4e66d7add0d40c3272e7" + integrity sha512-clfQEh21R+D0leSbUdWf3OcfqyaCSAQ8Ryq00bofSekfr9W8u1jyYZo6ir0xu9Gtcf7BjcHJpnbZH7JOCpP60A== + dependencies: + acorn "^6.0.1" + acorn-walk "^6.0.1" + +acorn-jsx@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.2.0.tgz#4c66069173d6fdd68ed85239fc256226182b2ebe" + integrity sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ== + +acorn-walk@^6.0.1: + version "6.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-6.2.0.tgz#123cb8f3b84c2171f1f7fb252615b1c78a6b1a8c" + integrity sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA== + +acorn@^5.5.3: + version "5.7.4" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.4.tgz#3e8d8a9947d0599a1796d10225d7432f4a4acf5e" + integrity sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg== + +acorn@^6.0.1, acorn@^6.0.4, acorn@^6.2.1: + version "6.4.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.1.tgz#531e58ba3f51b9dacb9a6646ca4debf5b14ca474" + integrity sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA== + +acorn@^7.1.1: + version "7.2.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.2.0.tgz#17ea7e40d7c8640ff54a694c889c26f31704effe" + integrity sha512-apwXVmYVpQ34m/i71vrApRrRKCWQnZZF1+npOD0WV5xZFfwWOmKGQ2RWlfdy9vWITsenisM8M0Qeq8agcFHNiQ== + +address@1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/address/-/address-1.1.2.tgz#bf1116c9c758c51b7a933d296b72c221ed9428b6" + integrity sha512-aT6camzM4xEA54YVJYSqxz1kv4IHnQZRtThJJHhUMRExaU5spC7jX5ugSwTaTgJliIgs4VhZOk7htClvQ/LmRA== + +address@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/address/-/address-1.0.3.tgz#b5f50631f8d6cec8bd20c963963afb55e06cbce9" + +adjust-sourcemap-loader@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/adjust-sourcemap-loader/-/adjust-sourcemap-loader-2.0.0.tgz#6471143af75ec02334b219f54bc7970c52fb29a4" + integrity sha512-4hFsTsn58+YjrU9qKzML2JSSDqKvN8mUGQ0nNIrfPi8hmIONT4L3uUaT6MKdMsZ9AjsU6D2xDkZxCkbQPxChrA== + dependencies: + assert "1.4.1" + camelcase "5.0.0" + loader-utils "1.2.3" + object-path "0.11.4" + regex-parser "2.2.10" + +after@0.8.2: + version "0.8.2" + resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f" + +aggregate-error@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.0.1.tgz#db2fe7246e536f40d9b5442a39e117d7dd6a24e0" + integrity sha512-quoaXsZ9/BLNae5yiNoUz+Nhkwz83GhWwtYFglcjEQB2NDHCIpApbqXxIFnm4Pq/Nvhrsq5sYJFyohrrxnTGAA== + dependencies: + clean-stack "^2.0.0" + indent-string "^4.0.0" + +ajv-errors@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d" + integrity sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ== + +ajv-keywords@^3.1.0, ajv-keywords@^3.4.1: + version "3.4.1" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.4.1.tgz#ef916e271c64ac12171fd8384eaae6b2345854da" + integrity sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ== + +ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.2: + version "6.12.2" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.2.tgz#c629c5eced17baf314437918d2da88c99d5958cd" + integrity sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ajv@^6.5.5: + version "6.9.1" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.9.1.tgz#a4d3683d74abc5670e75f0b16520f70a20ea8dc1" + dependencies: + fast-deep-equal "^2.0.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +alphanum-sort@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" + integrity sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM= + +amdefine@>=0.0.4: + version "1.0.1" + resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" + +ansi-colors@^3.0.0, ansi-colors@^3.2.1: + version "3.2.4" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.4.tgz#e3a3da4bfbae6c86a9c285625de124a234026fbf" + integrity sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA== + +ansi-escapes@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" + +ansi-escapes@^4.2.1, ansi-escapes@^4.3.0: + version "4.3.1" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.1.tgz#a5c47cc43181f1f38ffd7076837700d395522a61" + integrity sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA== + dependencies: + type-fest "^0.11.0" + +ansi-html@0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/ansi-html/-/ansi-html-0.0.7.tgz#813584021962a9e9e6fd039f940d12f56ca7859e" + +ansi-regex@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + +ansi-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" + +ansi-regex@^4.0.0, ansi-regex@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" + integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== + +ansi-regex@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" + integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== + +ansi-styles@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" + +ansi-styles@^3.2.0, ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359" + integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA== + dependencies: + "@types/color-name" "^1.1.1" + color-convert "^2.0.1" + +anymatch@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" + dependencies: + micromatch "^3.1.4" + normalize-path "^2.1.1" + +anymatch@~3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142" + integrity sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +approximate-number@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/approximate-number/-/approximate-number-2.0.0.tgz#43c7fbfbbb0070a412131d65581f868b24d1eb29" + integrity sha1-Q8f7+7sAcKQSEx1lWB+GiyTR6yk= + +aproba@^1.0.3, aproba@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" + +are-we-there-yet@~1.1.2: + version "1.1.5" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" + dependencies: + delegates "^1.0.0" + readable-stream "^2.0.6" + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + dependencies: + sprintf-js "~1.0.2" + +aria-query@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-3.0.0.tgz#65b3fcc1ca1155a8c9ae64d6eee297f15d5133cc" + integrity sha1-ZbP8wcoRVajJrmTW7uKX8V1RM8w= + dependencies: + ast-types-flow "0.0.7" + commander "^2.11.0" + +arity-n@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/arity-n/-/arity-n-1.0.4.tgz#d9e76b11733e08569c0847ae7b39b2860b30b745" + integrity sha1-2edrEXM+CFacCEeuezmyhgswt0U= + +arr-diff@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" + +arr-flatten@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" + +arr-union@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" + +array-equal@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93" + +array-filter@~0.0.0: + version "0.0.1" + resolved "https://registry.yarnpkg.com/array-filter/-/array-filter-0.0.1.tgz#7da8cf2e26628ed732803581fd21f67cacd2eeec" + +array-find-index@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" + +array-flatten@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" + +array-flatten@^2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.2.tgz#24ef80a28c1a893617e2149b0c6d0d788293b099" + +array-includes@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.0.3.tgz#184b48f62d92d7452bb31b323165c7f8bd02266d" + dependencies: + define-properties "^1.1.2" + es-abstract "^1.7.0" + +array-includes@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.1.tgz#cdd67e6852bdf9c1215460786732255ed2459348" + integrity sha512-c2VXaCHl7zPsvpkFsw4nxvFie4fh1ur9bpcgsVkIjqn0H/Xwdg+7fv3n2r/isyS8EBj5b06M9kHyZuIr4El6WQ== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0" + is-string "^1.0.5" + +array-map@~0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/array-map/-/array-map-0.0.0.tgz#88a2bab73d1cf7bcd5c1b118a003f66f665fa662" + +array-reduce@~0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/array-reduce/-/array-reduce-0.0.0.tgz#173899d3ffd1c7d9383e4479525dbe278cab5f2b" + +array-union@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" + dependencies: + array-uniq "^1.0.1" + +array-uniq@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" + +array-unique@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" + +array.prototype.flat@^1.2.1: + version "1.2.3" + resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.2.3.tgz#0de82b426b0318dbfdb940089e38b043d37f6c7b" + integrity sha512-gBlRZV0VSmfPIeWfuuy56XZMvbVfbEUnOXUvt3F/eUUUSyzlgLxhEX4YAEpxNAogRGehPSnfXyPtYyKAhkzQhQ== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + +arraybuffer.slice@~0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz#3bbc4275dd584cc1b10809b89d4e8b63a69e7675" + +arrify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" + +asap@~2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" + integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= + +asn1.js@^4.0.0: + version "4.10.1" + resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.10.1.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0" + dependencies: + bn.js "^4.0.0" + inherits "^2.0.1" + minimalistic-assert "^1.0.0" + +asn1@~0.2.3: + version "0.2.4" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" + dependencies: + safer-buffer "~2.1.0" + +assert-plus@1.0.0, assert-plus@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" + +assert@1.4.1, assert@^1.1.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/assert/-/assert-1.4.1.tgz#99912d591836b5a6f5b345c0f07eefc08fc65d91" + dependencies: + util "0.10.3" + +assign-symbols@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" + +ast-types-flow@0.0.7, ast-types-flow@^0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.7.tgz#f70b735c6bca1a5c9c22d982c3e39e7feba3bdad" + integrity sha1-9wtzXGvKGlycItmCw+Oef+ujva0= + +astral-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" + integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== + +astral-regex@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" + integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== + +async-each@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d" + +async-foreach@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/async-foreach/-/async-foreach-0.1.3.tgz#36121f845c0578172de419a97dbeb1d16ec34542" + +async-limiter@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8" + +async@^2.6.2: + version "2.6.3" + resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" + integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg== + dependencies: + lodash "^4.17.14" + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + +atob@^2.1.1, atob@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" + +autoprefixer@^9.6.1: + version "9.8.0" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.8.0.tgz#68e2d2bef7ba4c3a65436f662d0a56a741e56511" + integrity sha512-D96ZiIHXbDmU02dBaemyAg53ez+6F5yZmapmgKcjm35yEe1uVDYI8hGW3VYoGRaG290ZFf91YxHrR518vC0u/A== + dependencies: + browserslist "^4.12.0" + caniuse-lite "^1.0.30001061" + chalk "^2.4.2" + normalize-range "^0.1.2" + num2fraction "^1.2.2" + postcss "^7.0.30" + postcss-value-parser "^4.1.0" + +aws-sign2@~0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" + +aws4@^1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f" + +axobject-query@^2.0.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.1.2.tgz#2bdffc0371e643e5f03ba99065d5179b9ca79799" + integrity sha512-ICt34ZmrVt8UQnvPl6TVyDTkmhXmAyAT4Jh5ugfGUX4MOrZ+U/ZY6/sdylRw3qGNr9Ub5AJsaHeDMzNLehRdOQ== + +babel-code-frame@^6.22.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" + dependencies: + chalk "^1.1.3" + esutils "^2.0.2" + js-tokens "^3.0.2" + +babel-eslint@10.1.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.1.0.tgz#6968e568a910b78fb3779cdd8b6ac2f479943232" + integrity sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg== + dependencies: + "@babel/code-frame" "^7.0.0" + "@babel/parser" "^7.7.0" + "@babel/traverse" "^7.7.0" + "@babel/types" "^7.7.0" + eslint-visitor-keys "^1.0.0" + resolve "^1.12.0" + +babel-extract-comments@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/babel-extract-comments/-/babel-extract-comments-1.0.0.tgz#0a2aedf81417ed391b85e18b4614e693a0351a21" + integrity sha512-qWWzi4TlddohA91bFwgt6zO/J0X+io7Qp184Fw0m2JYRSTZnJbFR8+07KmzudHCZgOiKRCrjhylwv9Xd8gfhVQ== + dependencies: + babylon "^6.18.0" + +babel-jest@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-24.9.0.tgz#3fc327cb8467b89d14d7bc70e315104a783ccd54" + integrity sha512-ntuddfyiN+EhMw58PTNL1ph4C9rECiQXjI4nMMBKBaNjXvqLdkXpPRcMSr4iyBrJg/+wz9brFUD6RhOAT6r4Iw== + dependencies: + "@jest/transform" "^24.9.0" + "@jest/types" "^24.9.0" + "@types/babel__core" "^7.1.0" + babel-plugin-istanbul "^5.1.0" + babel-preset-jest "^24.9.0" + chalk "^2.4.2" + slash "^2.0.0" + +babel-loader@8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.1.0.tgz#c611d5112bd5209abe8b9fa84c3e4da25275f1c3" + integrity sha512-7q7nC1tYOrqvUrN3LQK4GwSk/TQorZSOlO9C+RZDZpODgyN4ZlCqE5q9cDsyWOliN+aU9B4JX01xK9eJXowJLw== + dependencies: + find-cache-dir "^2.1.0" + loader-utils "^1.4.0" + mkdirp "^0.5.3" + pify "^4.0.1" + schema-utils "^2.6.5" + +babel-plugin-dynamic-import-node@^2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz#84fda19c976ec5c6defef57f9427b3def66e17a3" + integrity sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ== + dependencies: + object.assign "^4.1.0" + +babel-plugin-istanbul@^5.1.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-5.2.0.tgz#df4ade83d897a92df069c4d9a25cf2671293c854" + integrity sha512-5LphC0USA8t4i1zCtjbbNb6jJj/9+X6P37Qfirc/70EQ34xKlMW+a1RHGwxGI+SwWpNwZ27HqvzAobeqaXwiZw== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + find-up "^3.0.0" + istanbul-lib-instrument "^3.3.0" + test-exclude "^5.2.3" + +babel-plugin-jest-hoist@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-24.9.0.tgz#4f837091eb407e01447c8843cbec546d0002d756" + integrity sha512-2EMA2P8Vp7lG0RAzr4HXqtYwacfMErOuv1U3wrvxHX6rD1sV6xS3WXG3r8TRQ2r6w8OhvSdWt+z41hQNwNm3Xw== + dependencies: + "@types/babel__traverse" "^7.0.6" + +babel-plugin-macros@2.8.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz#0f958a7cc6556b1e65344465d99111a1e5e10138" + integrity sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg== + dependencies: + "@babel/runtime" "^7.7.2" + cosmiconfig "^6.0.0" + resolve "^1.12.0" + +babel-plugin-named-asset-import@^0.3.6: + version "0.3.6" + resolved "https://registry.yarnpkg.com/babel-plugin-named-asset-import/-/babel-plugin-named-asset-import-0.3.6.tgz#c9750a1b38d85112c9e166bf3ef7c5dbc605f4be" + integrity sha512-1aGDUfL1qOOIoqk9QKGIo2lANk+C7ko/fqH0uIyC71x3PEGz0uVP8ISgfEsFuG+FKmjHTvFK/nNM8dowpmUxLA== + +babel-plugin-syntax-object-rest-spread@^6.8.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5" + +babel-plugin-transform-object-rest-spread@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.26.0.tgz#0f36692d50fef6b7e2d4b3ac1478137a963b7b06" + integrity sha1-DzZpLVD+9rfi1LOsFHgTepY7ewY= + dependencies: + babel-plugin-syntax-object-rest-spread "^6.8.0" + babel-runtime "^6.26.0" + +babel-plugin-transform-react-remove-prop-types@0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz#f2edaf9b4c6a5fbe5c1d678bfb531078c1555f3a" + integrity sha512-eqj0hVcJUR57/Ug2zE1Yswsw4LhuqqHhD+8v120T1cl3kjg76QwtyBrdIk4WVwK+lAhBJVYCd/v+4nc4y+8JsA== + +babel-preset-jest@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-24.9.0.tgz#192b521e2217fb1d1f67cf73f70c336650ad3cdc" + integrity sha512-izTUuhE4TMfTRPF92fFwD2QfdXaZW08qvWTFCI51V8rW5x00UuPgc3ajRoWofXOuxjfcOM5zzSYsQS3H8KGCAg== + dependencies: + "@babel/plugin-syntax-object-rest-spread" "^7.0.0" + babel-plugin-jest-hoist "^24.9.0" + +babel-preset-react-app@^9.1.2: + version "9.1.2" + resolved "https://registry.yarnpkg.com/babel-preset-react-app/-/babel-preset-react-app-9.1.2.tgz#54775d976588a8a6d1a99201a702befecaf48030" + integrity sha512-k58RtQOKH21NyKtzptoAvtAODuAJJs3ZhqBMl456/GnXEQ/0La92pNmwgWoMn5pBTrsvk3YYXdY7zpY4e3UIxA== + dependencies: + "@babel/core" "7.9.0" + "@babel/plugin-proposal-class-properties" "7.8.3" + "@babel/plugin-proposal-decorators" "7.8.3" + "@babel/plugin-proposal-nullish-coalescing-operator" "7.8.3" + "@babel/plugin-proposal-numeric-separator" "7.8.3" + "@babel/plugin-proposal-optional-chaining" "7.9.0" + "@babel/plugin-transform-flow-strip-types" "7.9.0" + "@babel/plugin-transform-react-display-name" "7.8.3" + "@babel/plugin-transform-runtime" "7.9.0" + "@babel/preset-env" "7.9.0" + "@babel/preset-react" "7.9.1" + "@babel/preset-typescript" "7.9.0" + "@babel/runtime" "7.9.0" + babel-plugin-macros "2.8.0" + babel-plugin-transform-react-remove-prop-types "0.4.24" + +babel-runtime@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" + dependencies: + core-js "^2.4.0" + regenerator-runtime "^0.11.0" + +babylon@^6.18.0: + version "6.18.0" + resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3" + +backo2@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947" + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + +base64-arraybuffer@0.1.5: + version "0.1.5" + resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz#73926771923b5a19747ad666aa5cd4bf9c6e9ce8" + +base64-js@^1.0.2: + version "1.3.0" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.0.tgz#cab1e6118f051095e58b5281aea8c1cd22bfc0e3" + +base@^0.11.1: + version "0.11.2" + resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" + dependencies: + cache-base "^1.0.1" + class-utils "^0.3.5" + component-emitter "^1.2.1" + define-property "^1.0.0" + isobject "^3.0.1" + mixin-deep "^1.2.0" + pascalcase "^0.1.1" + +batch@0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16" + +bcrypt-pbkdf@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" + dependencies: + tweetnacl "^0.14.3" + +better-assert@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/better-assert/-/better-assert-1.0.2.tgz#40866b9e1b9e0b55b481894311e68faffaebc522" + dependencies: + callsite "1.0.0" + +big.js@^5.2.2: + version "5.2.2" + resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" + +binary-extensions@^1.0.0: + version "1.13.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.0.tgz#9523e001306a32444b907423f1de2164222f6ab1" + +binary-extensions@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.0.0.tgz#23c0df14f6a88077f5f986c0d167ec03c3d5537c" + integrity sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow== + +blob@0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.5.tgz#d680eeef25f8cd91ad533f5b01eed48e64caf683" + +block-stream@*: + version "0.0.9" + resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a" + dependencies: + inherits "~2.0.0" + +bluebird@^3.5.5: + version "3.7.2" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" + integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== + +bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: + version "4.11.8" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" + +body-parser@1.19.0: + version "1.19.0" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" + integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw== + dependencies: + bytes "3.1.0" + content-type "~1.0.4" + debug "2.6.9" + depd "~1.1.2" + http-errors "1.7.2" + iconv-lite "0.4.24" + on-finished "~2.3.0" + qs "6.7.0" + raw-body "2.4.0" + type-is "~1.6.17" + +bonjour@^3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/bonjour/-/bonjour-3.5.0.tgz#8e890a183d8ee9a2393b3844c691a42bcf7bc9f5" + dependencies: + array-flatten "^2.1.0" + deep-equal "^1.0.1" + dns-equal "^1.0.0" + dns-txt "^2.0.2" + multicast-dns "^6.0.1" + multicast-dns-service-types "^1.1.0" + +boolbase@^1.0.0, boolbase@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@^2.3.1, braces@^2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" + dependencies: + arr-flatten "^1.1.0" + array-unique "^0.3.2" + extend-shallow "^2.0.1" + fill-range "^4.0.0" + isobject "^3.0.1" + repeat-element "^1.1.2" + snapdragon "^0.8.1" + snapdragon-node "^2.0.1" + split-string "^3.0.2" + to-regex "^3.0.1" + +braces@^3.0.1, braces@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +brorand@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" + +browser-process-hrtime@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" + integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== + +browser-resolve@^1.11.3: + version "1.11.3" + resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-1.11.3.tgz#9b7cbb3d0f510e4cb86bdbd796124d28b5890af6" + integrity sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ== + dependencies: + resolve "1.1.7" + +browserify-aes@^1.0.0, browserify-aes@^1.0.4: + version "1.2.0" + resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" + dependencies: + buffer-xor "^1.0.3" + cipher-base "^1.0.0" + create-hash "^1.1.0" + evp_bytestokey "^1.0.3" + inherits "^2.0.1" + safe-buffer "^5.0.1" + +browserify-cipher@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/browserify-cipher/-/browserify-cipher-1.0.1.tgz#8d6474c1b870bfdabcd3bcfcc1934a10e94f15f0" + dependencies: + browserify-aes "^1.0.4" + browserify-des "^1.0.0" + evp_bytestokey "^1.0.0" + +browserify-des@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/browserify-des/-/browserify-des-1.0.2.tgz#3af4f1f59839403572f1c66204375f7a7f703e9c" + dependencies: + cipher-base "^1.0.1" + des.js "^1.0.0" + inherits "^2.0.1" + safe-buffer "^5.1.2" + +browserify-rsa@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.0.1.tgz#21e0abfaf6f2029cf2fafb133567a701d4135524" + dependencies: + bn.js "^4.1.0" + randombytes "^2.0.1" + +browserify-sign@^4.0.0: + version "4.0.4" + resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.0.4.tgz#aa4eb68e5d7b658baa6bf6a57e630cbd7a93d298" + dependencies: + bn.js "^4.1.1" + browserify-rsa "^4.0.0" + create-hash "^1.1.0" + create-hmac "^1.1.2" + elliptic "^6.0.0" + inherits "^2.0.1" + parse-asn1 "^5.0.0" + +browserify-zlib@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz#2869459d9aa3be245fe8fe2ca1f46e2e7f54d73f" + dependencies: + pako "~1.0.5" + +browserslist@4.10.0: + version "4.10.0" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.10.0.tgz#f179737913eaf0d2b98e4926ac1ca6a15cbcc6a9" + integrity sha512-TpfK0TDgv71dzuTsEAlQiHeWQ/tiPqgNZVdv046fvNtBZrjbv2O3TsWCDU0AWGJJKCF/KsjNdLzR9hXOsh/CfA== + dependencies: + caniuse-lite "^1.0.30001035" + electron-to-chromium "^1.3.378" + node-releases "^1.1.52" + pkg-up "^3.1.0" + +browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.6.2, browserslist@^4.6.4, browserslist@^4.8.5, browserslist@^4.9.1: + version "4.12.0" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.12.0.tgz#06c6d5715a1ede6c51fc39ff67fd647f740b656d" + integrity sha512-UH2GkcEDSI0k/lRkuDSzFl9ZZ87skSy9w2XAn1MsZnL+4c4rqbBd3e82UWHbYDpztABrPBhZsTEeuxVfHppqDg== + dependencies: + caniuse-lite "^1.0.30001043" + electron-to-chromium "^1.3.413" + node-releases "^1.1.53" + pkg-up "^2.0.0" + +bser@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/bser/-/bser-2.0.0.tgz#9ac78d3ed5d915804fd87acb158bc797147a1719" + dependencies: + node-int64 "^0.4.0" + +buffer-from@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" + +buffer-indexof@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/buffer-indexof/-/buffer-indexof-1.1.1.tgz#52fabcc6a606d1a00302802648ef68f639da268c" + +buffer-xor@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" + +buffer@^4.3.0: + version "4.9.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.1.tgz#6d1bb601b07a4efced97094132093027c95bc298" + dependencies: + base64-js "^1.0.2" + ieee754 "^1.1.4" + isarray "^1.0.0" + +builtin-status-codes@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" + +bytes@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" + +bytes@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" + integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== + +cacache@^12.0.2: + version "12.0.4" + resolved "https://registry.yarnpkg.com/cacache/-/cacache-12.0.4.tgz#668bcbd105aeb5f1d92fe25570ec9525c8faa40c" + integrity sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ== + dependencies: + bluebird "^3.5.5" + chownr "^1.1.1" + figgy-pudding "^3.5.1" + glob "^7.1.4" + graceful-fs "^4.1.15" + infer-owner "^1.0.3" + lru-cache "^5.1.1" + mississippi "^3.0.0" + mkdirp "^0.5.1" + move-concurrently "^1.0.1" + promise-inflight "^1.0.1" + rimraf "^2.6.3" + ssri "^6.0.1" + unique-filename "^1.1.1" + y18n "^4.0.0" + +cacache@^13.0.1: + version "13.0.1" + resolved "https://registry.yarnpkg.com/cacache/-/cacache-13.0.1.tgz#a8000c21697089082f85287a1aec6e382024a71c" + integrity sha512-5ZvAxd05HDDU+y9BVvcqYu2LLXmPnQ0hW62h32g4xBTgL/MppR4/04NHfj/ycM2y6lmTnbw6HVi+1eN0Psba6w== + dependencies: + chownr "^1.1.2" + figgy-pudding "^3.5.1" + fs-minipass "^2.0.0" + glob "^7.1.4" + graceful-fs "^4.2.2" + infer-owner "^1.0.4" + lru-cache "^5.1.1" + minipass "^3.0.0" + minipass-collect "^1.0.2" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.2" + mkdirp "^0.5.1" + move-concurrently "^1.0.1" + p-map "^3.0.0" + promise-inflight "^1.0.1" + rimraf "^2.7.1" + ssri "^7.0.0" + unique-filename "^1.1.1" + +cache-base@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" + dependencies: + collection-visit "^1.0.0" + component-emitter "^1.2.1" + get-value "^2.0.6" + has-value "^1.0.0" + isobject "^3.0.1" + set-value "^2.0.0" + to-object-path "^0.3.0" + union-value "^1.0.0" + unset-value "^1.0.0" + +call-me-maybe@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.1.tgz#26d208ea89e37b5cbde60250a15f031c16a4d66b" + integrity sha1-JtII6onje1y95gJQoV8DHBak1ms= + +caller-callsite@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/caller-callsite/-/caller-callsite-2.0.0.tgz#847e0fce0a223750a9a027c54b33731ad3154134" + integrity sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ= + dependencies: + callsites "^2.0.0" + +caller-path@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-2.0.0.tgz#468f83044e369ab2010fac5f06ceee15bb2cb1f4" + integrity sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ= + dependencies: + caller-callsite "^2.0.0" + +callsite@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/callsite/-/callsite-1.0.0.tgz#280398e5d664bd74038b6f0905153e6e8af1bc20" + +callsites@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +camel-case@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-4.1.1.tgz#1fc41c854f00e2f7d0139dfeba1542d6896fe547" + integrity sha512-7fa2WcG4fYFkclIvEmxBbTvmibwF2/agfEBc6q3lOpVu0A13ltLsA+Hr/8Hp6kp5f+G7hKi6t8lys6XxP+1K6Q== + dependencies: + pascal-case "^3.1.1" + tslib "^1.10.0" + +camelcase-keys@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-2.1.0.tgz#308beeaffdf28119051efa1d932213c91b8f92e7" + dependencies: + camelcase "^2.0.0" + map-obj "^1.0.0" + +camelcase@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.0.0.tgz#03295527d58bd3cd4aa75363f35b2e8d97be2f42" + integrity sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA== + +camelcase@5.3.1, camelcase@^5.0.0, camelcase@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +camelcase@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f" + +camelcase@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a" + +caniuse-api@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0" + integrity sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw== + dependencies: + browserslist "^4.0.0" + caniuse-lite "^1.0.0" + lodash.memoize "^4.1.2" + lodash.uniq "^4.5.0" + +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001035, caniuse-lite@^1.0.30001043, caniuse-lite@^1.0.30001061: + version "1.0.30001079" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001079.tgz#ed3e5225cd9a6850984fdd88bf24ce45d69b9c22" + integrity sha512-2KaYheg0iOY+CMmDuAB3DHehrXhhb4OZU4KBVGDr/YKyYAcpudaiUQ9PJ9rxrPlKEoJ3ATasQ5AN48MqpwS43Q== + +capture-exit@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/capture-exit/-/capture-exit-2.0.0.tgz#fb953bfaebeb781f62898239dabb426d08a509a4" + integrity sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g== + dependencies: + rsvp "^4.8.4" + +case-sensitive-paths-webpack-plugin@2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.3.0.tgz#23ac613cc9a856e4f88ff8bb73bbb5e989825cf7" + integrity sha512-/4YgnZS8y1UXXmC02xD5rRrBEu6T5ub+mQHLNRj0fzTRbgdBYhsNo2V5EqwgqrExjxsjtF/OpAKAMkKsxbD5XQ== + +caseless@~0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" + +chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.4.1, chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^1.1.1, chalk@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" + dependencies: + ansi-styles "^2.2.1" + escape-string-regexp "^1.0.2" + has-ansi "^2.0.0" + strip-ansi "^3.0.0" + supports-color "^2.0.0" + +chalk@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" + integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chalk@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.0.0.tgz#6e98081ed2d17faab615eb52ac66ec1fe6209e72" + integrity sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chardet@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" + integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== + +chokidar@^2.0.4, chokidar@^2.1.8: + version "2.1.8" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917" + integrity sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg== + dependencies: + anymatch "^2.0.0" + async-each "^1.0.1" + braces "^2.3.2" + glob-parent "^3.1.0" + inherits "^2.0.3" + is-binary-path "^1.0.0" + is-glob "^4.0.0" + normalize-path "^3.0.0" + path-is-absolute "^1.0.0" + readdirp "^2.2.1" + upath "^1.1.1" + optionalDependencies: + fsevents "^1.2.7" + +chokidar@^3.3.0, chokidar@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.4.0.tgz#b30611423ce376357c765b9b8f904b9fba3c0be8" + integrity sha512-aXAaho2VJtisB/1fg1+3nlLJqGOuewTzQpd/Tz0yTg2R0e4IGtshYvtjowyEumcBv2z+y4+kc75Mz7j5xJskcQ== + dependencies: + anymatch "~3.1.1" + braces "~3.0.2" + glob-parent "~5.1.0" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.4.0" + optionalDependencies: + fsevents "~2.1.2" + +chownr@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.1.tgz#54726b8b8fff4df053c42187e801fb4412df1494" + +chownr@^1.1.2: + version "1.1.4" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" + integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== + +chrome-trace-event@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz#234090ee97c7d4ad1a2c4beae27505deffc608a4" + integrity sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ== + dependencies: + tslib "^1.9.0" + +ci-info@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" + integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== + +cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + +class-utils@^0.3.5: + version "0.3.6" + resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" + dependencies: + arr-union "^3.1.0" + define-property "^0.2.5" + isobject "^3.0.0" + static-extend "^0.1.1" + +classnames@~2.2.5: + version "2.2.6" + resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce" + integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q== + +clean-css@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.2.3.tgz#507b5de7d97b48ee53d84adb0160ff6216380f78" + integrity sha512-VcMWDN54ZN/DS+g58HYL5/n4Zrqe8vHJpGA8KdgUXFU4fuP/aHNw8eld9SyEIyabIMJX/0RaY/fplOo5hYLSFA== + dependencies: + source-map "~0.6.0" + +clean-stack@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" + integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== + +cli-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" + integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== + dependencies: + restore-cursor "^3.1.0" + +cli-truncate@2.1.0, cli-truncate@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-2.1.0.tgz#c39e28bf05edcde5be3b98992a22deed5a2b93c7" + integrity sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg== + dependencies: + slice-ansi "^3.0.0" + string-width "^4.2.0" + +cli-width@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639" + +cliui@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d" + dependencies: + string-width "^1.0.1" + strip-ansi "^3.0.1" + wrap-ansi "^2.0.0" + +cliui@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-4.1.0.tgz#348422dbe82d800b3022eef4f6ac10bf2e4d1b49" + integrity sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ== + dependencies: + string-width "^2.1.1" + strip-ansi "^4.0.0" + wrap-ansi "^2.0.0" + +cliui@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" + integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA== + dependencies: + string-width "^3.1.0" + strip-ansi "^5.2.0" + wrap-ansi "^5.1.0" + +clone-deep@^0.2.4: + version "0.2.4" + resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-0.2.4.tgz#4e73dd09e9fb971cc38670c5dced9c1896481cc6" + integrity sha1-TnPdCen7lxzDhnDF3O2cGJZIHMY= + dependencies: + for-own "^0.1.3" + is-plain-object "^2.0.1" + kind-of "^3.0.2" + lazy-cache "^1.0.3" + shallow-clone "^0.1.2" + +clone-deep@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" + integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ== + dependencies: + is-plain-object "^2.0.4" + kind-of "^6.0.2" + shallow-clone "^3.0.0" + +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + +coa@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/coa/-/coa-2.0.2.tgz#43f6c21151b4ef2bf57187db0d73de229e3e7ec3" + integrity sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA== + dependencies: + "@types/q" "^1.5.1" + chalk "^2.4.1" + q "^1.1.2" + +code-point-at@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" + +collection-visit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" + dependencies: + map-visit "^1.0.0" + object-visit "^1.0.0" + +color-convert@^1.9.0, color-convert@^1.9.1: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + +color-name@^1.0.0, color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +color-string@^1.5.2: + version "1.5.3" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.3.tgz#c9bbc5f01b58b5492f3d6857459cb6590ce204cc" + integrity sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw== + dependencies: + color-name "^1.0.0" + simple-swizzle "^0.2.2" + +color@^3.0.0: + version "3.1.2" + resolved "https://registry.yarnpkg.com/color/-/color-3.1.2.tgz#68148e7f85d41ad7649c5fa8c8106f098d229e10" + integrity sha512-vXTJhHebByxZn3lDvDJYw4lR5+uB3vuoHsuYA5AKuxRVn5wzzIfQKGLBmgdVRHKTJYeK5rvJcHnrd0Li49CFpg== + dependencies: + color-convert "^1.9.1" + color-string "^1.5.2" + +combined-stream@^1.0.6, combined-stream@~1.0.6: + version "1.0.7" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.7.tgz#2d1d24317afb8abe95d6d2c0b07b57813539d828" + dependencies: + delayed-stream "~1.0.0" + +combokeys@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/combokeys/-/combokeys-3.0.1.tgz#fc8ca5c3f5f2d2b03a458544cb88b14ab5f53f86" + integrity sha512-5nAfaLZ3oO3kA+/xdoL7t197UJTz2WWidyH3BBeU6hqHtvyFERICd0y3DQFrQkJFTKBrtUDck/xCLLoFpnjaCw== + +commander@^2.11.0: + version "2.19.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a" + +commander@^2.20.0: + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + +commander@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" + integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== + +commander@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae" + integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg== + +common-tags@^1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.8.0.tgz#8e3153e542d4a39e9b10554434afaaf98956a937" + integrity sha512-6P6g0uetGpW/sdyUy/iQQCbFF0kWVMSIVSyYz7Zgjcgh8mgw8PQzDNZeyZ5DQ2gM7LBoZPHmnjz8rUthkBG5tw== + +commondir@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" + +compare-versions@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-3.6.0.tgz#1a5689913685e5a87637b8d3ffca75514ec41d62" + integrity sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA== + +component-bind@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/component-bind/-/component-bind-1.0.0.tgz#00c608ab7dcd93897c0009651b1d3a8e1e73bbd1" + +component-emitter@1.2.1, component-emitter@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" + +component-emitter@~1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" + integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== + +component-inherit@0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/component-inherit/-/component-inherit-0.0.3.tgz#645fc4adf58b72b649d5cae65135619db26ff143" + +compose-function@3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/compose-function/-/compose-function-3.0.3.tgz#9ed675f13cc54501d30950a486ff6a7ba3ab185f" + integrity sha1-ntZ18TzFRQHTCVCkhv9qe6OrGF8= + dependencies: + arity-n "^1.0.4" + +compressible@~2.0.16: + version "2.0.18" + resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba" + integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg== + dependencies: + mime-db ">= 1.43.0 < 2" + +compression@^1.7.4: + version "1.7.4" + resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f" + integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ== + dependencies: + accepts "~1.3.5" + bytes "3.0.0" + compressible "~2.0.16" + debug "2.6.9" + on-headers "~1.0.2" + safe-buffer "5.1.2" + vary "~1.1.2" + +computed-styles@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/computed-styles/-/computed-styles-1.1.2.tgz#a7e732ba145149399ade70c2f94b353dd8ad629d" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + +concat-stream@^1.5.0: + version "1.6.2" + resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" + integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== + dependencies: + buffer-from "^1.0.0" + inherits "^2.0.3" + readable-stream "^2.2.2" + typedarray "^0.0.6" + +confusing-browser-globals@^1.0.9: + version "1.0.9" + resolved "https://registry.yarnpkg.com/confusing-browser-globals/-/confusing-browser-globals-1.0.9.tgz#72bc13b483c0276801681871d4898516f8f54fdd" + integrity sha512-KbS1Y0jMtyPgIxjO7ZzMAuUpAKMt1SzCL9fsrKsX6b0zJPTaT0SiSPmewwVZg9UAO83HVIlEhZF84LIjZ0lmAw== + +connect-history-api-fallback@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz#8b32089359308d111115d81cad3fceab888f97bc" + integrity sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg== + +console-browserify@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.1.0.tgz#f0241c45730a9fc6323b206dbf38edc741d0bb10" + dependencies: + date-now "^0.1.4" + +console-control-strings@^1.0.0, console-control-strings@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" + +constants-browserify@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" + +contains-path@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a" + +content-disposition@0.5.3: + version "0.5.3" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" + integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g== + dependencies: + safe-buffer "5.1.2" + +content-type@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" + +convert-source-map@1.7.0, convert-source-map@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442" + integrity sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA== + dependencies: + safe-buffer "~5.1.1" + +convert-source-map@^0.3.3: + version "0.3.5" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-0.3.5.tgz#f1d802950af7dd2631a1febe0596550c86ab3190" + integrity sha1-8dgClQr33SYxof6+BZZVDIarMZA= + +convert-source-map@^1.4.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.6.0.tgz#51b537a8c43e0f04dec1993bffcdd504e758ac20" + dependencies: + safe-buffer "~5.1.1" + +cookie-signature@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" + +cookie@0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" + integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== + +copy-concurrently@^1.0.0: + version "1.0.5" + resolved "https://registry.yarnpkg.com/copy-concurrently/-/copy-concurrently-1.0.5.tgz#92297398cae34937fcafd6ec8139c18051f0b5e0" + integrity sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A== + dependencies: + aproba "^1.1.1" + fs-write-stream-atomic "^1.0.8" + iferr "^0.1.5" + mkdirp "^0.5.1" + rimraf "^2.5.4" + run-queue "^1.0.0" + +copy-descriptor@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" + +core-js-compat@^3.6.2: + version "3.6.5" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.6.5.tgz#2a51d9a4e25dfd6e690251aa81f99e3c05481f1c" + integrity sha512-7ItTKOhOZbznhXAQ2g/slGg1PJV5zDO/WdkTwi7UEOJmkvsE32PWvx6mKtDjiMpjnR2CNf6BAD6sSxIlv7ptng== + dependencies: + browserslist "^4.8.5" + semver "7.0.0" + +core-js-pure@^3.0.0: + version "3.6.5" + resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.6.5.tgz#c79e75f5e38dbc85a662d91eea52b8256d53b813" + integrity sha512-lacdXOimsiD0QyNf9BC/mxivNJ/ybBGJXQFKzRekp1WTHoVUWsUHEn+2T8GJAzzIhyOuXA+gOxCVN3l+5PLPUA== + +core-js@^2.4.0: + version "2.6.4" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.4.tgz#b8897c062c4d769dd30a0ac5c73976c47f92ea0d" + +core-js@^3.5.0: + version "3.6.5" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.6.5.tgz#7395dc273af37fb2e50e9bd3d9fe841285231d1a" + integrity sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA== + +core-util-is@1.0.2, core-util-is@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + +cosmiconfig@^5.0.0, cosmiconfig@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.2.1.tgz#040f726809c591e77a17c0a3626ca45b4f168b1a" + integrity sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA== + dependencies: + import-fresh "^2.0.0" + is-directory "^0.3.1" + js-yaml "^3.13.1" + parse-json "^4.0.0" + +cosmiconfig@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-6.0.0.tgz#da4fee853c52f6b1e6935f41c1a2fc50bd4a9982" + integrity sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg== + dependencies: + "@types/parse-json" "^4.0.0" + import-fresh "^3.1.0" + parse-json "^5.0.0" + path-type "^4.0.0" + yaml "^1.7.2" + +create-ecdh@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.3.tgz#c9111b6f33045c4697f144787f9254cdc77c45ff" + dependencies: + bn.js "^4.1.0" + elliptic "^6.0.0" + +create-hash@^1.1.0, create-hash@^1.1.2: + version "1.2.0" + resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" + dependencies: + cipher-base "^1.0.1" + inherits "^2.0.1" + md5.js "^1.3.4" + ripemd160 "^2.0.1" + sha.js "^2.4.0" + +create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: + version "1.1.7" + resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff" + dependencies: + cipher-base "^1.0.3" + create-hash "^1.1.0" + inherits "^2.0.1" + ripemd160 "^2.0.0" + safe-buffer "^5.0.1" + sha.js "^2.4.8" + +cross-spawn@7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.1.tgz#0ab56286e0f7c24e153d04cc2aa027e43a9a5d14" + integrity sha512-u7v4o84SwFpD32Z8IIcPZ6z1/ie24O6RU3RbtL5Y316l3KuHVPx9ItBgWQ6VlfAFnRnTtMUrsQ9MUUTuEZjogg== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +cross-spawn@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-3.0.1.tgz#1256037ecb9f0c5f79e3d6ef135e30770184b982" + dependencies: + lru-cache "^4.0.1" + which "^1.2.9" + +cross-spawn@^6.0.0, cross-spawn@^6.0.5: + version "6.0.5" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" + dependencies: + nice-try "^1.0.4" + path-key "^2.0.1" + semver "^5.5.0" + shebang-command "^1.2.0" + which "^1.2.9" + +cross-spawn@^7.0.0: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +crypto-browserify@^3.11.0: + version "3.12.0" + resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" + dependencies: + browserify-cipher "^1.0.0" + browserify-sign "^4.0.0" + create-ecdh "^4.0.0" + create-hash "^1.1.0" + create-hmac "^1.1.0" + diffie-hellman "^5.0.0" + inherits "^2.0.1" + pbkdf2 "^3.0.3" + public-encrypt "^4.0.0" + randombytes "^2.0.0" + randomfill "^1.0.3" + +css-blank-pseudo@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/css-blank-pseudo/-/css-blank-pseudo-0.1.4.tgz#dfdefd3254bf8a82027993674ccf35483bfcb3c5" + integrity sha512-LHz35Hr83dnFeipc7oqFDmsjHdljj3TQtxGGiNWSOsTLIAubSm4TEz8qCaKFpk7idaQ1GfWscF4E6mgpBysA1w== + dependencies: + postcss "^7.0.5" + +css-color-names@0.0.4, css-color-names@^0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0" + +css-declaration-sorter@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-4.0.1.tgz#c198940f63a76d7e36c1e71018b001721054cb22" + integrity sha512-BcxQSKTSEEQUftYpBVnsH4SF05NTuBokb19/sBt6asXGKZ/6VP7PLG1CBCkFDYOnhXhPh0jMhO6xZ71oYHXHBA== + dependencies: + postcss "^7.0.1" + timsort "^0.3.0" + +css-has-pseudo@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/css-has-pseudo/-/css-has-pseudo-0.10.0.tgz#3c642ab34ca242c59c41a125df9105841f6966ee" + integrity sha512-Z8hnfsZu4o/kt+AuFzeGpLVhFOGO9mluyHBaA2bA8aCGTwah5sT3WV/fTHH8UNZUytOIImuGPrl/prlb4oX4qQ== + dependencies: + postcss "^7.0.6" + postcss-selector-parser "^5.0.0-rc.4" + +css-loader@3.4.2: + version "3.4.2" + resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-3.4.2.tgz#d3fdb3358b43f233b78501c5ed7b1c6da6133202" + integrity sha512-jYq4zdZT0oS0Iykt+fqnzVLRIeiPWhka+7BqPn+oSIpWJAHak5tmB/WZrJ2a21JhCeFyNnnlroSl8c+MtVndzA== + dependencies: + camelcase "^5.3.1" + cssesc "^3.0.0" + icss-utils "^4.1.1" + loader-utils "^1.2.3" + normalize-path "^3.0.0" + postcss "^7.0.23" + postcss-modules-extract-imports "^2.0.0" + postcss-modules-local-by-default "^3.0.2" + postcss-modules-scope "^2.1.1" + postcss-modules-values "^3.0.0" + postcss-value-parser "^4.0.2" + schema-utils "^2.6.0" + +css-prefers-color-scheme@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/css-prefers-color-scheme/-/css-prefers-color-scheme-3.1.1.tgz#6f830a2714199d4f0d0d0bb8a27916ed65cff1f4" + integrity sha512-MTu6+tMs9S3EUqzmqLXEcgNRbNkkD/TGFvowpeoWJn5Vfq7FMgsmRQs9X5NXAURiOBmOxm/lLjsDNXDE6k9bhg== + dependencies: + postcss "^7.0.5" + +css-select-base-adapter@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz#3b2ff4972cc362ab88561507a95408a1432135d7" + integrity sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w== + +css-select@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858" + dependencies: + boolbase "~1.0.0" + css-what "2.1" + domutils "1.5.1" + nth-check "~1.0.1" + +css-select@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-2.1.0.tgz#6a34653356635934a81baca68d0255432105dbef" + integrity sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ== + dependencies: + boolbase "^1.0.0" + css-what "^3.2.1" + domutils "^1.7.0" + nth-check "^1.0.2" + +css-tree@1.0.0-alpha.37: + version "1.0.0-alpha.37" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.37.tgz#98bebd62c4c1d9f960ec340cf9f7522e30709a22" + integrity sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg== + dependencies: + mdn-data "2.0.4" + source-map "^0.6.1" + +css-tree@1.0.0-alpha.39: + version "1.0.0-alpha.39" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.39.tgz#2bff3ffe1bb3f776cf7eefd91ee5cba77a149eeb" + integrity sha512-7UvkEYgBAHRG9Nt980lYxjsTrCyHFN53ky3wVsDkiMdVqylqRt+Zc+jm5qw7/qyOvN2dHSYtX0e4MbCCExSvnA== + dependencies: + mdn-data "2.0.6" + source-map "^0.6.1" + +css-what@2.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.2.tgz#c0876d9d0480927d7d4920dcd72af3595649554d" + +css-what@^3.2.1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-3.3.0.tgz#10fec696a9ece2e591ac772d759aacabac38cd39" + integrity sha512-pv9JPyatiPaQ6pf4OvD/dbfm0o5LviWmwxNWzblYf/1u9QZd0ihV+PMwy5jdQWQ3349kZmKEx9WXuSka2dM4cg== + +css@^2.0.0: + version "2.2.4" + resolved "https://registry.yarnpkg.com/css/-/css-2.2.4.tgz#c646755c73971f2bba6a601e2cf2fd71b1298929" + integrity sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw== + dependencies: + inherits "^2.0.3" + source-map "^0.6.1" + source-map-resolve "^0.5.2" + urix "^0.1.0" + +cssdb@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/cssdb/-/cssdb-4.4.0.tgz#3bf2f2a68c10f5c6a08abd92378331ee803cddb0" + integrity sha512-LsTAR1JPEM9TpGhl/0p3nQecC2LJ0kD8X5YARu1hk/9I1gril5vDtMZyNxcEpxxDj34YNck/ucjuoUd66K03oQ== + +cssesc@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-2.0.0.tgz#3b13bd1bb1cb36e1bcb5a4dcd27f54c5dcb35703" + integrity sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg== + +cssesc@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" + integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== + +cssnano-preset-default@^4.0.7: + version "4.0.7" + resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-4.0.7.tgz#51ec662ccfca0f88b396dcd9679cdb931be17f76" + integrity sha512-x0YHHx2h6p0fCl1zY9L9roD7rnlltugGu7zXSKQx6k2rYw0Hi3IqxcoAGF7u9Q5w1nt7vK0ulxV8Lo+EvllGsA== + dependencies: + css-declaration-sorter "^4.0.1" + cssnano-util-raw-cache "^4.0.1" + postcss "^7.0.0" + postcss-calc "^7.0.1" + postcss-colormin "^4.0.3" + postcss-convert-values "^4.0.1" + postcss-discard-comments "^4.0.2" + postcss-discard-duplicates "^4.0.2" + postcss-discard-empty "^4.0.1" + postcss-discard-overridden "^4.0.1" + postcss-merge-longhand "^4.0.11" + postcss-merge-rules "^4.0.3" + postcss-minify-font-values "^4.0.2" + postcss-minify-gradients "^4.0.2" + postcss-minify-params "^4.0.2" + postcss-minify-selectors "^4.0.2" + postcss-normalize-charset "^4.0.1" + postcss-normalize-display-values "^4.0.2" + postcss-normalize-positions "^4.0.2" + postcss-normalize-repeat-style "^4.0.2" + postcss-normalize-string "^4.0.2" + postcss-normalize-timing-functions "^4.0.2" + postcss-normalize-unicode "^4.0.1" + postcss-normalize-url "^4.0.1" + postcss-normalize-whitespace "^4.0.2" + postcss-ordered-values "^4.1.2" + postcss-reduce-initial "^4.0.3" + postcss-reduce-transforms "^4.0.2" + postcss-svgo "^4.0.2" + postcss-unique-selectors "^4.0.1" + +cssnano-util-get-arguments@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/cssnano-util-get-arguments/-/cssnano-util-get-arguments-4.0.0.tgz#ed3a08299f21d75741b20f3b81f194ed49cc150f" + integrity sha1-7ToIKZ8h11dBsg87gfGU7UnMFQ8= + +cssnano-util-get-match@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/cssnano-util-get-match/-/cssnano-util-get-match-4.0.0.tgz#c0e4ca07f5386bb17ec5e52250b4f5961365156d" + integrity sha1-wOTKB/U4a7F+xeUiULT1lhNlFW0= + +cssnano-util-raw-cache@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/cssnano-util-raw-cache/-/cssnano-util-raw-cache-4.0.1.tgz#b26d5fd5f72a11dfe7a7846fb4c67260f96bf282" + integrity sha512-qLuYtWK2b2Dy55I8ZX3ky1Z16WYsx544Q0UWViebptpwn/xDBmog2TLg4f+DBMg1rJ6JDWtn96WHbOKDWt1WQA== + dependencies: + postcss "^7.0.0" + +cssnano-util-same-parent@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/cssnano-util-same-parent/-/cssnano-util-same-parent-4.0.1.tgz#574082fb2859d2db433855835d9a8456ea18bbf3" + integrity sha512-WcKx5OY+KoSIAxBW6UBBRay1U6vkYheCdjyVNDm85zt5K9mHoGOfsOsqIszfAqrQQFIIKgjh2+FDgIj/zsl21Q== + +cssnano@^4.1.10: + version "4.1.10" + resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-4.1.10.tgz#0ac41f0b13d13d465487e111b778d42da631b8b2" + integrity sha512-5wny+F6H4/8RgNlaqab4ktc3e0/blKutmq8yNlBFXA//nSFFAqAngjNVRzUvCgYROULmZZUoosL/KSoZo5aUaQ== + dependencies: + cosmiconfig "^5.0.0" + cssnano-preset-default "^4.0.7" + is-resolvable "^1.0.0" + postcss "^7.0.0" + +csso@^4.0.2: + version "4.0.3" + resolved "https://registry.yarnpkg.com/csso/-/csso-4.0.3.tgz#0d9985dc852c7cc2b2cacfbbe1079014d1a8e903" + integrity sha512-NL3spysxUkcrOgnpsT4Xdl2aiEiBG6bXswAABQVHcMrfjjBisFOKwLDOmf4wf32aPdcJws1zds2B0Rg+jqMyHQ== + dependencies: + css-tree "1.0.0-alpha.39" + +cssom@0.3.x, "cssom@>= 0.3.2 < 0.4.0": + version "0.3.6" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.6.tgz#f85206cee04efa841f3c5982a74ba96ab20d65ad" + +cssom@^0.3.4: + version "0.3.8" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" + integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== + +cssstyle@^1.0.0, cssstyle@^1.1.1: + version "1.4.0" + resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-1.4.0.tgz#9d31328229d3c565c61e586b02041a28fccdccf1" + integrity sha512-GBrLZYZ4X4x6/QEoBnIrqb8B/f5l4+8me2dkom/j1Gtbxy0kBv6OGzKuAsGM75bkGwGAFkt56Iwg28S3XTZgSA== + dependencies: + cssom "0.3.x" + +csstype@^2.2.0: + version "2.6.2" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.2.tgz#3043d5e065454579afc7478a18de41909c8a2f01" + +currently-unhandled@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" + dependencies: + array-find-index "^1.0.1" + +cyclist@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9" + integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk= + +d3-array@^1.2.0: + version "1.2.4" + resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-1.2.4.tgz#635ce4d5eea759f6f605863dbcfc30edc737f71f" + +d3-collection@1: + version "1.0.7" + resolved "https://registry.yarnpkg.com/d3-collection/-/d3-collection-1.0.7.tgz#349bd2aa9977db071091c13144d5e4f16b5b310e" + +d3-color@1: + version "1.2.3" + resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-1.2.3.tgz#6c67bb2af6df3cc8d79efcc4d3a3e83e28c8048f" + +d3-ease@^1.0.0: + version "1.0.5" + resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-1.0.5.tgz#8ce59276d81241b1b72042d6af2d40e76d936ffb" + +d3-format@1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-1.3.2.tgz#6a96b5e31bcb98122a30863f7d92365c00603562" + +d3-interpolate@1, d3-interpolate@^1.1.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-1.3.2.tgz#417d3ebdeb4bc4efcc8fd4361c55e4040211fd68" + dependencies: + d3-color "1" + +d3-path@1: + version "1.0.7" + resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-1.0.7.tgz#8de7cd693a75ac0b5480d3abaccd94793e58aae8" + +d3-scale@^1.0.0: + version "1.0.7" + resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-1.0.7.tgz#fa90324b3ea8a776422bd0472afab0b252a0945d" + dependencies: + d3-array "^1.2.0" + d3-collection "1" + d3-color "1" + d3-format "1" + d3-interpolate "1" + d3-time "1" + d3-time-format "2" + +d3-shape@^1.0.0, d3-shape@^1.2.0: + version "1.3.4" + resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-1.3.4.tgz#358e76014645321eecc7c364e188f8ae3d2a07d4" + dependencies: + d3-path "1" + +d3-time-format@2: + version "2.1.3" + resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-2.1.3.tgz#ae06f8e0126a9d60d6364eac5b1533ae1bac826b" + dependencies: + d3-time "1" + +d3-time@1: + version "1.0.11" + resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-1.0.11.tgz#1d831a3e25cd189eb256c17770a666368762bbce" + +d3-timer@^1.0.0: + version "1.0.9" + resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-1.0.9.tgz#f7bb8c0d597d792ff7131e1c24a36dd471a471ba" + +d3-voronoi@^1.1.2: + version "1.1.4" + resolved "https://registry.yarnpkg.com/d3-voronoi/-/d3-voronoi-1.1.4.tgz#dd3c78d7653d2bb359284ae478645d95944c8297" + +d@1: + version "1.0.0" + resolved "https://registry.yarnpkg.com/d/-/d-1.0.0.tgz#754bb5bfe55451da69a58b94d45f4c5b0462d58f" + dependencies: + es5-ext "^0.10.9" + +damerau-levenshtein@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.6.tgz#143c1641cb3d85c60c32329e26899adea8701791" + integrity sha512-JVrozIeElnj3QzfUIt8tB8YMluBJom4Vw9qTPpjGYQ9fYlB3D/rb6OordUxf3xeFB35LKWs0xqcO5U6ySvBtug== + +dashdash@^1.12.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" + dependencies: + assert-plus "^1.0.0" + +data-urls@^1.0.0, data-urls@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-1.1.0.tgz#15ee0582baa5e22bb59c77140da8f9c76963bbfe" + integrity sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ== + dependencies: + abab "^2.0.0" + whatwg-mimetype "^2.2.0" + whatwg-url "^7.0.0" + +date-now@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b" + +debug@2.6.9, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.9: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + dependencies: + ms "2.0.0" + +debug@=3.1.0, debug@~3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" + dependencies: + ms "2.0.0" + +debug@^3.1.1, debug@^3.2.5: + version "3.2.6" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" + integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== + dependencies: + ms "^2.1.1" + +debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@~4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" + integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== + dependencies: + ms "^2.1.1" + +decamelize@^1.1.1, decamelize@^1.1.2, decamelize@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + +decode-uri-component@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" + +dedent@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" + +deep-diff@^0.3.5: + version "0.3.8" + resolved "https://registry.yarnpkg.com/deep-diff/-/deep-diff-0.3.8.tgz#c01de63efb0eec9798801d40c7e0dae25b582c84" + +deep-equal@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5" + +deep-extend@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + +deep-is@~0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" + +default-gateway@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-4.2.0.tgz#167104c7500c2115f6dd69b0a536bb8ed720552b" + integrity sha512-h6sMrVB1VMWVrW13mSc6ia/DwYYw5MN6+exNu1OaJeFac5aSAvwM7lZ0NVfTABuSkQelr4h5oebg3KB1XPdjgA== + dependencies: + execa "^1.0.0" + ip-regex "^2.1.0" + +define-properties@^1.1.2, define-properties@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" + dependencies: + object-keys "^1.0.12" + +define-property@^0.2.5: + version "0.2.5" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" + dependencies: + is-descriptor "^0.1.0" + +define-property@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" + dependencies: + is-descriptor "^1.0.0" + +define-property@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" + dependencies: + is-descriptor "^1.0.2" + isobject "^3.0.1" + +del@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/del/-/del-4.1.1.tgz#9e8f117222ea44a31ff3a156c049b99052a9f0b4" + integrity sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ== + dependencies: + "@types/glob" "^7.1.1" + globby "^6.1.0" + is-path-cwd "^2.0.0" + is-path-in-cwd "^2.0.0" + p-map "^2.0.0" + pify "^4.0.1" + rimraf "^2.6.3" + +delaunator@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/delaunator/-/delaunator-4.0.1.tgz#3d779687f57919a7a418f8ab947d3bddb6846957" + integrity sha512-WNPWi1IRKZfCt/qIDMfERkDp93+iZEmOxN2yy4Jg+Xhv8SLk2UTqqbe1sfiipn0and9QrE914/ihdx82Y/Giag== + +delaunay-find@0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/delaunay-find/-/delaunay-find-0.0.5.tgz#5fb37e6509da934881b4b16c08898ac89862c097" + integrity sha512-7yAJ/wmKWj3SgqjtkGqT/RCwI0HWAo5YnHMoF5nYXD8cdci+YSo23iPmgrZUNOpDxRWN91PqxUvMMr2lKpjr+w== + dependencies: + delaunator "^4.0.0" + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + +delegates@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" + +depd@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" + +des.js@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.0.tgz#c074d2e2aa6a8a9a07dbd61f9a15c2cd83ec8ecc" + dependencies: + inherits "^2.0.1" + minimalistic-assert "^1.0.0" + +destroy@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" + +detect-libc@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" + +detect-newline@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-2.1.0.tgz#f41f1c10be4b00e87b5f13da680759f2c5bfd3e2" + integrity sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I= + +detect-node@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c" + integrity sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw== + +detect-port-alt@1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/detect-port-alt/-/detect-port-alt-1.1.6.tgz#24707deabe932d4a3cf621302027c2b266568275" + dependencies: + address "^1.0.1" + debug "^2.6.0" + +diff-sequences@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.9.0.tgz#5715d6244e2aa65f48bba0bc972db0b0b11e95b5" + integrity sha512-Dj6Wk3tWyTE+Fo1rW8v0Xhwk80um6yFYKbuAxc9c3EZxIHFDYwbi34Uk42u1CdnIiVorvt4RmlSDjIPyzGC2ew== + +diffie-hellman@^5.0.0: + version "5.0.3" + resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" + dependencies: + bn.js "^4.1.0" + miller-rabin "^4.0.0" + randombytes "^2.0.0" + +dir-glob@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-2.0.0.tgz#0b205d2b6aef98238ca286598a8204d29d0a0034" + integrity sha512-37qirFDz8cA5fimp9feo43fSuRo2gHwaIn6dXL8Ber1dGwUosDrGZeCCXq57WnIqE4aQ+u3eQZzsk1yOzhdwag== + dependencies: + arrify "^1.0.1" + path-type "^3.0.0" + +dns-equal@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d" + +dns-packet@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-1.3.1.tgz#12aa426981075be500b910eedcd0b47dd7deda5a" + dependencies: + ip "^1.1.0" + safe-buffer "^5.0.1" + +dns-txt@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/dns-txt/-/dns-txt-2.0.2.tgz#b91d806f5d27188e4ab3e7d107d881a1cc4642b6" + dependencies: + buffer-indexof "^1.0.0" + +doctrine@1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa" + dependencies: + esutils "^2.0.2" + isarray "^1.0.0" + +doctrine@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" + integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== + dependencies: + esutils "^2.0.2" + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +dom-converter@~0.2: + version "0.2.0" + resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768" + dependencies: + utila "~0.4" + +dom-serializer@0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82" + dependencies: + domelementtype "~1.1.1" + entities "~1.1.1" + +domain-browser@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" + +domelementtype@1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" + +domelementtype@~1.1.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.1.3.tgz#bd28773e2642881aec51544924299c5cd822185b" + +domexception@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/domexception/-/domexception-1.0.1.tgz#937442644ca6a31261ef36e3ec677fe805582c90" + integrity sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug== + dependencies: + webidl-conversions "^4.0.2" + +domhandler@2.1: + version "2.1.0" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.1.0.tgz#d2646f5e57f6c3bab11cf6cb05d3c0acf7412594" + dependencies: + domelementtype "1" + +domutils@1.1: + version "1.1.6" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.1.6.tgz#bddc3de099b9a2efacc51c623f28f416ecc57485" + dependencies: + domelementtype "1" + +domutils@1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf" + dependencies: + dom-serializer "0" + domelementtype "1" + +domutils@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a" + integrity sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg== + dependencies: + dom-serializer "0" + domelementtype "1" + +dot-case@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.3.tgz#21d3b52efaaba2ea5fda875bb1aa8124521cf4aa" + integrity sha512-7hwEmg6RiSQfm/GwPL4AAWXKy3YNNZA3oFv2Pdiey0mwkRCPZ9x6SZbkLcn8Ma5PYeVokzoD4Twv2n7LKp5WeA== + dependencies: + no-case "^3.0.3" + tslib "^1.10.0" + +dot-prop@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.2.0.tgz#c34ecc29556dc45f1f4c22697b6f4904e0cc4fcb" + integrity sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A== + dependencies: + is-obj "^2.0.0" + +dotenv-expand@5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0" + integrity sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA== + +dotenv@8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a" + integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw== + +duplexer@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1" + +duplexify@^3.4.2, duplexify@^3.6.0: + version "3.7.1" + resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309" + integrity sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g== + dependencies: + end-of-stream "^1.0.0" + inherits "^2.0.1" + readable-stream "^2.0.0" + stream-shift "^1.0.0" + +ecc-jsbn@~0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" + dependencies: + jsbn "~0.1.0" + safer-buffer "^2.1.0" + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + +electron-to-chromium@^1.3.378, electron-to-chromium@^1.3.413: + version "1.3.464" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.464.tgz#fe13feaa08f6f865d3c89d5d72e54c194f463aa5" + integrity sha512-Oo+0+CN9d2z6FToQW6Hwvi9ez09Y/usKwr0tsDsyg43a871zVJCi1nR0v03djLbRNcaCKjtrnVf2XJhTxEpPCg== + +elliptic@^6.0.0: + version "6.4.1" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.4.1.tgz#c2d0b7776911b86722c632c3c06c60f2f819939a" + dependencies: + bn.js "^4.4.0" + brorand "^1.0.1" + hash.js "^1.0.0" + hmac-drbg "^1.0.0" + inherits "^2.0.1" + minimalistic-assert "^1.0.0" + minimalistic-crypto-utils "^1.0.0" + +emoji-regex@^7.0.1, emoji-regex@^7.0.2: + version "7.0.3" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" + integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +emojis-list@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389" + +emojis-list@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" + integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== + +encodeurl@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + +end-of-stream@^1.0.0, end-of-stream@^1.1.0: + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + +engine.io-client@~3.4.0: + version "3.4.3" + resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-3.4.3.tgz#192d09865403e3097e3575ebfeb3861c4d01a66c" + integrity sha512-0NGY+9hioejTEJCaSJZfWZLk4FPI9dN+1H1C4+wj2iuFba47UgZbJzfWs4aNFajnX/qAaYKbe2lLTfEEWzCmcw== + dependencies: + component-emitter "~1.3.0" + component-inherit "0.0.3" + debug "~4.1.0" + engine.io-parser "~2.2.0" + has-cors "1.1.0" + indexof "0.0.1" + parseqs "0.0.5" + parseuri "0.0.5" + ws "~6.1.0" + xmlhttprequest-ssl "~1.5.4" + yeast "0.1.2" + +engine.io-parser@~2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-2.2.0.tgz#312c4894f57d52a02b420868da7b5c1c84af80ed" + integrity sha512-6I3qD9iUxotsC5HEMuuGsKA0cXerGz+4uGcXQEkfBidgKf0amsjrrtwcbwK/nzpZBxclXlV7gGl9dgWvu4LF6w== + dependencies: + after "0.8.2" + arraybuffer.slice "~0.0.7" + base64-arraybuffer "0.1.5" + blob "0.0.5" + has-binary2 "~1.0.2" + +enhanced-resolve@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.1.1.tgz#2937e2b8066cd0fe7ce0990a98f0d71a35189f66" + integrity sha512-98p2zE+rL7/g/DzMHMTF4zZlCgeVdJ7yr6xzEpJRYwFYrGi9ANdn5DnJURg6RpBkyk60XYDnWIv51VfIhfNGuA== + dependencies: + graceful-fs "^4.1.2" + memory-fs "^0.5.0" + tapable "^1.0.0" + +enquirer@^2.3.5: + version "2.3.5" + resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.5.tgz#3ab2b838df0a9d8ab9e7dff235b0e8712ef92381" + integrity sha512-BNT1C08P9XD0vNg3J475yIUG+mVdp9T6towYFHUv897X0KoHBjB1shyrNmhmtHWKP17iSWgo7Gqh7BBuzLZMSA== + dependencies: + ansi-colors "^3.2.1" + +entities@~1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" + +errno@^0.1.3, errno@~0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618" + dependencies: + prr "~1.0.1" + +error-ex@^1.2.0, error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + dependencies: + is-arrayish "^0.2.1" + +es-abstract@^1.17.0, es-abstract@^1.17.0-next.1, es-abstract@^1.17.2, es-abstract@^1.17.5: + version "1.17.5" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.5.tgz#d8c9d1d66c8981fb9200e2251d799eee92774ae9" + integrity sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg== + dependencies: + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + is-callable "^1.1.5" + is-regex "^1.0.5" + object-inspect "^1.7.0" + object-keys "^1.1.1" + object.assign "^4.1.0" + string.prototype.trimleft "^2.1.1" + string.prototype.trimright "^2.1.1" + +es-abstract@^1.4.3, es-abstract@^1.7.0: + version "1.13.0" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.13.0.tgz#ac86145fdd5099d8dd49558ccba2eaf9b88e24e9" + dependencies: + es-to-primitive "^1.2.0" + function-bind "^1.1.1" + has "^1.0.3" + is-callable "^1.1.4" + is-regex "^1.0.4" + object-keys "^1.0.12" + +es-to-primitive@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.0.tgz#edf72478033456e8dda8ef09e00ad9650707f377" + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + +es-to-primitive@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" + integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + +es5-ext@^0.10.35, es5-ext@^0.10.9, es5-ext@~0.10.14: + version "0.10.47" + resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.47.tgz#d24232e1380daad5449a817be19bde9729024a11" + dependencies: + es6-iterator "~2.0.3" + es6-symbol "~3.1.1" + next-tick "1" + +es6-iterator@2.0.3, es6-iterator@~2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" + integrity sha1-p96IkUGgWpSwhUQDstCg+/qY87c= + dependencies: + d "1" + es5-ext "^0.10.35" + es6-symbol "^3.1.1" + +es6-symbol@^3.1.1, es6-symbol@~3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.1.tgz#bf00ef4fdab6ba1b46ecb7b629b4c7ed5715cc77" + dependencies: + d "1" + es5-ext "~0.10.14" + +escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + +escape-string-regexp@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" + integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== + +escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + +escodegen@^1.11.0, escodegen@^1.9.1: + version "1.14.2" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.2.tgz#14ab71bf5026c2aa08173afba22c6f3173284a84" + integrity sha512-InuOIiKk8wwuOFg6x9BQXbzjrQhtyXh46K9bqVTPzSo2FnyMBaYGBMC6PhQy7yxxil9vIedFBweQBMK74/7o8A== + dependencies: + esprima "^4.0.1" + estraverse "^4.2.0" + esutils "^2.0.2" + optionator "^0.8.1" + optionalDependencies: + source-map "~0.6.1" + +eslint-config-react-app@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/eslint-config-react-app/-/eslint-config-react-app-5.2.1.tgz#698bf7aeee27f0cea0139eaef261c7bf7dd623df" + integrity sha512-pGIZ8t0mFLcV+6ZirRgYK6RVqUIKRIi9MmgzUEmrIknsn3AdO0I32asO86dJgloHq+9ZPl8UIg8mYrvgP5u2wQ== + dependencies: + confusing-browser-globals "^1.0.9" + +eslint-import-resolver-node@^0.3.2: + version "0.3.3" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.3.tgz#dbaa52b6b2816b50bc6711af75422de808e98404" + integrity sha512-b8crLDo0M5RSe5YG8Pu2DYBj71tSB6OvXkfzwbJU2w7y8P4/yo0MyF8jU26IEuEuHF2K5/gcAJE3LhQGqBBbVg== + dependencies: + debug "^2.6.9" + resolve "^1.13.1" + +eslint-loader@3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/eslint-loader/-/eslint-loader-3.0.3.tgz#e018e3d2722381d982b1201adb56819c73b480ca" + integrity sha512-+YRqB95PnNvxNp1HEjQmvf9KNvCin5HXYYseOXVC2U0KEcw4IkQ2IQEBG46j7+gW39bMzeu0GsUhVbBY3Votpw== + dependencies: + fs-extra "^8.1.0" + loader-fs-cache "^1.0.2" + loader-utils "^1.2.3" + object-hash "^2.0.1" + schema-utils "^2.6.1" + +eslint-module-utils@^2.4.1: + version "2.6.0" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.6.0.tgz#579ebd094f56af7797d19c9866c9c9486629bfa6" + integrity sha512-6j9xxegbqe8/kZY8cYpcp0xhbK0EgJlg3g9mib3/miLaExuuwc3n5UEfSnU6hWMbT0FAYVvDbL9RrRgpUeQIvA== + dependencies: + debug "^2.6.9" + pkg-dir "^2.0.0" + +eslint-plugin-flowtype@4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-flowtype/-/eslint-plugin-flowtype-4.6.0.tgz#82b2bd6f21770e0e5deede0228e456cb35308451" + integrity sha512-W5hLjpFfZyZsXfo5anlu7HM970JBDqbEshAJUkeczP6BFCIfJXuiIBQXyberLRtOStT0OGPF8efeTbxlHk4LpQ== + dependencies: + lodash "^4.17.15" + +eslint-plugin-import@2.20.1: + version "2.20.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.20.1.tgz#802423196dcb11d9ce8435a5fc02a6d3b46939b3" + integrity sha512-qQHgFOTjguR+LnYRoToeZWT62XM55MBVXObHM6SKFd1VzDcX/vqT1kAz8ssqigh5eMj8qXcRoXXGZpPP6RfdCw== + dependencies: + array-includes "^3.0.3" + array.prototype.flat "^1.2.1" + contains-path "^0.1.0" + debug "^2.6.9" + doctrine "1.5.0" + eslint-import-resolver-node "^0.3.2" + eslint-module-utils "^2.4.1" + has "^1.0.3" + minimatch "^3.0.4" + object.values "^1.1.0" + read-pkg-up "^2.0.0" + resolve "^1.12.0" + +eslint-plugin-jsx-a11y@6.2.3: + version "6.2.3" + resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.2.3.tgz#b872a09d5de51af70a97db1eea7dc933043708aa" + integrity sha512-CawzfGt9w83tyuVekn0GDPU9ytYtxyxyFZ3aSWROmnRRFQFT2BiPJd7jvRdzNDi6oLWaS2asMeYSNMjWTV4eNg== + dependencies: + "@babel/runtime" "^7.4.5" + aria-query "^3.0.0" + array-includes "^3.0.3" + ast-types-flow "^0.0.7" + axobject-query "^2.0.2" + damerau-levenshtein "^1.0.4" + emoji-regex "^7.0.2" + has "^1.0.3" + jsx-ast-utils "^2.2.1" + +eslint-plugin-react-hooks@^1.6.1: + version "1.7.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-1.7.0.tgz#6210b6d5a37205f0b92858f895a4e827020a7d04" + integrity sha512-iXTCFcOmlWvw4+TOE8CLWj6yX1GwzT0Y6cUfHHZqWnSk144VmVIRcVGtUAzrLES7C798lmvnt02C7rxaOX1HNA== + +eslint-plugin-react@7.19.0: + version "7.19.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.19.0.tgz#6d08f9673628aa69c5559d33489e855d83551666" + integrity sha512-SPT8j72CGuAP+JFbT0sJHOB80TX/pu44gQ4vXH/cq+hQTiY2PuZ6IHkqXJV6x1b28GDdo1lbInjKUrrdUf0LOQ== + dependencies: + array-includes "^3.1.1" + doctrine "^2.1.0" + has "^1.0.3" + jsx-ast-utils "^2.2.3" + object.entries "^1.1.1" + object.fromentries "^2.0.2" + object.values "^1.1.1" + prop-types "^15.7.2" + resolve "^1.15.1" + semver "^6.3.0" + string.prototype.matchall "^4.0.2" + xregexp "^4.3.0" + +eslint-scope@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848" + integrity sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg== + dependencies: + esrecurse "^4.1.0" + estraverse "^4.1.1" + +eslint-scope@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.0.tgz#d0f971dfe59c69e0cada684b23d49dbf82600ce5" + integrity sha512-iiGRvtxWqgtx5m8EyQUJihBloE4EnYeGE/bz1wSPwJE6tZuJUtHlhqDM4Xj2ukE8Dyy1+HCZ4hE0fzIVMzb58w== + dependencies: + esrecurse "^4.1.0" + estraverse "^4.1.1" + +eslint-utils@^1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.3.tgz#74fec7c54d0776b6f67e0251040b5806564e981f" + integrity sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q== + dependencies: + eslint-visitor-keys "^1.1.0" + +eslint-utils@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.0.0.tgz#7be1cc70f27a72a76cd14aa698bcabed6890e1cd" + integrity sha512-0HCPuJv+7Wv1bACm8y5/ECVfYdfsAm9xmVb7saeFlxjPYALefjhbYoCkBjPdPzGH8wWyTpAez82Fh3VKYEZ8OA== + dependencies: + eslint-visitor-keys "^1.1.0" + +eslint-visitor-keys@^1.0.0, eslint-visitor-keys@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.2.0.tgz#74415ac884874495f78ec2a97349525344c981fa" + integrity sha512-WFb4ihckKil6hu3Dp798xdzSfddwKKU3+nGniKF6HfeW6OLd2OUDEPP7TcHtB5+QXOKg2s6B2DaMPE1Nn/kxKQ== + +eslint@^6.6.0: + version "6.8.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.8.0.tgz#62262d6729739f9275723824302fb227c8c93ffb" + integrity sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig== + dependencies: + "@babel/code-frame" "^7.0.0" + ajv "^6.10.0" + chalk "^2.1.0" + cross-spawn "^6.0.5" + debug "^4.0.1" + doctrine "^3.0.0" + eslint-scope "^5.0.0" + eslint-utils "^1.4.3" + eslint-visitor-keys "^1.1.0" + espree "^6.1.2" + esquery "^1.0.1" + esutils "^2.0.2" + file-entry-cache "^5.0.1" + functional-red-black-tree "^1.0.1" + glob-parent "^5.0.0" + globals "^12.1.0" + ignore "^4.0.6" + import-fresh "^3.0.0" + imurmurhash "^0.1.4" + inquirer "^7.0.0" + is-glob "^4.0.0" + js-yaml "^3.13.1" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.3.0" + lodash "^4.17.14" + minimatch "^3.0.4" + mkdirp "^0.5.1" + natural-compare "^1.4.0" + optionator "^0.8.3" + progress "^2.0.0" + regexpp "^2.0.1" + semver "^6.1.2" + strip-ansi "^5.2.0" + strip-json-comments "^3.0.1" + table "^5.2.3" + text-table "^0.2.0" + v8-compile-cache "^2.0.3" + +espree@^6.1.2: + version "6.2.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-6.2.1.tgz#77fc72e1fd744a2052c20f38a5b575832e82734a" + integrity sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw== + dependencies: + acorn "^7.1.1" + acorn-jsx "^5.2.0" + eslint-visitor-keys "^1.1.0" + +esprima@^4.0.0, esprima@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + +esquery@^1.0.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.3.1.tgz#b78b5828aa8e214e29fb74c4d5b752e1c033da57" + integrity sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.1.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.1.tgz#007a3b9fdbc2b3bb87e4879ea19c92fdbd3942cf" + dependencies: + estraverse "^4.1.0" + +estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13" + +estraverse@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.1.0.tgz#374309d39fd935ae500e7b92e8a6b4c720e59642" + integrity sha512-FyohXK+R0vE+y1nHLoBM7ZTyqRpqAlhdZHCWIWEviFLiGB8b04H6bQs8G+XTthacvT8VuwvteiP7RJSxMs8UEw== + +esutils@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" + +etag@~1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + +eventemitter3@^4.0.0: + version "4.0.4" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.4.tgz#b5463ace635a083d018bdc7c917b4c5f10a85384" + integrity sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ== + +events@^1.0.2: + version "1.1.1" + resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" + +events@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.0.0.tgz#9a0a0dfaf62893d92b875b8f2698ca4114973e88" + +eventsource@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-1.0.7.tgz#8fbc72c93fcd34088090bc0a4e64f4b5cee6d8d0" + integrity sha512-4Ln17+vVT0k8aWq+t/bF5arcS3EpT9gYtW66EPacdj/mAFevznsnyoHLPy2BA8gbIQeIHoPsvwmfBftfcG//BQ== + dependencies: + original "^1.0.0" + +evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" + dependencies: + md5.js "^1.3.4" + safe-buffer "^5.1.1" + +exec-sh@^0.3.2: + version "0.3.4" + resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.3.4.tgz#3a018ceb526cc6f6df2bb504b2bfe8e3a4934ec5" + integrity sha512-sEFIkc61v75sWeOe72qyrqg2Qg0OuLESziUDk/O/z2qgS15y2gWVFrI6f2Qn/qw/0/NCfCEsmNA4zOjkwEZT1A== + +execa@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" + integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA== + dependencies: + cross-spawn "^6.0.0" + get-stream "^4.0.0" + is-stream "^1.1.0" + npm-run-path "^2.0.0" + p-finally "^1.0.0" + signal-exit "^3.0.0" + strip-eof "^1.0.0" + +execa@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/execa/-/execa-4.0.2.tgz#ad87fb7b2d9d564f70d2b62d511bee41d5cbb240" + integrity sha512-QI2zLa6CjGWdiQsmSkZoGtDx2N+cQIGb3yNolGTdjSQzydzLgYYf8LRuagp7S7fPimjcrzUDSUFd/MgzELMi4Q== + dependencies: + cross-spawn "^7.0.0" + get-stream "^5.0.0" + human-signals "^1.1.1" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.0" + onetime "^5.1.0" + signal-exit "^3.0.2" + strip-final-newline "^2.0.0" + +exenv@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/exenv/-/exenv-1.2.2.tgz#2ae78e85d9894158670b03d47bec1f03bd91bb9d" + +exit@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" + integrity sha1-BjJjj42HfMghB9MKD/8aF8uhzQw= + +expand-brackets@^2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" + dependencies: + debug "^2.3.3" + define-property "^0.2.5" + extend-shallow "^2.0.1" + posix-character-classes "^0.1.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +expect@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/expect/-/expect-24.9.0.tgz#b75165b4817074fa4a157794f46fe9f1ba15b6ca" + integrity sha512-wvVAx8XIol3Z5m9zvZXiyZOQ+sRJqNTIm6sGjdWlaZIeupQGO3WbYI+15D/AmEwZywL6wtJkbAbJtzkOfBuR0Q== + dependencies: + "@jest/types" "^24.9.0" + ansi-styles "^3.2.0" + jest-get-type "^24.9.0" + jest-matcher-utils "^24.9.0" + jest-message-util "^24.9.0" + jest-regex-util "^24.9.0" + +express@^4.17.1: + version "4.17.1" + resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" + integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g== + dependencies: + accepts "~1.3.7" + array-flatten "1.1.1" + body-parser "1.19.0" + content-disposition "0.5.3" + content-type "~1.0.4" + cookie "0.4.0" + cookie-signature "1.0.6" + debug "2.6.9" + depd "~1.1.2" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "~1.1.2" + fresh "0.5.2" + merge-descriptors "1.0.1" + methods "~1.1.2" + on-finished "~2.3.0" + parseurl "~1.3.3" + path-to-regexp "0.1.7" + proxy-addr "~2.0.5" + qs "6.7.0" + range-parser "~1.2.1" + safe-buffer "5.1.2" + send "0.17.1" + serve-static "1.14.1" + setprototypeof "1.1.1" + statuses "~1.5.0" + type-is "~1.6.18" + utils-merge "1.0.1" + vary "~1.1.2" + +extend-shallow@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" + dependencies: + is-extendable "^0.1.0" + +extend-shallow@^3.0.0, extend-shallow@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" + dependencies: + assign-symbols "^1.0.0" + is-extendable "^1.0.1" + +extend@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + +external-editor@^3.0.3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" + integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== + dependencies: + chardet "^0.7.0" + iconv-lite "^0.4.24" + tmp "^0.0.33" + +extglob@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" + dependencies: + array-unique "^0.3.2" + define-property "^1.0.0" + expand-brackets "^2.1.4" + extend-shallow "^2.0.1" + fragment-cache "^0.2.1" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +extsprintf@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" + +extsprintf@^1.2.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" + +fast-deep-equal@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" + +fast-deep-equal@^3.1.1: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-glob@^2.0.2: + version "2.2.7" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-2.2.7.tgz#6953857c3afa475fff92ee6015d52da70a4cd39d" + integrity sha512-g1KuQwHOZAmOZMuBtHdxDtju+T2RT8jgCC9aANsbpdiDDTSnjgfuVsIBNKbUeJI3oKMRExcfNDtJl4OhbffMsw== + dependencies: + "@mrmlnc/readdir-enhanced" "^2.2.1" + "@nodelib/fs.stat" "^1.1.2" + glob-parent "^3.1.0" + is-glob "^4.0.0" + merge2 "^1.2.3" + micromatch "^3.1.10" + +fast-json-stable-stringify@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" + +fast-levenshtein@~2.0.4, fast-levenshtein@~2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= + +faye-websocket@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.10.0.tgz#4e492f8d04dfb6f89003507f6edbf2d501e7c6f4" + dependencies: + websocket-driver ">=0.5.1" + +faye-websocket@~0.11.1: + version "0.11.3" + resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.3.tgz#5c0e9a8968e8912c286639fde977a8b209f2508e" + integrity sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA== + dependencies: + websocket-driver ">=0.5.1" + +fb-watchman@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.0.tgz#54e9abf7dfa2f26cd9b1636c588c1afc05de5d58" + dependencies: + bser "^2.0.0" + +figgy-pudding@^3.5.1: + version "3.5.2" + resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e" + integrity sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw== + +figures@^3.0.0, figures@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" + integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== + dependencies: + escape-string-regexp "^1.0.5" + +file-entry-cache@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-5.0.1.tgz#ca0f6efa6dd3d561333fb14515065c2fafdf439c" + integrity sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g== + dependencies: + flat-cache "^2.0.1" + +file-loader@4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-4.3.0.tgz#780f040f729b3d18019f20605f723e844b8a58af" + integrity sha512-aKrYPYjF1yG3oX0kWRrqrSMfgftm7oJW5M+m4owoldH5C51C0RkIwB++JbRvEW3IU6/ZG5n8UvEcdgwOt2UOWA== + dependencies: + loader-utils "^1.2.3" + schema-utils "^2.5.0" + +file-saver@^1.3.3: + version "1.3.8" + resolved "https://registry.yarnpkg.com/file-saver/-/file-saver-1.3.8.tgz#e68a30c7cb044e2fb362b428469feb291c2e09d8" + +filesize@6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/filesize/-/filesize-6.0.1.tgz#f850b509909c7c86f7e450ea19006c31c2ed3d2f" + integrity sha512-u4AYWPgbI5GBhs6id1KdImZWn5yfyFrrQ8OWZdN7ZMfA8Bf4HcO0BGo9bmUIEV8yrp8I1xVfJ/dn90GtFNNJcg== + +fill-range@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" + dependencies: + extend-shallow "^2.0.1" + is-number "^3.0.0" + repeat-string "^1.6.1" + to-regex-range "^2.1.0" + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +finalhandler@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" + integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA== + dependencies: + debug "2.6.9" + encodeurl "~1.0.2" + escape-html "~1.0.3" + on-finished "~2.3.0" + parseurl "~1.3.3" + statuses "~1.5.0" + unpipe "~1.0.0" + +find-cache-dir@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-0.1.1.tgz#c8defae57c8a52a8a784f9e31c57c742e993a0b9" + dependencies: + commondir "^1.0.1" + mkdirp "^0.5.1" + pkg-dir "^1.0.0" + +find-cache-dir@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7" + integrity sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ== + dependencies: + commondir "^1.0.1" + make-dir "^2.0.0" + pkg-dir "^3.0.0" + +find-cache-dir@^3.2.0: + version "3.3.1" + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.1.tgz#89b33fad4a4670daa94f855f7fbe31d6d84fe880" + integrity sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ== + dependencies: + commondir "^1.0.1" + make-dir "^3.0.2" + pkg-dir "^4.1.0" + +find-up@4.1.0, find-up@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +find-up@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" + dependencies: + path-exists "^2.0.0" + pinkie-promise "^2.0.0" + +find-up@^2.0.0, find-up@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" + dependencies: + locate-path "^2.0.0" + +find-up@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" + integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== + dependencies: + locate-path "^3.0.0" + +find-versions@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/find-versions/-/find-versions-3.2.0.tgz#10297f98030a786829681690545ef659ed1d254e" + integrity sha512-P8WRou2S+oe222TOCHitLy8zj+SIsVJh52VP4lvXkaFVnOFFdoWv1H1Jjvel1aI6NCFOAaeAVm8qrI0odiLcww== + dependencies: + semver-regex "^2.0.0" + +flat-cache@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0" + integrity sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA== + dependencies: + flatted "^2.0.0" + rimraf "2.6.3" + write "1.0.3" + +flatted@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138" + integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA== + +flatten@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782" + +flush-write-stream@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8" + integrity sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w== + dependencies: + inherits "^2.0.3" + readable-stream "^2.3.6" + +follow-redirects@^1.0.0: + version "1.6.1" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.6.1.tgz#514973c44b5757368bad8bddfe52f81f015c94cb" + dependencies: + debug "=3.1.0" + +for-in@^0.1.3: + version "0.1.8" + resolved "https://registry.yarnpkg.com/for-in/-/for-in-0.1.8.tgz#d8773908e31256109952b1fdb9b3fa867d2775e1" + integrity sha1-2Hc5COMSVhCZUrH9ubP6hn0ndeE= + +for-in@^1.0.1, for-in@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" + +for-own@^0.1.3: + version "0.1.5" + resolved "https://registry.yarnpkg.com/for-own/-/for-own-0.1.5.tgz#5265c681a4f294dabbf17c9509b6763aa84510ce" + integrity sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4= + dependencies: + for-in "^1.0.1" + +forever-agent@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" + +fork-ts-checker-webpack-plugin@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-3.1.1.tgz#a1642c0d3e65f50c2cc1742e9c0a80f441f86b19" + integrity sha512-DuVkPNrM12jR41KM2e+N+styka0EgLkTnXmNcXdgOM37vtGeY+oCBK/Jx0hzSeEU6memFCtWb4htrHPMDfwwUQ== + dependencies: + babel-code-frame "^6.22.0" + chalk "^2.4.1" + chokidar "^3.3.0" + micromatch "^3.1.10" + minimatch "^3.0.4" + semver "^5.6.0" + tapable "^1.0.0" + worker-rpc "^0.1.0" + +form-data@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.6" + mime-types "^2.1.12" + +forwarded@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" + +fragment-cache@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" + dependencies: + map-cache "^0.2.2" + +fresh@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" + +from2@^2.1.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af" + integrity sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8= + dependencies: + inherits "^2.0.1" + readable-stream "^2.0.0" + +fs-extra@^4.0.2: + version "4.0.3" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.3.tgz#0d852122e5bc5beb453fb028e9c0c9bf36340c94" + integrity sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg== + dependencies: + graceful-fs "^4.1.2" + jsonfile "^4.0.0" + universalify "^0.1.0" + +fs-extra@^7.0.0: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" + integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw== + dependencies: + graceful-fs "^4.1.2" + jsonfile "^4.0.0" + universalify "^0.1.0" + +fs-extra@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" + integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^4.0.0" + universalify "^0.1.0" + +fs-minipass@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.5.tgz#06c277218454ec288df77ada54a03b8702aacb9d" + dependencies: + minipass "^2.2.1" + +fs-minipass@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" + integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== + dependencies: + minipass "^3.0.0" + +fs-write-stream-atomic@^1.0.8: + version "1.0.10" + resolved "https://registry.yarnpkg.com/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz#b47df53493ef911df75731e70a9ded0189db40c9" + integrity sha1-tH31NJPvkR33VzHnCp3tAYnbQMk= + dependencies: + graceful-fs "^4.1.2" + iferr "^0.1.5" + imurmurhash "^0.1.4" + readable-stream "1 || 2" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + +fsevents@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.2.tgz#4c0a1fb34bc68e543b4b82a9ec392bfbda840805" + integrity sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA== + +fsevents@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.7.tgz#4851b664a3783e52003b3c66eb0eee1074933aa4" + dependencies: + nan "^2.9.2" + node-pre-gyp "^0.10.0" + +fsevents@~2.1.2: + version "2.1.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e" + integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ== + +fstream@^1.0.0, fstream@^1.0.2: + version "1.0.11" + resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.11.tgz#5c1fb1f117477114f0632a0eb4b71b3cb0fd3171" + dependencies: + graceful-fs "^4.1.2" + inherits "~2.0.0" + mkdirp ">=0.5 0" + rimraf "2" + +function-bind@^1.0.2, function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + +functional-red-black-tree@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" + +gauge@~2.7.3: + version "2.7.4" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" + dependencies: + aproba "^1.0.3" + console-control-strings "^1.0.0" + has-unicode "^2.0.0" + object-assign "^4.1.0" + signal-exit "^3.0.0" + string-width "^1.0.1" + strip-ansi "^3.0.1" + wide-align "^1.1.0" + +gaze@^1.0.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/gaze/-/gaze-1.1.3.tgz#c441733e13b927ac8c0ff0b4c3b033f28812924a" + dependencies: + globule "^1.0.0" + +gensync@^1.0.0-beta.1: + version "1.0.0-beta.1" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.1.tgz#58f4361ff987e5ff6e1e7a210827aa371eaac269" + integrity sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg== + +get-caller-file@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a" + +get-caller-file@^2.0.1: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-own-enumerable-property-symbols@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.0.tgz#b877b49a5c16aefac3655f2ed2ea5b684df8d203" + +get-stdin@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" + +get-stream@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" + integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== + dependencies: + pump "^3.0.0" + +get-stream@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.1.0.tgz#01203cdc92597f9b909067c3e656cc1f4d3c4dc9" + integrity sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw== + dependencies: + pump "^3.0.0" + +get-value@^2.0.3, get-value@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" + +getpass@^0.1.1: + version "0.1.7" + resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" + dependencies: + assert-plus "^1.0.0" + +glob-parent@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" + dependencies: + is-glob "^3.1.0" + path-dirname "^1.0.0" + +glob-parent@^5.0.0, glob-parent@~5.1.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229" + integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ== + dependencies: + is-glob "^4.0.1" + +glob-to-regexp@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab" + integrity sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs= + +glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@~7.1.1: + version "7.1.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +glob@^7.1.4, glob@^7.1.6: + version "7.1.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +global-modules@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780" + integrity sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A== + dependencies: + global-prefix "^3.0.0" + +global-prefix@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-3.0.0.tgz#fc85f73064df69f50421f47f883fe5b913ba9b97" + integrity sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg== + dependencies: + ini "^1.3.5" + kind-of "^6.0.2" + which "^1.3.1" + +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +globals@^12.1.0: + version "12.4.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-12.4.0.tgz#a18813576a41b00a24a97e7f815918c2e19925f8" + integrity sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg== + dependencies: + type-fest "^0.8.1" + +globby@8.0.2: + version "8.0.2" + resolved "https://registry.yarnpkg.com/globby/-/globby-8.0.2.tgz#5697619ccd95c5275dbb2d6faa42087c1a941d8d" + integrity sha512-yTzMmKygLp8RUpG1Ymu2VXPSJQZjNAZPD4ywgYEaG7e4tBJeUQBO8OpXrf1RCNcEs5alsoJYPAMiIHP0cmeC7w== + dependencies: + array-union "^1.0.1" + dir-glob "2.0.0" + fast-glob "^2.0.2" + glob "^7.1.2" + ignore "^3.3.5" + pify "^3.0.0" + slash "^1.0.0" + +globby@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c" + dependencies: + array-union "^1.0.1" + glob "^7.0.3" + object-assign "^4.0.1" + pify "^2.0.0" + pinkie-promise "^2.0.0" + +globule@^1.0.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/globule/-/globule-1.2.1.tgz#5dffb1b191f22d20797a9369b49eab4e9839696d" + dependencies: + glob "~7.1.1" + lodash "~4.17.10" + minimatch "~3.0.2" + +graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6: + version "4.1.15" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00" + +graceful-fs@^4.1.15, graceful-fs@^4.2.0, graceful-fs@^4.2.2: + version "4.2.4" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" + integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== + +growly@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" + +gud@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/gud/-/gud-1.0.0.tgz#a489581b17e6a70beca9abe3ae57de7a499852c0" + integrity sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw== + +gzip-size@5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-5.1.1.tgz#cb9bee692f87c0612b232840a873904e4c135274" + integrity sha512-FNHi6mmoHvs1mxZAds4PpdCS6QG8B4C1krxJsMutgxl5t3+GlRTzzI3NEkifXx2pVsOvJdOGSmIgDhQ55FwdPA== + dependencies: + duplexer "^0.1.1" + pify "^4.0.1" + +handle-thing@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e" + integrity sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg== + +har-schema@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" + +har-validator@~5.1.0: + version "5.1.3" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080" + dependencies: + ajv "^6.5.5" + har-schema "^2.0.0" + +harmony-reflect@^1.4.6: + version "1.6.1" + resolved "https://registry.yarnpkg.com/harmony-reflect/-/harmony-reflect-1.6.1.tgz#c108d4f2bb451efef7a37861fdbdae72c9bdefa9" + integrity sha512-WJTeyp0JzGtHcuMsi7rw2VwtkvLa+JyfEKJCFyfcS0+CDkjQ5lHPu7zEhFZP+PDSRrEgXa5Ah0l1MbgbE41XjA== + +has-ansi@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" + dependencies: + ansi-regex "^2.0.0" + +has-binary2@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-binary2/-/has-binary2-1.0.3.tgz#7776ac627f3ea77250cfc332dab7ddf5e4f5d11d" + dependencies: + isarray "2.0.1" + +has-cors@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/has-cors/-/has-cors-1.1.0.tgz#5e474793f7ea9843d1bb99c23eef49ff126fff39" + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-symbols@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44" + +has-symbols@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" + integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== + +has-unicode@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" + +has-value@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" + dependencies: + get-value "^2.0.3" + has-values "^0.1.4" + isobject "^2.0.0" + +has-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" + dependencies: + get-value "^2.0.6" + has-values "^1.0.0" + isobject "^3.0.0" + +has-values@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" + +has-values@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" + dependencies: + is-number "^3.0.0" + kind-of "^4.0.0" + +has@^1.0.0, has@^1.0.1, has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + dependencies: + function-bind "^1.1.1" + +hash-base@^3.0.0: + version "3.0.4" + resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.0.4.tgz#5fc8686847ecd73499403319a6b0a3f3f6ae4918" + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + +hash.js@^1.0.0, hash.js@^1.0.3: + version "1.1.7" + resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" + dependencies: + inherits "^2.0.3" + minimalistic-assert "^1.0.1" + +he@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" + integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== + +hex-color-regex@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e" + integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ== + +history@^4.9.0: + version "4.10.1" + resolved "https://registry.yarnpkg.com/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3" + integrity sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew== + dependencies: + "@babel/runtime" "^7.1.2" + loose-envify "^1.2.0" + resolve-pathname "^3.0.0" + tiny-invariant "^1.0.2" + tiny-warning "^1.0.0" + value-equal "^1.0.1" + +hmac-drbg@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" + dependencies: + hash.js "^1.0.3" + minimalistic-assert "^1.0.0" + minimalistic-crypto-utils "^1.0.1" + +hoist-non-react-statics@^3.1.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.0.tgz#b09178f0122184fb95acf525daaecb4d8f45958b" + dependencies: + react-is "^16.7.0" + +hoist-non-react-statics@^3.3.0: + version "3.3.2" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" + integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== + dependencies: + react-is "^16.7.0" + +hosted-git-info@^2.1.4: + version "2.7.1" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.7.1.tgz#97f236977bd6e125408930ff6de3eec6281ec047" + +hpack.js@^2.1.6: + version "2.1.6" + resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2" + dependencies: + inherits "^2.0.1" + obuf "^1.0.0" + readable-stream "^2.0.1" + wbuf "^1.1.0" + +hsl-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/hsl-regex/-/hsl-regex-1.0.0.tgz#d49330c789ed819e276a4c0d272dffa30b18fe6e" + integrity sha1-1JMwx4ntgZ4nakwNJy3/owsY/m4= + +hsla-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/hsla-regex/-/hsla-regex-1.0.0.tgz#c1ce7a3168c8c6614033a4b5f7877f3b225f9c38" + integrity sha1-wc56MWjIxmFAM6S194d/OyJfnDg= + +html-comment-regex@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.2.tgz#97d4688aeb5c81886a364faa0cad1dda14d433a7" + +html-encoding-sniffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz#e70d84b94da53aa375e11fe3a351be6642ca46f8" + integrity sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw== + dependencies: + whatwg-encoding "^1.0.1" + +html-entities@^1.2.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.3.1.tgz#fb9a1a4b5b14c5daba82d3e34c6ae4fe701a0e44" + integrity sha512-rhE/4Z3hIhzHAUKbW8jVcCyuT5oJCXXqhN/6mXXVCpzTmvJnoH2HL/bt3EZ6p55jbFJBeAe1ZNpL5BugLujxNA== + +html-escaper@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + +html-minifier-terser@^5.0.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz#922e96f1f3bb60832c2634b79884096389b1f054" + integrity sha512-ZPr5MNObqnV/T9akshPKbVgyOqLmy+Bxo7juKCfTfnjNniTAMdy4hz21YQqoofMBJD2kdREaqPPdThoR78Tgxg== + dependencies: + camel-case "^4.1.1" + clean-css "^4.2.3" + commander "^4.1.1" + he "^1.2.0" + param-case "^3.0.3" + relateurl "^0.2.7" + terser "^4.6.3" + +html-webpack-plugin@4.0.0-beta.11: + version "4.0.0-beta.11" + resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-4.0.0-beta.11.tgz#3059a69144b5aecef97708196ca32f9e68677715" + integrity sha512-4Xzepf0qWxf8CGg7/WQM5qBB2Lc/NFI7MhU59eUDTkuQp3skZczH4UA1d6oQyDEIoMDgERVhRyTdtUPZ5s5HBg== + dependencies: + html-minifier-terser "^5.0.1" + loader-utils "^1.2.3" + lodash "^4.17.15" + pretty-error "^2.1.1" + tapable "^1.1.3" + util.promisify "1.0.0" + +htmlparser2@~3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.3.0.tgz#cc70d05a59f6542e43f0e685c982e14c924a9efe" + dependencies: + domelementtype "1" + domhandler "2.1" + domutils "1.1" + readable-stream "1.0" + +http-deceiver@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" + +http-errors@1.7.2: + version "1.7.2" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f" + integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg== + dependencies: + depd "~1.1.2" + inherits "2.0.3" + setprototypeof "1.1.1" + statuses ">= 1.5.0 < 2" + toidentifier "1.0.0" + +http-errors@~1.6.2: + version "1.6.3" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" + dependencies: + depd "~1.1.2" + inherits "2.0.3" + setprototypeof "1.1.0" + statuses ">= 1.4.0 < 2" + +http-errors@~1.7.2: + version "1.7.3" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06" + integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw== + dependencies: + depd "~1.1.2" + inherits "2.0.4" + setprototypeof "1.1.1" + statuses ">= 1.5.0 < 2" + toidentifier "1.0.0" + +http-parser-js@>=0.4.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.0.tgz#d65edbede84349d0dc30320815a15d39cc3cbbd8" + +http-proxy-middleware@0.19.1: + version "0.19.1" + resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz#183c7dc4aa1479150306498c210cdaf96080a43a" + integrity sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q== + dependencies: + http-proxy "^1.17.0" + is-glob "^4.0.0" + lodash "^4.17.11" + micromatch "^3.1.10" + +http-proxy@^1.17.0: + version "1.18.1" + resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549" + integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ== + dependencies: + eventemitter3 "^4.0.0" + follow-redirects "^1.0.0" + requires-port "^1.0.0" + +http-signature@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" + dependencies: + assert-plus "^1.0.0" + jsprim "^1.2.2" + sshpk "^1.7.0" + +https-browserify@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" + +human-signals@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" + integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw== + +husky@~4.2.5: + version "4.2.5" + resolved "https://registry.yarnpkg.com/husky/-/husky-4.2.5.tgz#2b4f7622673a71579f901d9885ed448394b5fa36" + integrity sha512-SYZ95AjKcX7goYVZtVZF2i6XiZcHknw50iXvY7b0MiGoj5RwdgRQNEHdb+gPDPCXKlzwrybjFjkL6FOj8uRhZQ== + dependencies: + chalk "^4.0.0" + ci-info "^2.0.0" + compare-versions "^3.6.0" + cosmiconfig "^6.0.0" + find-versions "^3.2.0" + opencollective-postinstall "^2.0.2" + pkg-dir "^4.2.0" + please-upgrade-node "^3.2.0" + slash "^3.0.0" + which-pm-runs "^1.0.0" + +iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + dependencies: + safer-buffer ">= 2.1.2 < 3" + +icss-utils@^4.0.0, icss-utils@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-4.1.1.tgz#21170b53789ee27447c2f47dd683081403f9a467" + integrity sha512-4aFq7wvWyMHKgxsH8QQtGpvbASCf+eM3wPRLI6R+MgAnTCZ6STYsRvttLvRWK0Nfif5piF394St3HeJDaljGPA== + dependencies: + postcss "^7.0.14" + +identity-obj-proxy@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/identity-obj-proxy/-/identity-obj-proxy-3.0.0.tgz#94d2bda96084453ef36fbc5aaec37e0f79f1fc14" + integrity sha1-lNK9qWCERT7zb7xarsN+D3nx/BQ= + dependencies: + harmony-reflect "^1.4.6" + +ieee754@^1.1.4: + version "1.1.12" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.12.tgz#50bf24e5b9c8bb98af4964c941cdb0918da7b60b" + +iferr@^0.1.5: + version "0.1.5" + resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501" + integrity sha1-xg7taebY/bazEEofy8ocGS3FtQE= + +ignore-walk@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.1.tgz#a83e62e7d272ac0e3b551aaa82831a19b69f82f8" + dependencies: + minimatch "^3.0.4" + +ignore@^3.3.5: + version "3.3.10" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.10.tgz#0a97fb876986e8081c631160f8f9f389157f0043" + integrity sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug== + +ignore@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" + integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== + +immer@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/immer/-/immer-1.10.0.tgz#bad67605ba9c810275d91e1c2a47d4582e98286d" + integrity sha512-O3sR1/opvCDGLEVcvrGTMtLac8GJ5IwZC4puPrLuRj3l7ICKvkmA0vGuU9OW8mV9WIBRnaxp5GJh9IEAaNOoYg== + +import-cwd@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/import-cwd/-/import-cwd-2.1.0.tgz#aa6cf36e722761285cb371ec6519f53e2435b0a9" + integrity sha1-qmzzbnInYShcs3HsZRn1PiQ1sKk= + dependencies: + import-from "^2.1.0" + +import-fresh@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546" + integrity sha1-2BNVwVYS04bGH53dOSLUMEgipUY= + dependencies: + caller-path "^2.0.0" + resolve-from "^3.0.0" + +import-fresh@^3.0.0, import-fresh@^3.1.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.2.1.tgz#633ff618506e793af5ac91bf48b72677e15cbe66" + integrity sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +import-from@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/import-from/-/import-from-2.1.0.tgz#335db7f2a7affd53aaa471d4b8021dee36b7f3b1" + integrity sha1-M1238qev/VOqpHHUuAId7ja387E= + dependencies: + resolve-from "^3.0.0" + +import-local@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-2.0.0.tgz#55070be38a5993cf18ef6db7e961f5bee5c5a09d" + integrity sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ== + dependencies: + pkg-dir "^3.0.0" + resolve-cwd "^2.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + +in-publish@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/in-publish/-/in-publish-2.0.0.tgz#e20ff5e3a2afc2690320b6dc552682a9c7fadf51" + +indent-string@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80" + dependencies: + repeating "^2.0.0" + +indent-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" + integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== + +indexes-of@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" + +indexof@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d" + +infer-owner@^1.0.3, infer-owner@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467" + integrity sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + +inherits@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" + +inherits@2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +ini@^1.3.5, ini@~1.3.0: + version "1.3.5" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" + +inquirer@7.0.4: + version "7.0.4" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.0.4.tgz#99af5bde47153abca23f5c7fc30db247f39da703" + integrity sha512-Bu5Td5+j11sCkqfqmUTiwv+tWisMtP0L7Q8WrqA2C/BbBhy1YTdFrvjjlrKq8oagA/tLQBski2Gcx/Sqyi2qSQ== + dependencies: + ansi-escapes "^4.2.1" + chalk "^2.4.2" + cli-cursor "^3.1.0" + cli-width "^2.0.0" + external-editor "^3.0.3" + figures "^3.0.0" + lodash "^4.17.15" + mute-stream "0.0.8" + run-async "^2.2.0" + rxjs "^6.5.3" + string-width "^4.1.0" + strip-ansi "^5.1.0" + through "^2.3.6" + +inquirer@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.1.0.tgz#1298a01859883e17c7264b82870ae1034f92dd29" + integrity sha512-5fJMWEmikSYu0nv/flMc475MhGbB7TSPd/2IpFV4I4rMklboCH2rQjYY5kKiYGHqUF9gvaambupcJFFG9dvReg== + dependencies: + ansi-escapes "^4.2.1" + chalk "^3.0.0" + cli-cursor "^3.1.0" + cli-width "^2.0.0" + external-editor "^3.0.3" + figures "^3.0.0" + lodash "^4.17.15" + mute-stream "0.0.8" + run-async "^2.4.0" + rxjs "^6.5.3" + string-width "^4.1.0" + strip-ansi "^6.0.0" + through "^2.3.6" + +internal-ip@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-4.3.0.tgz#845452baad9d2ca3b69c635a137acb9a0dad0907" + integrity sha512-S1zBo1D6zcsyuC6PMmY5+55YMILQ9av8lotMx447Bq6SAgo/sDK6y6uUKmuYhW7eacnIhFfsPmCNYdDzsnnDCg== + dependencies: + default-gateway "^4.2.0" + ipaddr.js "^1.9.0" + +internal-slot@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.2.tgz#9c2e9fb3cd8e5e4256c6f45fe310067fcfa378a3" + integrity sha512-2cQNfwhAfJIkU4KZPkDI+Gj5yNNnbqi40W9Gge6dfnk4TocEVm00B3bdiL+JINrbGJil2TeHvM4rETGzk/f/0g== + dependencies: + es-abstract "^1.17.0-next.1" + has "^1.0.3" + side-channel "^1.0.2" + +invariant@^2.1.0, invariant@^2.2.2, invariant@^2.2.4: + version "2.2.4" + resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" + dependencies: + loose-envify "^1.0.0" + +invert-kv@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" + +invert-kv@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-2.0.0.tgz#7393f5afa59ec9ff5f67a27620d11c226e3eec02" + integrity sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA== + +ip-regex@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9" + integrity sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk= + +ip@^1.1.0, ip@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" + +ipaddr.js@1.9.1, ipaddr.js@^1.9.0: + version "1.9.1" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" + integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== + +is-absolute-url@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-2.1.0.tgz#50530dfb84fcc9aa7dbe7852e83a37b93b9f2aa6" + +is-absolute-url@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-3.0.3.tgz#96c6a22b6a23929b11ea0afb1836c36ad4a5d698" + integrity sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q== + +is-accessor-descriptor@^0.1.6: + version "0.1.6" + resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" + dependencies: + kind-of "^3.0.2" + +is-accessor-descriptor@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" + dependencies: + kind-of "^6.0.0" + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + +is-arrayish@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" + integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== + +is-binary-path@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" + dependencies: + binary-extensions "^1.0.0" + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-buffer@^1.0.2, is-buffer@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" + +is-callable@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.4.tgz#1e1adf219e1eeb684d691f9d6a05ff0d30a24d75" + +is-callable@^1.1.5: + version "1.2.0" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.0.tgz#83336560b54a38e35e3a2df7afd0454d691468bb" + integrity sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw== + +is-ci@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" + integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w== + dependencies: + ci-info "^2.0.0" + +is-color-stop@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-color-stop/-/is-color-stop-1.1.0.tgz#cfff471aee4dd5c9e158598fbe12967b5cdad345" + integrity sha1-z/9HGu5N1cnhWFmPvhKWe1za00U= + dependencies: + css-color-names "^0.0.4" + hex-color-regex "^1.1.0" + hsl-regex "^1.0.0" + hsla-regex "^1.0.0" + rgb-regex "^1.0.1" + rgba-regex "^1.0.0" + +is-data-descriptor@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" + dependencies: + kind-of "^3.0.2" + +is-data-descriptor@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" + dependencies: + kind-of "^6.0.0" + +is-date-object@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16" + +is-descriptor@^0.1.0: + version "0.1.6" + resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" + dependencies: + is-accessor-descriptor "^0.1.6" + is-data-descriptor "^0.1.4" + kind-of "^5.0.0" + +is-descriptor@^1.0.0, is-descriptor@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" + dependencies: + is-accessor-descriptor "^1.0.0" + is-data-descriptor "^1.0.0" + kind-of "^6.0.2" + +is-directory@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1" + +is-docker@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.0.0.tgz#2cb0df0e75e2d064fe1864c37cdeacb7b2dcf25b" + integrity sha512-pJEdRugimx4fBMra5z2/5iRdZ63OhYV0vr0Dwm5+xtW4D1FvRkB8hamMIhnWfyJeDdyr/aa7BDyNbtG38VxgoQ== + +is-extendable@^0.1.0, is-extendable@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" + +is-extendable@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" + dependencies: + is-plain-object "^2.0.4" + +is-extglob@^2.1.0, is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + +is-finite@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa" + dependencies: + number-is-nan "^1.0.0" + +is-fullwidth-code-point@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" + dependencies: + number-is-nan "^1.0.0" + +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-generator-fn@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" + integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== + +is-glob@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" + dependencies: + is-extglob "^2.1.0" + +is-glob@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.0.tgz#9521c76845cc2610a85203ddf080a958c2ffabc0" + dependencies: + is-extglob "^2.1.1" + +is-glob@^4.0.1, is-glob@~4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" + integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== + dependencies: + is-extglob "^2.1.1" + +is-number@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" + dependencies: + kind-of "^3.0.2" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-obj@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" + +is-obj@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" + integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== + +is-path-cwd@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-2.2.0.tgz#67d43b82664a7b5191fd9119127eb300048a9fdb" + integrity sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ== + +is-path-in-cwd@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz#bfe2dca26c69f397265a4009963602935a053acb" + integrity sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ== + dependencies: + is-path-inside "^2.1.0" + +is-path-inside@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-2.1.0.tgz#7c9810587d659a40d27bcdb4d5616eab059494b2" + integrity sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg== + dependencies: + path-is-inside "^1.0.2" + +is-plain-obj@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" + +is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" + dependencies: + isobject "^3.0.1" + +is-promise@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" + +is-regex@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491" + dependencies: + has "^1.0.1" + +is-regex@^1.0.5: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.0.tgz#ece38e389e490df0dc21caea2bd596f987f767ff" + integrity sha512-iI97M8KTWID2la5uYXlkbSDQIg4F6o1sYboZKKTDpnDQMLtUL86zxhgDet3Q2SriaYsyGqZ6Mn2SjbRKeLHdqw== + dependencies: + has-symbols "^1.0.1" + +is-regexp@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-1.0.0.tgz#fd2d883545c46bac5a633e7b9a09e87fa2cb5069" + +is-resolvable@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88" + +is-root@2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-root/-/is-root-2.1.0.tgz#809e18129cf1129644302a4f8544035d51984a9c" + integrity sha512-AGOriNp96vNBd3HtU+RzFEc75FfR5ymiYv8E553I71SCeXBiMsVDUtdio1OEFvrPyLIQ9tVR5RxXIFe5PUFjMg== + +is-stream@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" + +is-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3" + integrity sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw== + +is-string@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.5.tgz#40493ed198ef3ff477b8c7f92f644ec82a5cd3a6" + integrity sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ== + +is-svg@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-svg/-/is-svg-3.0.0.tgz#9321dbd29c212e5ca99c4fa9794c714bcafa2f75" + integrity sha512-gi4iHK53LR2ujhLVVj+37Ykh9GLqYHX6JOVXbLAucaG/Cqw9xwdFOjDM2qeifLs1sF1npXXFvDu0r5HNgCMrzQ== + dependencies: + html-comment-regex "^1.1.0" + +is-symbol@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.2.tgz#a055f6ae57192caee329e7a860118b497a950f38" + dependencies: + has-symbols "^1.0.0" + +is-typedarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + +is-utf8@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" + +is-windows@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" + +is-wsl@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" + +is-wsl@^2.1.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" + integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== + dependencies: + is-docker "^2.0.0" + +isarray@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" + +isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + +isarray@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.1.tgz#a37d94ed9cda2d59865c9f76fe596ee1f338741e" + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + +isobject@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" + dependencies: + isarray "1.0.0" + +isobject@^3.0.0, isobject@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" + +isstream@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" + +istanbul-lib-coverage@^2.0.2, istanbul-lib-coverage@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz#675f0ab69503fad4b1d849f736baaca803344f49" + integrity sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA== + +istanbul-lib-instrument@^3.0.1, istanbul-lib-instrument@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz#a5f63d91f0bbc0c3e479ef4c5de027335ec6d630" + integrity sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA== + dependencies: + "@babel/generator" "^7.4.0" + "@babel/parser" "^7.4.3" + "@babel/template" "^7.4.0" + "@babel/traverse" "^7.4.3" + "@babel/types" "^7.4.0" + istanbul-lib-coverage "^2.0.5" + semver "^6.0.0" + +istanbul-lib-report@^2.0.4: + version "2.0.8" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-2.0.8.tgz#5a8113cd746d43c4889eba36ab10e7d50c9b4f33" + integrity sha512-fHBeG573EIihhAblwgxrSenp0Dby6tJMFR/HvlerBsrCTD5bkUuoNtn3gVh29ZCS824cGGBPn7Sg7cNk+2xUsQ== + dependencies: + istanbul-lib-coverage "^2.0.5" + make-dir "^2.1.0" + supports-color "^6.1.0" + +istanbul-lib-source-maps@^3.0.1: + version "3.0.6" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz#284997c48211752ec486253da97e3879defba8c8" + integrity sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw== + dependencies: + debug "^4.1.1" + istanbul-lib-coverage "^2.0.5" + make-dir "^2.1.0" + rimraf "^2.6.3" + source-map "^0.6.1" + +istanbul-reports@^2.2.6: + version "2.2.7" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-2.2.7.tgz#5d939f6237d7b48393cc0959eab40cd4fd056931" + integrity sha512-uu1F/L1o5Y6LzPVSVZXNOoD/KXpJue9aeLRd0sM9uMXfZvzomB0WxVamWb5ue8kA2vVWEmW7EG+A5n3f1kqHKg== + dependencies: + html-escaper "^2.0.0" + +jest-changed-files@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-24.9.0.tgz#08d8c15eb79a7fa3fc98269bc14b451ee82f8039" + integrity sha512-6aTWpe2mHF0DhL28WjdkO8LyGjs3zItPET4bMSeXU6T3ub4FPMw+mcOcbdGXQOAfmLcxofD23/5Bl9Z4AkFwqg== + dependencies: + "@jest/types" "^24.9.0" + execa "^1.0.0" + throat "^4.0.0" + +jest-cli@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-24.9.0.tgz#ad2de62d07472d419c6abc301fc432b98b10d2af" + integrity sha512-+VLRKyitT3BWoMeSUIHRxV/2g8y9gw91Jh5z2UmXZzkZKpbC08CSehVxgHUwTpy+HwGcns/tqafQDJW7imYvGg== + dependencies: + "@jest/core" "^24.9.0" + "@jest/test-result" "^24.9.0" + "@jest/types" "^24.9.0" + chalk "^2.0.1" + exit "^0.1.2" + import-local "^2.0.0" + is-ci "^2.0.0" + jest-config "^24.9.0" + jest-util "^24.9.0" + jest-validate "^24.9.0" + prompts "^2.0.1" + realpath-native "^1.1.0" + yargs "^13.3.0" + +jest-config@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-24.9.0.tgz#fb1bbc60c73a46af03590719efa4825e6e4dd1b5" + integrity sha512-RATtQJtVYQrp7fvWg6f5y3pEFj9I+H8sWw4aKxnDZ96mob5i5SD6ZEGWgMLXQ4LE8UurrjbdlLWdUeo+28QpfQ== + dependencies: + "@babel/core" "^7.1.0" + "@jest/test-sequencer" "^24.9.0" + "@jest/types" "^24.9.0" + babel-jest "^24.9.0" + chalk "^2.0.1" + glob "^7.1.1" + jest-environment-jsdom "^24.9.0" + jest-environment-node "^24.9.0" + jest-get-type "^24.9.0" + jest-jasmine2 "^24.9.0" + jest-regex-util "^24.3.0" + jest-resolve "^24.9.0" + jest-util "^24.9.0" + jest-validate "^24.9.0" + micromatch "^3.1.10" + pretty-format "^24.9.0" + realpath-native "^1.1.0" + +jest-diff@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-24.9.0.tgz#931b7d0d5778a1baf7452cb816e325e3724055da" + integrity sha512-qMfrTs8AdJE2iqrTp0hzh7kTd2PQWrsFyj9tORoKmu32xjPjeE4NyjVRDz8ybYwqS2ik8N4hsIpiVTyFeo2lBQ== + dependencies: + chalk "^2.0.1" + diff-sequences "^24.9.0" + jest-get-type "^24.9.0" + pretty-format "^24.9.0" + +jest-docblock@^24.3.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-24.9.0.tgz#7970201802ba560e1c4092cc25cbedf5af5a8ce2" + integrity sha512-F1DjdpDMJMA1cN6He0FNYNZlo3yYmOtRUnktrT9Q37njYzC5WEaDdmbynIgy0L/IvXvvgsG8OsqhLPXTpfmZAA== + dependencies: + detect-newline "^2.1.0" + +jest-each@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-24.9.0.tgz#eb2da602e2a610898dbc5f1f6df3ba86b55f8b05" + integrity sha512-ONi0R4BvW45cw8s2Lrx8YgbeXL1oCQ/wIDwmsM3CqM/nlblNCPmnC3IPQlMbRFZu3wKdQ2U8BqM6lh3LJ5Bsog== + dependencies: + "@jest/types" "^24.9.0" + chalk "^2.0.1" + jest-get-type "^24.9.0" + jest-util "^24.9.0" + pretty-format "^24.9.0" + +jest-environment-jsdom-fourteen@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/jest-environment-jsdom-fourteen/-/jest-environment-jsdom-fourteen-1.0.1.tgz#4cd0042f58b4ab666950d96532ecb2fc188f96fb" + integrity sha512-DojMX1sY+at5Ep+O9yME34CdidZnO3/zfPh8UW+918C5fIZET5vCjfkegixmsi7AtdYfkr4bPlIzmWnlvQkP7Q== + dependencies: + "@jest/environment" "^24.3.0" + "@jest/fake-timers" "^24.3.0" + "@jest/types" "^24.3.0" + jest-mock "^24.0.0" + jest-util "^24.0.0" + jsdom "^14.1.0" + +jest-environment-jsdom@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-24.9.0.tgz#4b0806c7fc94f95edb369a69cc2778eec2b7375b" + integrity sha512-Zv9FV9NBRzLuALXjvRijO2351DRQeLYXtpD4xNvfoVFw21IOKNhZAEUKcbiEtjTkm2GsJ3boMVgkaR7rN8qetA== + dependencies: + "@jest/environment" "^24.9.0" + "@jest/fake-timers" "^24.9.0" + "@jest/types" "^24.9.0" + jest-mock "^24.9.0" + jest-util "^24.9.0" + jsdom "^11.5.1" + +jest-environment-node@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-24.9.0.tgz#333d2d2796f9687f2aeebf0742b519f33c1cbfd3" + integrity sha512-6d4V2f4nxzIzwendo27Tr0aFm+IXWa0XEUnaH6nU0FMaozxovt+sfRvh4J47wL1OvF83I3SSTu0XK+i4Bqe7uA== + dependencies: + "@jest/environment" "^24.9.0" + "@jest/fake-timers" "^24.9.0" + "@jest/types" "^24.9.0" + jest-mock "^24.9.0" + jest-util "^24.9.0" + +jest-get-type@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-24.9.0.tgz#1684a0c8a50f2e4901b6644ae861f579eed2ef0e" + integrity sha512-lUseMzAley4LhIcpSP9Jf+fTrQ4a1yHQwLNeeVa2cEmbCGeoZAtYPOIv8JaxLD/sUpKxetKGP+gsHl8f8TSj8Q== + +jest-haste-map@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-24.9.0.tgz#b38a5d64274934e21fa417ae9a9fbeb77ceaac7d" + integrity sha512-kfVFmsuWui2Sj1Rp1AJ4D9HqJwE4uwTlS/vO+eRUaMmd54BFpli2XhMQnPC2k4cHFVbB2Q2C+jtI1AGLgEnCjQ== + dependencies: + "@jest/types" "^24.9.0" + anymatch "^2.0.0" + fb-watchman "^2.0.0" + graceful-fs "^4.1.15" + invariant "^2.2.4" + jest-serializer "^24.9.0" + jest-util "^24.9.0" + jest-worker "^24.9.0" + micromatch "^3.1.10" + sane "^4.0.3" + walker "^1.0.7" + optionalDependencies: + fsevents "^1.2.7" + +jest-jasmine2@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-24.9.0.tgz#1f7b1bd3242c1774e62acabb3646d96afc3be6a0" + integrity sha512-Cq7vkAgaYKp+PsX+2/JbTarrk0DmNhsEtqBXNwUHkdlbrTBLtMJINADf2mf5FkowNsq8evbPc07/qFO0AdKTzw== + dependencies: + "@babel/traverse" "^7.1.0" + "@jest/environment" "^24.9.0" + "@jest/test-result" "^24.9.0" + "@jest/types" "^24.9.0" + chalk "^2.0.1" + co "^4.6.0" + expect "^24.9.0" + is-generator-fn "^2.0.0" + jest-each "^24.9.0" + jest-matcher-utils "^24.9.0" + jest-message-util "^24.9.0" + jest-runtime "^24.9.0" + jest-snapshot "^24.9.0" + jest-util "^24.9.0" + pretty-format "^24.9.0" + throat "^4.0.0" + +jest-leak-detector@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-24.9.0.tgz#b665dea7c77100c5c4f7dfcb153b65cf07dcf96a" + integrity sha512-tYkFIDsiKTGwb2FG1w8hX9V0aUb2ot8zY/2nFg087dUageonw1zrLMP4W6zsRO59dPkTSKie+D4rhMuP9nRmrA== + dependencies: + jest-get-type "^24.9.0" + pretty-format "^24.9.0" + +jest-matcher-utils@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-24.9.0.tgz#f5b3661d5e628dffe6dd65251dfdae0e87c3a073" + integrity sha512-OZz2IXsu6eaiMAwe67c1T+5tUAtQyQx27/EMEkbFAGiw52tB9em+uGbzpcgYVpA8wl0hlxKPZxrly4CXU/GjHA== + dependencies: + chalk "^2.0.1" + jest-diff "^24.9.0" + jest-get-type "^24.9.0" + pretty-format "^24.9.0" + +jest-message-util@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-24.9.0.tgz#527f54a1e380f5e202a8d1149b0ec872f43119e3" + integrity sha512-oCj8FiZ3U0hTP4aSui87P4L4jC37BtQwUMqk+zk/b11FR19BJDeZsZAvIHutWnmtw7r85UmR3CEWZ0HWU2mAlw== + dependencies: + "@babel/code-frame" "^7.0.0" + "@jest/test-result" "^24.9.0" + "@jest/types" "^24.9.0" + "@types/stack-utils" "^1.0.1" + chalk "^2.0.1" + micromatch "^3.1.10" + slash "^2.0.0" + stack-utils "^1.0.1" + +jest-mock@^24.0.0, jest-mock@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-24.9.0.tgz#c22835541ee379b908673ad51087a2185c13f1c6" + integrity sha512-3BEYN5WbSq9wd+SyLDES7AHnjH9A/ROBwmz7l2y+ol+NtSFO8DYiEBzoO1CeFc9a8DYy10EO4dDFVv/wN3zl1w== + dependencies: + "@jest/types" "^24.9.0" + +jest-pnp-resolver@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.1.tgz#ecdae604c077a7fbc70defb6d517c3c1c898923a" + integrity sha512-pgFw2tm54fzgYvc/OHrnysABEObZCUNFnhjoRjaVOCN8NYc032/gVjPaHD4Aq6ApkSieWtfKAFQtmDKAmhupnQ== + +jest-regex-util@^24.3.0, jest-regex-util@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-24.9.0.tgz#c13fb3380bde22bf6575432c493ea8fe37965636" + integrity sha512-05Cmb6CuxaA+Ys6fjr3PhvV3bGQmO+2p2La4hFbU+W5uOc479f7FdLXUWXw4pYMAhhSZIuKHwSXSu6CsSBAXQA== + +jest-resolve-dependencies@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-24.9.0.tgz#ad055198959c4cfba8a4f066c673a3f0786507ab" + integrity sha512-Fm7b6AlWnYhT0BXy4hXpactHIqER7erNgIsIozDXWl5dVm+k8XdGVe1oTg1JyaFnOxarMEbax3wyRJqGP2Pq+g== + dependencies: + "@jest/types" "^24.9.0" + jest-regex-util "^24.3.0" + jest-snapshot "^24.9.0" + +jest-resolve@24.9.0, jest-resolve@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-24.9.0.tgz#dff04c7687af34c4dd7e524892d9cf77e5d17321" + integrity sha512-TaLeLVL1l08YFZAt3zaPtjiVvyy4oSA6CRe+0AFPPVX3Q/VI0giIWWoAvoS5L96vj9Dqxj4fB5p2qrHCmTU/MQ== + dependencies: + "@jest/types" "^24.9.0" + browser-resolve "^1.11.3" + chalk "^2.0.1" + jest-pnp-resolver "^1.2.1" + realpath-native "^1.1.0" + +jest-runner@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-24.9.0.tgz#574fafdbd54455c2b34b4bdf4365a23857fcdf42" + integrity sha512-KksJQyI3/0mhcfspnxxEOBueGrd5E4vV7ADQLT9ESaCzz02WnbdbKWIf5Mkaucoaj7obQckYPVX6JJhgUcoWWg== + dependencies: + "@jest/console" "^24.7.1" + "@jest/environment" "^24.9.0" + "@jest/test-result" "^24.9.0" + "@jest/types" "^24.9.0" + chalk "^2.4.2" + exit "^0.1.2" + graceful-fs "^4.1.15" + jest-config "^24.9.0" + jest-docblock "^24.3.0" + jest-haste-map "^24.9.0" + jest-jasmine2 "^24.9.0" + jest-leak-detector "^24.9.0" + jest-message-util "^24.9.0" + jest-resolve "^24.9.0" + jest-runtime "^24.9.0" + jest-util "^24.9.0" + jest-worker "^24.6.0" + source-map-support "^0.5.6" + throat "^4.0.0" + +jest-runtime@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-24.9.0.tgz#9f14583af6a4f7314a6a9d9f0226e1a781c8e4ac" + integrity sha512-8oNqgnmF3v2J6PVRM2Jfuj8oX3syKmaynlDMMKQ4iyzbQzIG6th5ub/lM2bCMTmoTKM3ykcUYI2Pw9xwNtjMnw== + dependencies: + "@jest/console" "^24.7.1" + "@jest/environment" "^24.9.0" + "@jest/source-map" "^24.3.0" + "@jest/transform" "^24.9.0" + "@jest/types" "^24.9.0" + "@types/yargs" "^13.0.0" + chalk "^2.0.1" + exit "^0.1.2" + glob "^7.1.3" + graceful-fs "^4.1.15" + jest-config "^24.9.0" + jest-haste-map "^24.9.0" + jest-message-util "^24.9.0" + jest-mock "^24.9.0" + jest-regex-util "^24.3.0" + jest-resolve "^24.9.0" + jest-snapshot "^24.9.0" + jest-util "^24.9.0" + jest-validate "^24.9.0" + realpath-native "^1.1.0" + slash "^2.0.0" + strip-bom "^3.0.0" + yargs "^13.3.0" + +jest-serializer@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-24.9.0.tgz#e6d7d7ef96d31e8b9079a714754c5d5c58288e73" + integrity sha512-DxYipDr8OvfrKH3Kel6NdED3OXxjvxXZ1uIY2I9OFbGg+vUkkg7AGvi65qbhbWNPvDckXmzMPbK3u3HaDO49bQ== + +jest-snapshot@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-24.9.0.tgz#ec8e9ca4f2ec0c5c87ae8f925cf97497b0e951ba" + integrity sha512-uI/rszGSs73xCM0l+up7O7a40o90cnrk429LOiK3aeTvfC0HHmldbd81/B7Ix81KSFe1lwkbl7GnBGG4UfuDew== + dependencies: + "@babel/types" "^7.0.0" + "@jest/types" "^24.9.0" + chalk "^2.0.1" + expect "^24.9.0" + jest-diff "^24.9.0" + jest-get-type "^24.9.0" + jest-matcher-utils "^24.9.0" + jest-message-util "^24.9.0" + jest-resolve "^24.9.0" + mkdirp "^0.5.1" + natural-compare "^1.4.0" + pretty-format "^24.9.0" + semver "^6.2.0" + +jest-util@^24.0.0, jest-util@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-24.9.0.tgz#7396814e48536d2e85a37de3e4c431d7cb140162" + integrity sha512-x+cZU8VRmOJxbA1K5oDBdxQmdq0OIdADarLxk0Mq+3XS4jgvhG/oKGWcIDCtPG0HgjxOYvF+ilPJQsAyXfbNOg== + dependencies: + "@jest/console" "^24.9.0" + "@jest/fake-timers" "^24.9.0" + "@jest/source-map" "^24.9.0" + "@jest/test-result" "^24.9.0" + "@jest/types" "^24.9.0" + callsites "^3.0.0" + chalk "^2.0.1" + graceful-fs "^4.1.15" + is-ci "^2.0.0" + mkdirp "^0.5.1" + slash "^2.0.0" + source-map "^0.6.0" + +jest-validate@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-24.9.0.tgz#0775c55360d173cd854e40180756d4ff52def8ab" + integrity sha512-HPIt6C5ACwiqSiwi+OfSSHbK8sG7akG8eATl+IPKaeIjtPOeBUd/g3J7DghugzxrGjI93qS/+RPKe1H6PqvhRQ== + dependencies: + "@jest/types" "^24.9.0" + camelcase "^5.3.1" + chalk "^2.0.1" + jest-get-type "^24.9.0" + leven "^3.1.0" + pretty-format "^24.9.0" + +jest-watch-typeahead@0.4.2: + version "0.4.2" + resolved "https://registry.yarnpkg.com/jest-watch-typeahead/-/jest-watch-typeahead-0.4.2.tgz#e5be959698a7fa2302229a5082c488c3c8780a4a" + integrity sha512-f7VpLebTdaXs81rg/oj4Vg/ObZy2QtGzAmGLNsqUS5G5KtSN68tFcIsbvNODfNyQxU78g7D8x77o3bgfBTR+2Q== + dependencies: + ansi-escapes "^4.2.1" + chalk "^2.4.1" + jest-regex-util "^24.9.0" + jest-watcher "^24.3.0" + slash "^3.0.0" + string-length "^3.1.0" + strip-ansi "^5.0.0" + +jest-watcher@^24.3.0, jest-watcher@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-24.9.0.tgz#4b56e5d1ceff005f5b88e528dc9afc8dd4ed2b3b" + integrity sha512-+/fLOfKPXXYJDYlks62/4R4GoT+GU1tYZed99JSCOsmzkkF7727RqKrjNAxtfO4YpGv11wybgRvCjR73lK2GZw== + dependencies: + "@jest/test-result" "^24.9.0" + "@jest/types" "^24.9.0" + "@types/yargs" "^13.0.0" + ansi-escapes "^3.0.0" + chalk "^2.0.1" + jest-util "^24.9.0" + string-length "^2.0.0" + +jest-worker@^24.6.0, jest-worker@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-24.9.0.tgz#5dbfdb5b2d322e98567898238a9697bcce67b3e5" + integrity sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw== + dependencies: + merge-stream "^2.0.0" + supports-color "^6.1.0" + +jest-worker@^25.1.0: + version "25.5.0" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-25.5.0.tgz#2611d071b79cea0f43ee57a3d118593ac1547db1" + integrity sha512-/dsSmUkIy5EBGfv/IjjqmFxrNAUpBERfGs1oHROyD7yxjG/w+t0GOJDX8O1k32ySmd7+a5IhnJU2qQFcJ4n1vw== + dependencies: + merge-stream "^2.0.0" + supports-color "^7.0.0" + +jest@24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/jest/-/jest-24.9.0.tgz#987d290c05a08b52c56188c1002e368edb007171" + integrity sha512-YvkBL1Zm7d2B1+h5fHEOdyjCG+sGMz4f8D86/0HiqJ6MB4MnDc8FgP5vdWsGnemOQro7lnYo8UakZ3+5A0jxGw== + dependencies: + import-local "^2.0.0" + jest-cli "^24.9.0" + +js-base64@^2.1.8: + version "2.5.1" + resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.5.1.tgz#1efa39ef2c5f7980bb1784ade4a8af2de3291121" + +"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-tokens@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" + +js-yaml@^3.13.1: + version "3.14.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482" + integrity sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +jsbn@~0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" + +jsdom@^11.5.1: + version "11.12.0" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-11.12.0.tgz#1a80d40ddd378a1de59656e9e6dc5a3ba8657bc8" + integrity sha512-y8Px43oyiBM13Zc1z780FrfNLJCXTL40EWlty/LXUtcjykRBNgLlCjWXpfSPBl2iv+N7koQN+dvqszHZgT/Fjw== + dependencies: + abab "^2.0.0" + acorn "^5.5.3" + acorn-globals "^4.1.0" + array-equal "^1.0.0" + cssom ">= 0.3.2 < 0.4.0" + cssstyle "^1.0.0" + data-urls "^1.0.0" + domexception "^1.0.1" + escodegen "^1.9.1" + html-encoding-sniffer "^1.0.2" + left-pad "^1.3.0" + nwsapi "^2.0.7" + parse5 "4.0.0" + pn "^1.1.0" + request "^2.87.0" + request-promise-native "^1.0.5" + sax "^1.2.4" + symbol-tree "^3.2.2" + tough-cookie "^2.3.4" + w3c-hr-time "^1.0.1" + webidl-conversions "^4.0.2" + whatwg-encoding "^1.0.3" + whatwg-mimetype "^2.1.0" + whatwg-url "^6.4.1" + ws "^5.2.0" + xml-name-validator "^3.0.0" + +jsdom@^14.1.0: + version "14.1.0" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-14.1.0.tgz#916463b6094956b0a6c1782c94e380cd30e1981b" + integrity sha512-O901mfJSuTdwU2w3Sn+74T+RnDVP+FuV5fH8tcPWyqrseRAb0s5xOtPgCFiPOtLcyK7CLIJwPyD83ZqQWvA5ng== + dependencies: + abab "^2.0.0" + acorn "^6.0.4" + acorn-globals "^4.3.0" + array-equal "^1.0.0" + cssom "^0.3.4" + cssstyle "^1.1.1" + data-urls "^1.1.0" + domexception "^1.0.1" + escodegen "^1.11.0" + html-encoding-sniffer "^1.0.2" + nwsapi "^2.1.3" + parse5 "5.1.0" + pn "^1.1.0" + request "^2.88.0" + request-promise-native "^1.0.5" + saxes "^3.1.9" + symbol-tree "^3.2.2" + tough-cookie "^2.5.0" + w3c-hr-time "^1.0.1" + w3c-xmlserializer "^1.1.2" + webidl-conversions "^4.0.2" + whatwg-encoding "^1.0.5" + whatwg-mimetype "^2.3.0" + whatwg-url "^7.0.0" + ws "^6.1.2" + xml-name-validator "^3.0.0" + +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + +jsesc@~0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" + +json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + +json-schema@0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= + +json-stable-stringify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af" + dependencies: + jsonify "~0.0.0" + +json-stringify-safe@~5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + +json3@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.2.tgz#3c0434743df93e2f5c42aee7b19bcb483575f4e1" + +json5@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" + dependencies: + minimist "^1.2.0" + +json5@^2.1.2: + version "2.1.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.3.tgz#c9b0f7fa9233bfe5807fe66fcf3a5617ed597d43" + integrity sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA== + dependencies: + minimist "^1.2.5" + +jsonfile@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" + integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss= + optionalDependencies: + graceful-fs "^4.1.6" + +jsonify@~0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" + +jsprim@^1.2.2: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" + dependencies: + assert-plus "1.0.0" + extsprintf "1.3.0" + json-schema "0.2.3" + verror "1.10.0" + +jsx-ast-utils@^2.2.1, jsx-ast-utils@^2.2.3: + version "2.3.0" + resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-2.3.0.tgz#edd727794ea284d7fda575015ed1b0cde0289ab6" + integrity sha512-3HNoc7nZ1hpZIKB3hJ7BlFRkzCx2BynRtfSwbkqZdpRdvAPsGMnzclPwrvDBS7/lalHTj21NwIeaEpysHBOudg== + dependencies: + array-includes "^3.1.1" + object.assign "^4.1.0" + +just-reduce-object@^1.0.3: + version "1.1.0" + resolved "https://registry.yarnpkg.com/just-reduce-object/-/just-reduce-object-1.1.0.tgz#d29d172264f8511c74462de30d72d5838b6967e6" + +killable@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892" + integrity sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg== + +kind-of@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-2.0.1.tgz#018ec7a4ce7e3a86cb9141be519d24c8faa981b5" + integrity sha1-AY7HpM5+OobLkUG+UZ0kyPqpgbU= + dependencies: + is-buffer "^1.0.2" + +kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" + dependencies: + is-buffer "^1.1.5" + +kind-of@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" + dependencies: + is-buffer "^1.1.5" + +kind-of@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" + +kind-of@^6.0.0, kind-of@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051" + +kleur@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" + integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== + +konva@~6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/konva/-/konva-6.0.0.tgz#9b3d13a4622f353c4ce736fbf1fa4b6483240649" + integrity sha512-YTwmtz3KzbzdC0KDRHWLzuk0KXB4NUdaQqytrxacXE1C39V6wCk7Nnu0wgq+GdGbG6m8A1qiEU9TSJ19qdIzDw== + +last-call-webpack-plugin@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/last-call-webpack-plugin/-/last-call-webpack-plugin-3.0.0.tgz#9742df0e10e3cf46e5c0381c2de90d3a7a2d7555" + integrity sha512-7KI2l2GIZa9p2spzPIVZBYyNKkN+e/SQPpnjlTiPhdbDW3F86tdKKELxKpzJ5sgU19wQWsACULZmpTPYHeWO5w== + dependencies: + lodash "^4.17.5" + webpack-sources "^1.1.0" + +lazy-cache@^0.2.3: + version "0.2.7" + resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-0.2.7.tgz#7feddf2dcb6edb77d11ef1d117ab5ffdf0ab1b65" + integrity sha1-f+3fLctu23fRHvHRF6tf/fCrG2U= + +lazy-cache@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e" + +lcid@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835" + dependencies: + invert-kv "^1.0.0" + +lcid@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/lcid/-/lcid-2.0.0.tgz#6ef5d2df60e52f82eb228a4c373e8d1f397253cf" + integrity sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA== + dependencies: + invert-kv "^2.0.0" + +left-pad@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.3.0.tgz#5b8a3a7765dfe001261dde915589e782f8c94d1e" + integrity sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA== + +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + +levenary@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/levenary/-/levenary-1.1.1.tgz#842a9ee98d2075aa7faeedbe32679e9205f46f77" + integrity sha512-mkAdOIt79FD6irqjYSs4rdbnlT5vRonMEvBVPVb3XmevfS8kgRXwfes0dhPdEtzTWD/1eNE/Bm/G1iRt6DcnQQ== + dependencies: + leven "^3.1.0" + +levn@^0.3.0, levn@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" + dependencies: + prelude-ls "~1.1.2" + type-check "~0.3.2" + +lines-and-columns@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" + integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= + +lint-staged@~10.2.2: + version "10.2.9" + resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-10.2.9.tgz#6013ecfa80829cd422446b545fd30a96bca3098c" + integrity sha512-ziRAuXEqvJLSXg43ezBpHxRW8FOJCXISaXU//BWrxRrp5cBdRkIx7g5IsB3OI45xYGE0S6cOacfekSjDyDKF2g== + dependencies: + chalk "^4.0.0" + cli-truncate "2.1.0" + commander "^5.1.0" + cosmiconfig "^6.0.0" + debug "^4.1.1" + dedent "^0.7.0" + enquirer "^2.3.5" + execa "^4.0.1" + listr2 "^2.1.0" + log-symbols "^4.0.0" + micromatch "^4.0.2" + normalize-path "^3.0.0" + please-upgrade-node "^3.2.0" + string-argv "0.3.1" + stringify-object "^3.3.0" + +listr2@^2.1.0: + version "2.1.3" + resolved "https://registry.yarnpkg.com/listr2/-/listr2-2.1.3.tgz#f527e197de12ad8c488c566921fa2da34cbc67f6" + integrity sha512-6oy3QhrZAlJGrG8oPcRp1hix1zUpb5AvyvZ5je979HCyf48tIj3Hn1TG5+rfyhz30t7HfySH/OIaVbwrI2kruA== + dependencies: + chalk "^4.0.0" + cli-truncate "^2.1.0" + figures "^3.2.0" + indent-string "^4.0.0" + log-update "^4.0.0" + p-map "^4.0.0" + rxjs "^6.5.5" + through "^2.3.8" + +load-json-file@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" + dependencies: + graceful-fs "^4.1.2" + parse-json "^2.2.0" + pify "^2.0.0" + pinkie-promise "^2.0.0" + strip-bom "^2.0.0" + +load-json-file@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8" + dependencies: + graceful-fs "^4.1.2" + parse-json "^2.2.0" + pify "^2.0.0" + strip-bom "^3.0.0" + +load-json-file@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b" + dependencies: + graceful-fs "^4.1.2" + parse-json "^4.0.0" + pify "^3.0.0" + strip-bom "^3.0.0" + +loader-fs-cache@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/loader-fs-cache/-/loader-fs-cache-1.0.3.tgz#f08657646d607078be2f0a032f8bd69dd6f277d9" + integrity sha512-ldcgZpjNJj71n+2Mf6yetz+c9bM4xpKtNds4LbqXzU/PTdeAX0g3ytnU1AJMEcTk2Lex4Smpe3Q/eCTsvUBxbA== + dependencies: + find-cache-dir "^0.1.1" + mkdirp "^0.5.1" + +loader-runner@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357" + integrity sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw== + +loader-utils@1.2.3, loader-utils@^1.1.0: + version "1.2.3" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7" + dependencies: + big.js "^5.2.2" + emojis-list "^2.0.0" + json5 "^1.0.1" + +loader-utils@^1.2.3, loader-utils@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613" + integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA== + dependencies: + big.js "^5.2.2" + emojis-list "^3.0.0" + json5 "^1.0.1" + +locate-path@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" + dependencies: + p-locate "^2.0.0" + path-exists "^3.0.0" + +locate-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" + integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== + dependencies: + p-locate "^3.0.0" + path-exists "^3.0.0" + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +lodash._reinterpolate@^3.0.0, lodash._reinterpolate@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" + +lodash.memoize@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + +lodash.sortby@^4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" + integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg= + +lodash.template@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.4.0.tgz#e73a0385c8355591746e020b99679c690e68fba0" + dependencies: + lodash._reinterpolate "~3.0.0" + lodash.templatesettings "^4.0.0" + +lodash.template@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.5.0.tgz#f976195cf3f347d0d5f52483569fe8031ccce8ab" + integrity sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A== + dependencies: + lodash._reinterpolate "^3.0.0" + lodash.templatesettings "^4.0.0" + +lodash.templatesettings@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-4.1.0.tgz#2b4d4e95ba440d915ff08bc899e4553666713316" + dependencies: + lodash._reinterpolate "~3.0.0" + +lodash.uniq@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" + +"lodash@>=3.5 <5", lodash@^4.0.0, lodash@~4.17.10: + version "4.17.11" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" + +lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.5: + version "4.17.15" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" + integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== + +log-symbols@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.0.0.tgz#69b3cc46d20f448eccdb75ea1fa733d9e821c920" + integrity sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA== + dependencies: + chalk "^4.0.0" + +log-update@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/log-update/-/log-update-4.0.0.tgz#589ecd352471f2a1c0c570287543a64dfd20e0a1" + integrity sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg== + dependencies: + ansi-escapes "^4.3.0" + cli-cursor "^3.1.0" + slice-ansi "^4.0.0" + wrap-ansi "^6.2.0" + +loglevel@^1.6.6: + version "1.6.8" + resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.8.tgz#8a25fb75d092230ecd4457270d80b54e28011171" + integrity sha512-bsU7+gc9AJ2SqpzxwU3+1fedl8zAntbtC5XYlt3s2j1hJcn2PsXSmgN8TaLG/J1/2mod4+cE/3vNL70/c1RNCA== + +loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + +loud-rejection@^1.0.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f" + dependencies: + currently-unhandled "^0.4.1" + signal-exit "^3.0.0" + +lower-case@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.1.tgz#39eeb36e396115cc05e29422eaea9e692c9408c7" + integrity sha512-LiWgfDLLb1dwbFQZsSglpRj+1ctGnayXz3Uv0/WO8n558JycT5fg6zkNcnW0G68Nn0aEldTFeEfmjCfmqry/rQ== + dependencies: + tslib "^1.10.0" + +lru-cache@^4.0.1: + version "4.1.5" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" + dependencies: + pseudomap "^1.0.2" + yallist "^2.1.2" + +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + +make-dir@^2.0.0, make-dir@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" + integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA== + dependencies: + pify "^4.0.1" + semver "^5.6.0" + +make-dir@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" + integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== + dependencies: + semver "^6.0.0" + +makeerror@1.0.x: + version "1.0.11" + resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.11.tgz#e01a5c9109f2af79660e4e8b9587790184f5a96c" + dependencies: + tmpl "1.0.x" + +mamacro@^0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/mamacro/-/mamacro-0.0.3.tgz#ad2c9576197c9f1abf308d0787865bd975a3f3e4" + integrity sha512-qMEwh+UujcQ+kbz3T6V+wAmO2U8veoq2w+3wY8MquqwVA3jChfwY+Tk52GZKDfACEPjuZ7r2oJLejwpt8jtwTA== + +map-age-cleaner@^0.1.1: + version "0.1.3" + resolved "https://registry.yarnpkg.com/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz#7d583a7306434c055fe474b0f45078e6e1b4b92a" + integrity sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w== + dependencies: + p-defer "^1.0.0" + +map-cache@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" + +map-obj@^1.0.0, map-obj@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" + +map-visit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" + dependencies: + object-visit "^1.0.0" + +md5.js@^1.3.4: + version "1.3.5" + resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" + dependencies: + hash-base "^3.0.0" + inherits "^2.0.1" + safe-buffer "^5.1.2" + +mdn-data@2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.4.tgz#699b3c38ac6f1d728091a64650b65d388502fd5b" + integrity sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA== + +mdn-data@2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.6.tgz#852dc60fcaa5daa2e8cf6c9189c440ed3e042978" + integrity sha512-rQvjv71olwNHgiTbfPZFkJtjNMciWgswYeciZhtvWLO8bmX3TnhyA62I6sTWOyZssWHJJjY6/KiWwqQsWWsqOA== + +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + +mem@^4.0.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/mem/-/mem-4.3.0.tgz#461af497bc4ae09608cdb2e60eefb69bff744178" + integrity sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w== + dependencies: + map-age-cleaner "^0.1.1" + mimic-fn "^2.0.0" + p-is-promise "^2.0.0" + +memory-fs@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" + integrity sha1-OpoguEYlI+RHz7x+i7gO1me/xVI= + dependencies: + errno "^0.1.3" + readable-stream "^2.0.1" + +memory-fs@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.5.0.tgz#324c01288b88652966d161db77838720845a8e3c" + integrity sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA== + dependencies: + errno "^0.1.3" + readable-stream "^2.0.1" + +memorystream@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/memorystream/-/memorystream-0.3.1.tgz#86d7090b30ce455d63fbae12dda51a47ddcaf9b2" + +meow@^3.7.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" + dependencies: + camelcase-keys "^2.0.0" + decamelize "^1.1.2" + loud-rejection "^1.0.0" + map-obj "^1.0.1" + minimist "^1.1.3" + normalize-package-data "^2.3.4" + object-assign "^4.0.1" + read-pkg-up "^1.0.1" + redent "^1.0.0" + trim-newlines "^1.0.0" + +merge-deep@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/merge-deep/-/merge-deep-3.0.2.tgz#f39fa100a4f1bd34ff29f7d2bf4508fbb8d83ad2" + integrity sha512-T7qC8kg4Zoti1cFd8Cr0M+qaZfOwjlPDEdZIIPPB2JZctjaPM4fX+i7HOId69tAti2fvO6X5ldfYUONDODsrkA== + dependencies: + arr-union "^3.1.0" + clone-deep "^0.2.4" + kind-of "^3.0.2" + +merge-descriptors@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +merge2@^1.2.3: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +methods@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + +microevent.ts@~0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/microevent.ts/-/microevent.ts-0.1.1.tgz#70b09b83f43df5172d0205a63025bce0f7357fa0" + integrity sha512-jo1OfR4TaEwd5HOrt5+tAZ9mqT4jmpNAusXtyfNzqVm9uiSYFZlKM1wYL4oU7azZW/PxQW53wM0S6OR1JHNa2g== + +micromatch@^3.1.10, micromatch@^3.1.4: + version "3.1.10" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" + dependencies: + arr-diff "^4.0.0" + array-unique "^0.3.2" + braces "^2.3.1" + define-property "^2.0.2" + extend-shallow "^3.0.2" + extglob "^2.0.4" + fragment-cache "^0.2.1" + kind-of "^6.0.2" + nanomatch "^1.2.9" + object.pick "^1.3.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.2" + +micromatch@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" + integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q== + dependencies: + braces "^3.0.1" + picomatch "^2.0.5" + +miller-rabin@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d" + dependencies: + bn.js "^4.0.0" + brorand "^1.0.1" + +mime-db@1.44.0, "mime-db@>= 1.43.0 < 2": + version "1.44.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92" + integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg== + +mime-db@~1.37.0: + version "1.37.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.37.0.tgz#0b6a0ce6fdbe9576e25f1f2d2fde8830dc0ad0d8" + +mime-types@^2.1.12, mime-types@~2.1.17, mime-types@~2.1.18, mime-types@~2.1.19: + version "2.1.21" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.21.tgz#28995aa1ecb770742fe6ae7e58f9181c744b3f96" + dependencies: + mime-db "~1.37.0" + +mime-types@~2.1.24: + version "2.1.27" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f" + integrity sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w== + dependencies: + mime-db "1.44.0" + +mime@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + +mime@^2.4.4: + version "2.4.6" + resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.6.tgz#e5b407c90db442f2beb5b162373d07b69affa4d1" + integrity sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA== + +mimic-fn@^2.0.0, mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +mini-create-react-context@^0.3.0: + version "0.3.2" + resolved "https://registry.yarnpkg.com/mini-create-react-context/-/mini-create-react-context-0.3.2.tgz#79fc598f283dd623da8e088b05db8cddab250189" + integrity sha512-2v+OeetEyliMt5VHMXsBhABoJ0/M4RCe7fatd/fBy6SMiKazUSEt3gxxypfnk2SHMkdBYvorHRoQxuGoiwbzAw== + dependencies: + "@babel/runtime" "^7.4.0" + gud "^1.0.0" + tiny-warning "^1.0.2" + +mini-css-extract-plugin@0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.9.0.tgz#47f2cf07aa165ab35733b1fc97d4c46c0564339e" + integrity sha512-lp3GeY7ygcgAmVIcRPBVhIkf8Us7FZjA+ILpal44qLdSu11wmjKQ3d9k15lfD7pO4esu9eUIAW7qiYIBppv40A== + dependencies: + loader-utils "^1.1.0" + normalize-url "1.9.1" + schema-utils "^1.0.0" + webpack-sources "^1.1.0" + +minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" + +minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" + +minimatch@3.0.4, minimatch@^3.0.4, minimatch@~3.0.2: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + dependencies: + brace-expansion "^1.1.7" + +minimist@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" + +minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" + +minimist@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" + integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== + +minipass-collect@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/minipass-collect/-/minipass-collect-1.0.2.tgz#22b813bf745dc6edba2576b940022ad6edc8c617" + integrity sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA== + dependencies: + minipass "^3.0.0" + +minipass-flush@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/minipass-flush/-/minipass-flush-1.0.5.tgz#82e7135d7e89a50ffe64610a787953c4c4cbb373" + integrity sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw== + dependencies: + minipass "^3.0.0" + +minipass-pipeline@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/minipass-pipeline/-/minipass-pipeline-1.2.3.tgz#55f7839307d74859d6e8ada9c3ebe72cec216a34" + integrity sha512-cFOknTvng5vqnwOpDsZTWhNll6Jf8o2x+/diplafmxpuIymAjzoOolZG0VvQf3V2HgqzJNhnuKHYp2BqDgz8IQ== + dependencies: + minipass "^3.0.0" + +minipass@^2.2.1, minipass@^2.3.4: + version "2.3.5" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.5.tgz#cacebe492022497f656b0f0f51e2682a9ed2d848" + dependencies: + safe-buffer "^5.1.2" + yallist "^3.0.0" + +minipass@^3.0.0, minipass@^3.1.1: + version "3.1.3" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.3.tgz#7d42ff1f39635482e15f9cdb53184deebd5815fd" + integrity sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg== + dependencies: + yallist "^4.0.0" + +minizlib@^1.1.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.2.1.tgz#dd27ea6136243c7c880684e8672bb3a45fd9b614" + dependencies: + minipass "^2.2.1" + +mississippi@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-3.0.0.tgz#ea0a3291f97e0b5e8776b363d5f0a12d94c67022" + integrity sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA== + dependencies: + concat-stream "^1.5.0" + duplexify "^3.4.2" + end-of-stream "^1.1.0" + flush-write-stream "^1.0.0" + from2 "^2.1.0" + parallel-transform "^1.1.0" + pump "^3.0.0" + pumpify "^1.3.3" + stream-each "^1.1.0" + through2 "^2.0.0" + +mixin-deep@^1.2.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.1.tgz#a49e7268dce1a0d9698e45326c5626df3543d0fe" + dependencies: + for-in "^1.0.2" + is-extendable "^1.0.1" + +mixin-object@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/mixin-object/-/mixin-object-2.0.1.tgz#4fb949441dab182540f1fe035ba60e1947a5e57e" + integrity sha1-T7lJRB2rGCVA8f4DW6YOGUel5X4= + dependencies: + for-in "^0.1.3" + is-extendable "^0.1.1" + +"mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" + dependencies: + minimist "0.0.8" + +mkdirp@^0.5.3: + version "0.5.5" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" + integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== + dependencies: + minimist "^1.2.5" + +move-concurrently@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92" + integrity sha1-viwAX9oy4LKa8fBdfEszIUxwH5I= + dependencies: + aproba "^1.1.1" + copy-concurrently "^1.0.0" + fs-write-stream-atomic "^1.0.8" + mkdirp "^0.5.1" + rimraf "^2.5.4" + run-queue "^1.0.3" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + +ms@2.1.1, ms@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" + +multicast-dns-service-types@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz#899f11d9686e5e05cb91b35d5f0e63b773cfc901" + +multicast-dns@^6.0.1: + version "6.2.3" + resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-6.2.3.tgz#a0ec7bd9055c4282f790c3c82f4e28db3b31b229" + dependencies: + dns-packet "^1.3.1" + thunky "^1.0.2" + +mute-stream@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" + integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== + +nan@^2.13.2: + version "2.14.1" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01" + integrity sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw== + +nan@^2.9.2: + version "2.12.1" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.12.1.tgz#7b1aa193e9aa86057e3c7bbd0ac448e770925552" + +nanomatch@^1.2.9: + version "1.2.13" + resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" + dependencies: + arr-diff "^4.0.0" + array-unique "^0.3.2" + define-property "^2.0.2" + extend-shallow "^3.0.2" + fragment-cache "^0.2.1" + is-windows "^1.0.2" + kind-of "^6.0.2" + object.pick "^1.3.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + +needle@^2.2.1: + version "2.2.4" + resolved "https://registry.yarnpkg.com/needle/-/needle-2.2.4.tgz#51931bff82533b1928b7d1d69e01f1b00ffd2a4e" + dependencies: + debug "^2.1.2" + iconv-lite "^0.4.4" + sax "^1.2.4" + +negotiator@0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" + +negotiator@0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" + integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== + +neo-async@^2.5.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.0.tgz#b9d15e4d71c6762908654b5183ed38b753340835" + +neo-async@^2.6.1: + version "2.6.1" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c" + integrity sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw== + +next-tick@1: + version "1.0.0" + resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" + +nice-try@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" + +no-case@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.3.tgz#c21b434c1ffe48b39087e86cfb4d2582e9df18f8" + integrity sha512-ehY/mVQCf9BL0gKfsJBvFJen+1V//U+0HQMPrWct40ixE4jnv0bfvxDbWtAHL9EcaPEOJHVVYKoQn1TlZUB8Tw== + dependencies: + lower-case "^2.0.1" + tslib "^1.10.0" + +node-forge@0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.9.0.tgz#d624050edbb44874adca12bb9a52ec63cb782579" + integrity sha512-7ASaDa3pD+lJ3WvXFsxekJQelBKRpne+GOVbLbtHYdd7pFspyeuJHnWfLplGf3SwKGbfs/aYl5V/JCIaHVUKKQ== + +node-gyp@^3.8.0: + version "3.8.0" + resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-3.8.0.tgz#540304261c330e80d0d5edce253a68cb3964218c" + dependencies: + fstream "^1.0.0" + glob "^7.0.3" + graceful-fs "^4.1.2" + mkdirp "^0.5.0" + nopt "2 || 3" + npmlog "0 || 1 || 2 || 3 || 4" + osenv "0" + request "^2.87.0" + rimraf "2" + semver "~5.3.0" + tar "^2.0.0" + which "1" + +node-int64@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" + +node-libs-browser@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.2.1.tgz#b64f513d18338625f90346d27b0d235e631f6425" + integrity sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q== + dependencies: + assert "^1.1.1" + browserify-zlib "^0.2.0" + buffer "^4.3.0" + console-browserify "^1.1.0" + constants-browserify "^1.0.0" + crypto-browserify "^3.11.0" + domain-browser "^1.1.1" + events "^3.0.0" + https-browserify "^1.0.0" + os-browserify "^0.3.0" + path-browserify "0.0.1" + process "^0.11.10" + punycode "^1.2.4" + querystring-es3 "^0.2.0" + readable-stream "^2.3.3" + stream-browserify "^2.0.1" + stream-http "^2.7.2" + string_decoder "^1.0.0" + timers-browserify "^2.0.4" + tty-browserify "0.0.0" + url "^0.11.0" + util "^0.11.0" + vm-browserify "^1.0.1" + +node-modules-regexp@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz#8d9dbe28964a4ac5712e9131642107c71e90ec40" + integrity sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA= + +node-notifier@^5.4.2: + version "5.4.3" + resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-5.4.3.tgz#cb72daf94c93904098e28b9c590fd866e464bd50" + integrity sha512-M4UBGcs4jeOK9CjTsYwkvH6/MzuUmGCyTW+kCY7uO+1ZVr0+FHGdPdIf5CCLqAaxnRrWidyoQlNkMIIVwbKB8Q== + dependencies: + growly "^1.3.0" + is-wsl "^1.1.0" + semver "^5.5.0" + shellwords "^0.1.1" + which "^1.3.0" + +node-pre-gyp@^0.10.0: + version "0.10.3" + resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.10.3.tgz#3070040716afdc778747b61b6887bf78880b80fc" + dependencies: + detect-libc "^1.0.2" + mkdirp "^0.5.1" + needle "^2.2.1" + nopt "^4.0.1" + npm-packlist "^1.1.6" + npmlog "^4.0.2" + rc "^1.2.7" + rimraf "^2.6.1" + semver "^5.3.0" + tar "^4" + +node-releases@^1.1.52, node-releases@^1.1.53: + version "1.1.58" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.58.tgz#8ee20eef30fa60e52755fcc0942def5a734fe935" + integrity sha512-NxBudgVKiRh/2aPWMgPR7bPTX0VPmGx5QBwCtdHitnqFE5/O8DeBXuIMH1nwNnw/aMo6AjOrpsHzfY3UbUJ7yg== + +node-sass-chokidar@~1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/node-sass-chokidar/-/node-sass-chokidar-1.4.0.tgz#3e5f2531e4606291b7d071761908a509075419d5" + integrity sha512-yQWApNiQhg7tMGSbcbeOY0P7v1nKofGBMpX0N4bnOjYlOaN1s+Lw+gqAwuqNu8iuGZobZJlDDCiC6NCIv5/ejA== + dependencies: + async-foreach "^0.1.3" + chokidar "^2.0.4" + get-stdin "^4.0.1" + glob "^7.0.3" + meow "^3.7.0" + node-sass "4.13" + sass-graph "^2.1.1" + stdout-stream "^1.4.0" + +node-sass@4.13: + version "4.13.1" + resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.13.1.tgz#9db5689696bb2eec2c32b98bfea4c7a2e992d0a3" + integrity sha512-TTWFx+ZhyDx1Biiez2nB0L3YrCZ/8oHagaDalbuBSlqXgUPsdkUSzJsVxeDO9LtPB49+Fh3WQl3slABo6AotNw== + dependencies: + async-foreach "^0.1.3" + chalk "^1.1.1" + cross-spawn "^3.0.0" + gaze "^1.0.0" + get-stdin "^4.0.1" + glob "^7.0.3" + in-publish "^2.0.0" + lodash "^4.17.15" + meow "^3.7.0" + mkdirp "^0.5.1" + nan "^2.13.2" + node-gyp "^3.8.0" + npmlog "^4.0.0" + request "^2.88.0" + sass-graph "^2.2.4" + stdout-stream "^1.4.0" + "true-case-path" "^1.0.2" + +"nopt@2 || 3": + version "3.0.6" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9" + dependencies: + abbrev "1" + +nopt@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d" + dependencies: + abbrev "1" + osenv "^0.1.4" + +normalize-package-data@^2.3.2, normalize-package-data@^2.3.4: + version "2.5.0" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" + dependencies: + hosted-git-info "^2.1.4" + resolve "^1.10.0" + semver "2 || 3 || 4 || 5" + validate-npm-package-license "^3.0.1" + +normalize-path@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" + dependencies: + remove-trailing-separator "^1.0.1" + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + +normalize-range@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" + +normalize-url@1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-1.9.1.tgz#2cc0d66b31ea23036458436e3620d85954c66c3c" + integrity sha1-LMDWazHqIwNkWENuNiDYWVTGbDw= + dependencies: + object-assign "^4.0.1" + prepend-http "^1.0.0" + query-string "^4.1.0" + sort-keys "^1.0.0" + +normalize-url@^3.0.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559" + integrity sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg== + +npm-bundled@^1.0.1: + version "1.0.6" + resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.6.tgz#e7ba9aadcef962bb61248f91721cd932b3fe6bdd" + +npm-packlist@^1.1.6: + version "1.3.0" + resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.3.0.tgz#7f01e8e44408341379ca98cfd756e7b29bd2626c" + dependencies: + ignore-walk "^3.0.1" + npm-bundled "^1.0.1" + +npm-run-all@~4.1.2: + version "4.1.5" + resolved "https://registry.yarnpkg.com/npm-run-all/-/npm-run-all-4.1.5.tgz#04476202a15ee0e2e214080861bff12a51d98fba" + integrity sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ== + dependencies: + ansi-styles "^3.2.1" + chalk "^2.4.1" + cross-spawn "^6.0.5" + memorystream "^0.3.1" + minimatch "^3.0.4" + pidtree "^0.3.0" + read-pkg "^3.0.0" + shell-quote "^1.6.1" + string.prototype.padend "^3.0.0" + +npm-run-path@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" + dependencies: + path-key "^2.0.0" + +npm-run-path@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + +"npmlog@0 || 1 || 2 || 3 || 4", npmlog@^4.0.0, npmlog@^4.0.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" + dependencies: + are-we-there-yet "~1.1.2" + console-control-strings "~1.1.0" + gauge "~2.7.3" + set-blocking "~2.0.0" + +nth-check@^1.0.2, nth-check@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c" + dependencies: + boolbase "~1.0.0" + +num2fraction@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede" + +number-is-nan@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" + +nwsapi@^2.0.7, nwsapi@^2.1.3: + version "2.2.0" + resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7" + integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ== + +oauth-sign@~0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" + +object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + +object-component@0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/object-component/-/object-component-0.0.3.tgz#f0c69aa50efc95b866c186f400a33769cb2f1291" + +object-copy@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" + dependencies: + copy-descriptor "^0.1.0" + define-property "^0.2.5" + kind-of "^3.0.3" + +object-hash@^2.0.1: + version "2.0.3" + resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.0.3.tgz#d12db044e03cd2ca3d77c0570d87225b02e1e6ea" + integrity sha512-JPKn0GMu+Fa3zt3Bmr66JhokJU5BaNBIh4ZeTlaCBzrBsOeXzwcKKAK1tbLiPKgvwmPXsDvvLHoWh5Bm7ofIYg== + +object-inspect@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.7.0.tgz#f4f6bd181ad77f006b5ece60bd0b6f398ff74a67" + integrity sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw== + +object-keys@^1.0.11, object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object-keys@^1.0.12: + version "1.1.0" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.0.tgz#11bd22348dd2e096a045ab06f6c85bcc340fa032" + +object-path@0.11.4: + version "0.11.4" + resolved "https://registry.yarnpkg.com/object-path/-/object-path-0.11.4.tgz#370ae752fbf37de3ea70a861c23bba8915691949" + integrity sha1-NwrnUvvzfePqcKhhwju6iRVpGUk= + +object-visit@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" + dependencies: + isobject "^3.0.0" + +object.assign@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" + integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w== + dependencies: + define-properties "^1.1.2" + function-bind "^1.1.1" + has-symbols "^1.0.0" + object-keys "^1.0.11" + +object.entries@^1.1.0, object.entries@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.2.tgz#bc73f00acb6b6bb16c203434b10f9a7e797d3add" + integrity sha512-BQdB9qKmb/HyNdMNWVr7O3+z5MUIx3aiegEIJqjMBbBf0YT9RRxTJSim4mzFqtyr7PDAHigq0N9dO0m0tRakQA== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.5" + has "^1.0.3" + +object.fromentries@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.2.tgz#4a09c9b9bb3843dd0f89acdb517a794d4f355ac9" + integrity sha512-r3ZiBH7MQppDJVLx6fhD618GKNG40CZYH9wgwdhKxBDDbQgjeWGGd4AtkZad84d291YxvWe7bJGuE65Anh0dxQ== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + function-bind "^1.1.1" + has "^1.0.3" + +object.getownpropertydescriptors@^2.0.3, object.getownpropertydescriptors@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz#369bf1f9592d8ab89d712dced5cb81c7c5352649" + integrity sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + +object.pick@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" + dependencies: + isobject "^3.0.1" + +object.values@^1.1.0, object.values@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.1.tgz#68a99ecde356b7e9295a3c5e0ce31dc8c953de5e" + integrity sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + function-bind "^1.1.1" + has "^1.0.3" + +obuf@^1.0.0, obuf@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" + +on-finished@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" + dependencies: + ee-first "1.1.1" + +on-headers@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" + integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== + +once@^1.3.0, once@^1.3.1, once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + dependencies: + wrappy "1" + +onetime@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.0.tgz#fff0f3c91617fe62bb50189636e99ac8a6df7be5" + integrity sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q== + dependencies: + mimic-fn "^2.1.0" + +open@^7.0.2: + version "7.0.4" + resolved "https://registry.yarnpkg.com/open/-/open-7.0.4.tgz#c28a9d315e5c98340bf979fdcb2e58664aa10d83" + integrity sha512-brSA+/yq+b08Hsr4c8fsEW2CRzk1BmfN3SAK/5VCHQ9bdoZJ4qa/+AfR0xHjlbbZUyPkUHs1b8x1RqdyZdkVqQ== + dependencies: + is-docker "^2.0.0" + is-wsl "^2.1.1" + +opencollective-postinstall@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz#7a0fff978f6dbfa4d006238fbac98ed4198c3259" + integrity sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q== + +opn@^5.5.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/opn/-/opn-5.5.0.tgz#fc7164fab56d235904c51c3b27da6758ca3b9bfc" + integrity sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA== + dependencies: + is-wsl "^1.1.0" + +optimize-css-assets-webpack-plugin@5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/optimize-css-assets-webpack-plugin/-/optimize-css-assets-webpack-plugin-5.0.3.tgz#e2f1d4d94ad8c0af8967ebd7cf138dcb1ef14572" + integrity sha512-q9fbvCRS6EYtUKKSwI87qm2IxlyJK5b4dygW1rKUBT6mMDhdG5e5bZT63v6tnJR9F9FB/H5a0HTmtw+laUBxKA== + dependencies: + cssnano "^4.1.10" + last-call-webpack-plugin "^3.0.0" + +optionator@^0.8.1: + version "0.8.2" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64" + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.4" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + wordwrap "~1.0.0" + +optionator@^0.8.3: + version "0.8.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" + integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.6" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + word-wrap "~1.2.3" + +original@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/original/-/original-1.0.2.tgz#e442a61cffe1c5fd20a65f3261c26663b303f25f" + integrity sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg== + dependencies: + url-parse "^1.4.3" + +os-browserify@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" + +os-homedir@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" + +os-locale@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9" + dependencies: + lcid "^1.0.0" + +os-locale@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-3.1.0.tgz#a802a6ee17f24c10483ab9935719cef4ed16bf1a" + integrity sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q== + dependencies: + execa "^1.0.0" + lcid "^2.0.0" + mem "^4.0.0" + +os-tmpdir@^1.0.0, os-tmpdir@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + +osenv@0, osenv@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" + dependencies: + os-homedir "^1.0.0" + os-tmpdir "^1.0.0" + +p-defer@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c" + integrity sha1-n26xgvbJqozXQwBKfU+WsZaw+ww= + +p-each-series@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-1.0.0.tgz#930f3d12dd1f50e7434457a22cd6f04ac6ad7f71" + integrity sha1-kw89Et0fUOdDRFeiLNbwSsatf3E= + dependencies: + p-reduce "^1.0.0" + +p-finally@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" + +p-is-promise@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-2.1.0.tgz#918cebaea248a62cf7ffab8e3bca8c5f882fc42e" + integrity sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg== + +p-limit@^1.1.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" + dependencies: + p-try "^1.0.0" + +p-limit@^2.0.0, p-limit@^2.2.0, p-limit@^2.2.2: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-locate@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" + dependencies: + p-limit "^1.1.0" + +p-locate@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" + integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== + dependencies: + p-limit "^2.0.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-map@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175" + integrity sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw== + +p-map@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-3.0.0.tgz#d704d9af8a2ba684e2600d9a215983d4141a979d" + integrity sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ== + dependencies: + aggregate-error "^3.0.0" + +p-map@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" + integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== + dependencies: + aggregate-error "^3.0.0" + +p-reduce@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-reduce/-/p-reduce-1.0.0.tgz#18c2b0dd936a4690a529f8231f58a0fdb6a47dfa" + integrity sha1-GMKw3ZNqRpClKfgjH1ig/bakffo= + +p-retry@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-3.0.1.tgz#316b4c8893e2c8dc1cfa891f406c4b422bebf328" + integrity sha512-XE6G4+YTTkT2a0UWb2kjZe8xNwf8bIbnqpc/IS/idOBVhyves0mK5OJgeocjx7q5pvX/6m23xuzVPYT1uGM73w== + dependencies: + retry "^0.12.0" + +p-try@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +pako@~1.0.5: + version "1.0.8" + resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.8.tgz#6844890aab9c635af868ad5fecc62e8acbba3ea4" + +parallel-transform@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/parallel-transform/-/parallel-transform-1.2.0.tgz#9049ca37d6cb2182c3b1d2c720be94d14a5814fc" + integrity sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg== + dependencies: + cyclist "^1.0.1" + inherits "^2.0.3" + readable-stream "^2.1.5" + +param-case@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/param-case/-/param-case-3.0.3.tgz#4be41f8399eff621c56eebb829a5e451d9801238" + integrity sha512-VWBVyimc1+QrzappRs7waeN2YmoZFCGXWASRYX1/rGHtXqEcrGEIDm+jqIwFa2fRXNgQEwrxaYuIrX0WcAguTA== + dependencies: + dot-case "^3.0.3" + tslib "^1.10.0" + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parse-asn1@^5.0.0: + version "5.1.3" + resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.3.tgz#1600c6cc0727365d68b97f3aa78939e735a75204" + dependencies: + asn1.js "^4.0.0" + browserify-aes "^1.0.0" + create-hash "^1.1.0" + evp_bytestokey "^1.0.0" + pbkdf2 "^3.0.3" + safe-buffer "^5.1.1" + +parse-json@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" + dependencies: + error-ex "^1.2.0" + +parse-json@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" + dependencies: + error-ex "^1.3.1" + json-parse-better-errors "^1.0.1" + +parse-json@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.0.0.tgz#73e5114c986d143efa3712d4ea24db9a4266f60f" + integrity sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-better-errors "^1.0.1" + lines-and-columns "^1.1.6" + +parse5@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-4.0.0.tgz#6d78656e3da8d78b4ec0b906f7c08ef1dfe3f608" + integrity sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA== + +parse5@5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.0.tgz#c59341c9723f414c452975564c7c00a68d58acd2" + integrity sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ== + +parseqs@0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/parseqs/-/parseqs-0.0.5.tgz#d5208a3738e46766e291ba2ea173684921a8b89d" + dependencies: + better-assert "~1.0.0" + +parseuri@0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/parseuri/-/parseuri-0.0.5.tgz#80204a50d4dbb779bfdc6ebe2778d90e4bce320a" + dependencies: + better-assert "~1.0.0" + +parseurl@~1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.2.tgz#fc289d4ed8993119460c156253262cdc8de65bf3" + +parseurl@~1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + +pascal-case@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/pascal-case/-/pascal-case-3.1.1.tgz#5ac1975133ed619281e88920973d2cd1f279de5f" + integrity sha512-XIeHKqIrsquVTQL2crjq3NfJUxmdLasn3TYOU0VBM+UX2a6ztAWBlJQBePLGY7VHW8+2dRadeIPK5+KImwTxQA== + dependencies: + no-case "^3.0.3" + tslib "^1.10.0" + +pascalcase@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" + +path-browserify@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a" + integrity sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ== + +path-dirname@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" + +path-exists@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b" + dependencies: + pinkie-promise "^2.0.0" + +path-exists@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + +path-is-inside@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" + +path-key@^2.0.0, path-key@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" + +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" + +path-to-regexp@0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" + +path-to-regexp@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.7.0.tgz#59fde0f435badacba103a84e9d3bc64e96b9937d" + dependencies: + isarray "0.0.1" + +path-type@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" + dependencies: + graceful-fs "^4.1.2" + pify "^2.0.0" + pinkie-promise "^2.0.0" + +path-type@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73" + dependencies: + pify "^2.0.0" + +path-type@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f" + dependencies: + pify "^3.0.0" + +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +pbkdf2@^3.0.3: + version "3.0.17" + resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.17.tgz#976c206530617b14ebb32114239f7b09336e93a6" + dependencies: + create-hash "^1.1.2" + create-hmac "^1.1.4" + ripemd160 "^2.0.1" + safe-buffer "^5.0.1" + sha.js "^2.4.8" + +performance-now@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" + +picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.2.1: + version "2.2.2" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" + integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== + +pidtree@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.3.0.tgz#f6fada10fccc9f99bf50e90d0b23d72c9ebc2e6b" + +pify@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" + +pify@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" + +pify@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" + integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== + +pinkie-promise@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" + dependencies: + pinkie "^2.0.0" + +pinkie@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" + +pirates@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.1.tgz#643a92caf894566f91b2b986d2c66950a8e2fb87" + integrity sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA== + dependencies: + node-modules-regexp "^1.0.0" + +pkg-dir@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-1.0.0.tgz#7a4b508a8d5bb2d629d447056ff4e9c9314cf3d4" + dependencies: + find-up "^1.0.0" + +pkg-dir@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b" + dependencies: + find-up "^2.1.0" + +pkg-dir@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-3.0.0.tgz#2749020f239ed990881b1f71210d51eb6523bea3" + integrity sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw== + dependencies: + find-up "^3.0.0" + +pkg-dir@^4.1.0, pkg-dir@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + +pkg-up@3.1.0, pkg-up@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-3.1.0.tgz#100ec235cc150e4fd42519412596a28512a0def5" + integrity sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA== + dependencies: + find-up "^3.0.0" + +pkg-up@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-2.0.0.tgz#c819ac728059a461cab1c3889a2be3c49a004d7f" + integrity sha1-yBmscoBZpGHKscOImivjxJoATX8= + dependencies: + find-up "^2.1.0" + +platform@^1.3.0: + version "1.3.5" + resolved "https://registry.yarnpkg.com/platform/-/platform-1.3.5.tgz#fb6958c696e07e2918d2eeda0f0bc9448d733444" + +please-upgrade-node@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz#aeddd3f994c933e4ad98b99d9a556efa0e2fe942" + integrity sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg== + dependencies: + semver-compare "^1.0.0" + +pn@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/pn/-/pn-1.1.0.tgz#e2f4cef0e219f463c179ab37463e4e1ecdccbafb" + integrity sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA== + +pnp-webpack-plugin@1.6.4: + version "1.6.4" + resolved "https://registry.yarnpkg.com/pnp-webpack-plugin/-/pnp-webpack-plugin-1.6.4.tgz#c9711ac4dc48a685dabafc86f8b6dd9f8df84149" + integrity sha512-7Wjy+9E3WwLOEL30D+m8TSTF7qJJUJLONBnwQp0518siuMxUQUbgZwssaFX+QKlZkjHZcw/IpZCt/H0srrntSg== + dependencies: + ts-pnp "^1.1.6" + +portfinder@^1.0.25: + version "1.0.26" + resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.26.tgz#475658d56ca30bed72ac7f1378ed350bd1b64e70" + integrity sha512-Xi7mKxJHHMI3rIUrnm/jjUgwhbYMkp/XKEcZX3aG4BrumLpq3nmoQMX+ClYnDZnZ/New7IatC1no5RX0zo1vXQ== + dependencies: + async "^2.6.2" + debug "^3.1.1" + mkdirp "^0.5.1" + +posix-character-classes@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" + +postcss-attribute-case-insensitive@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-4.0.2.tgz#d93e46b504589e94ac7277b0463226c68041a880" + integrity sha512-clkFxk/9pcdb4Vkn0hAHq3YnxBQ2p0CGD1dy24jN+reBck+EWxMbxSUqN4Yj7t0w8csl87K6p0gxBe1utkJsYA== + dependencies: + postcss "^7.0.2" + postcss-selector-parser "^6.0.2" + +postcss-browser-comments@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postcss-browser-comments/-/postcss-browser-comments-3.0.0.tgz#1248d2d935fb72053c8e1f61a84a57292d9f65e9" + integrity sha512-qfVjLfq7HFd2e0HW4s1dvU8X080OZdG46fFbIBFjW7US7YPDcWfRvdElvwMJr2LI6hMmD+7LnH2HcmXTs+uOig== + dependencies: + postcss "^7" + +postcss-calc@^7.0.1: + version "7.0.2" + resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-7.0.2.tgz#504efcd008ca0273120568b0792b16cdcde8aac1" + integrity sha512-rofZFHUg6ZIrvRwPeFktv06GdbDYLcGqh9EwiMutZg+a0oePCCw1zHOEiji6LCpyRcjTREtPASuUqeAvYlEVvQ== + dependencies: + postcss "^7.0.27" + postcss-selector-parser "^6.0.2" + postcss-value-parser "^4.0.2" + +postcss-color-functional-notation@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/postcss-color-functional-notation/-/postcss-color-functional-notation-2.0.1.tgz#5efd37a88fbabeb00a2966d1e53d98ced93f74e0" + integrity sha512-ZBARCypjEDofW4P6IdPVTLhDNXPRn8T2s1zHbZidW6rPaaZvcnCS2soYFIQJrMZSxiePJ2XIYTlcb2ztr/eT2g== + dependencies: + postcss "^7.0.2" + postcss-values-parser "^2.0.0" + +postcss-color-gray@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/postcss-color-gray/-/postcss-color-gray-5.0.0.tgz#532a31eb909f8da898ceffe296fdc1f864be8547" + integrity sha512-q6BuRnAGKM/ZRpfDascZlIZPjvwsRye7UDNalqVz3s7GDxMtqPY6+Q871liNxsonUw8oC61OG+PSaysYpl1bnw== + dependencies: + "@csstools/convert-colors" "^1.4.0" + postcss "^7.0.5" + postcss-values-parser "^2.0.0" + +postcss-color-hex-alpha@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/postcss-color-hex-alpha/-/postcss-color-hex-alpha-5.0.3.tgz#a8d9ca4c39d497c9661e374b9c51899ef0f87388" + integrity sha512-PF4GDel8q3kkreVXKLAGNpHKilXsZ6xuu+mOQMHWHLPNyjiUBOr75sp5ZKJfmv1MCus5/DWUGcK9hm6qHEnXYw== + dependencies: + postcss "^7.0.14" + postcss-values-parser "^2.0.1" + +postcss-color-mod-function@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/postcss-color-mod-function/-/postcss-color-mod-function-3.0.3.tgz#816ba145ac11cc3cb6baa905a75a49f903e4d31d" + integrity sha512-YP4VG+xufxaVtzV6ZmhEtc+/aTXH3d0JLpnYfxqTvwZPbJhWqp8bSY3nfNzNRFLgB4XSaBA82OE4VjOOKpCdVQ== + dependencies: + "@csstools/convert-colors" "^1.4.0" + postcss "^7.0.2" + postcss-values-parser "^2.0.0" + +postcss-color-rebeccapurple@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-4.0.1.tgz#c7a89be872bb74e45b1e3022bfe5748823e6de77" + integrity sha512-aAe3OhkS6qJXBbqzvZth2Au4V3KieR5sRQ4ptb2b2O8wgvB3SJBsdG+jsn2BZbbwekDG8nTfcCNKcSfe/lEy8g== + dependencies: + postcss "^7.0.2" + postcss-values-parser "^2.0.0" + +postcss-colormin@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-4.0.3.tgz#ae060bce93ed794ac71264f08132d550956bd381" + integrity sha512-WyQFAdDZpExQh32j0U0feWisZ0dmOtPl44qYmJKkq9xFWY3p+4qnRzCHeNrkeRhwPHz9bQ3mo0/yVkaply0MNw== + dependencies: + browserslist "^4.0.0" + color "^3.0.0" + has "^1.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-convert-values@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-4.0.1.tgz#ca3813ed4da0f812f9d43703584e449ebe189a7f" + integrity sha512-Kisdo1y77KUC0Jmn0OXU/COOJbzM8cImvw1ZFsBgBgMgb1iL23Zs/LXRe3r+EZqM3vGYKdQ2YJVQ5VkJI+zEJQ== + dependencies: + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-custom-media@^7.0.8: + version "7.0.8" + resolved "https://registry.yarnpkg.com/postcss-custom-media/-/postcss-custom-media-7.0.8.tgz#fffd13ffeffad73621be5f387076a28b00294e0c" + integrity sha512-c9s5iX0Ge15o00HKbuRuTqNndsJUbaXdiNsksnVH8H4gdc+zbLzr/UasOwNG6CTDpLFekVY4672eWdiiWu2GUg== + dependencies: + postcss "^7.0.14" + +postcss-custom-properties@^8.0.11: + version "8.0.11" + resolved "https://registry.yarnpkg.com/postcss-custom-properties/-/postcss-custom-properties-8.0.11.tgz#2d61772d6e92f22f5e0d52602df8fae46fa30d97" + integrity sha512-nm+o0eLdYqdnJ5abAJeXp4CEU1c1k+eB2yMCvhgzsds/e0umabFrN6HoTy/8Q4K5ilxERdl/JD1LO5ANoYBeMA== + dependencies: + postcss "^7.0.17" + postcss-values-parser "^2.0.1" + +postcss-custom-selectors@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/postcss-custom-selectors/-/postcss-custom-selectors-5.1.2.tgz#64858c6eb2ecff2fb41d0b28c9dd7b3db4de7fba" + integrity sha512-DSGDhqinCqXqlS4R7KGxL1OSycd1lydugJ1ky4iRXPHdBRiozyMHrdu0H3o7qNOCiZwySZTUI5MV0T8QhCLu+w== + dependencies: + postcss "^7.0.2" + postcss-selector-parser "^5.0.0-rc.3" + +postcss-dir-pseudo-class@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-5.0.0.tgz#6e3a4177d0edb3abcc85fdb6fbb1c26dabaeaba2" + integrity sha512-3pm4oq8HYWMZePJY+5ANriPs3P07q+LW6FAdTlkFH2XqDdP4HeeJYMOzn0HYLhRSjBO3fhiqSwwU9xEULSrPgw== + dependencies: + postcss "^7.0.2" + postcss-selector-parser "^5.0.0-rc.3" + +postcss-discard-comments@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-4.0.2.tgz#1fbabd2c246bff6aaad7997b2b0918f4d7af4033" + integrity sha512-RJutN259iuRf3IW7GZyLM5Sw4GLTOH8FmsXBnv8Ab/Tc2k4SR4qbV4DNbyyY4+Sjo362SyDmW2DQ7lBSChrpkg== + dependencies: + postcss "^7.0.0" + +postcss-discard-duplicates@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-4.0.2.tgz#3fe133cd3c82282e550fc9b239176a9207b784eb" + integrity sha512-ZNQfR1gPNAiXZhgENFfEglF93pciw0WxMkJeVmw8eF+JZBbMD7jp6C67GqJAXVZP2BWbOztKfbsdmMp/k8c6oQ== + dependencies: + postcss "^7.0.0" + +postcss-discard-empty@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-4.0.1.tgz#c8c951e9f73ed9428019458444a02ad90bb9f765" + integrity sha512-B9miTzbznhDjTfjvipfHoqbWKwd0Mj+/fL5s1QOz06wufguil+Xheo4XpOnc4NqKYBCNqqEzgPv2aPBIJLox0w== + dependencies: + postcss "^7.0.0" + +postcss-discard-overridden@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-4.0.1.tgz#652aef8a96726f029f5e3e00146ee7a4e755ff57" + integrity sha512-IYY2bEDD7g1XM1IDEsUT4//iEYCxAmP5oDSFMVU/JVvT7gh+l4fmjciLqGgwjdWpQIdb0Che2VX00QObS5+cTg== + dependencies: + postcss "^7.0.0" + +postcss-double-position-gradients@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/postcss-double-position-gradients/-/postcss-double-position-gradients-1.0.0.tgz#fc927d52fddc896cb3a2812ebc5df147e110522e" + integrity sha512-G+nV8EnQq25fOI8CH/B6krEohGWnF5+3A6H/+JEpOncu5dCnkS1QQ6+ct3Jkaepw1NGVqqOZH6lqrm244mCftA== + dependencies: + postcss "^7.0.5" + postcss-values-parser "^2.0.0" + +postcss-env-function@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/postcss-env-function/-/postcss-env-function-2.0.2.tgz#0f3e3d3c57f094a92c2baf4b6241f0b0da5365d7" + integrity sha512-rwac4BuZlITeUbiBq60h/xbLzXY43qOsIErngWa4l7Mt+RaSkT7QBjXVGTcBHupykkblHMDrBFh30zchYPaOUw== + dependencies: + postcss "^7.0.2" + postcss-values-parser "^2.0.0" + +postcss-flexbugs-fixes@4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/postcss-flexbugs-fixes/-/postcss-flexbugs-fixes-4.1.0.tgz#e094a9df1783e2200b7b19f875dcad3b3aff8b20" + integrity sha512-jr1LHxQvStNNAHlgco6PzY308zvLklh7SJVYuWUwyUQncofaAlD2l+P/gxKHOdqWKe7xJSkVLFF/2Tp+JqMSZA== + dependencies: + postcss "^7.0.0" + +postcss-focus-visible@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/postcss-focus-visible/-/postcss-focus-visible-4.0.0.tgz#477d107113ade6024b14128317ade2bd1e17046e" + integrity sha512-Z5CkWBw0+idJHSV6+Bgf2peDOFf/x4o+vX/pwcNYrWpXFrSfTkQ3JQ1ojrq9yS+upnAlNRHeg8uEwFTgorjI8g== + dependencies: + postcss "^7.0.2" + +postcss-focus-within@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postcss-focus-within/-/postcss-focus-within-3.0.0.tgz#763b8788596cee9b874c999201cdde80659ef680" + integrity sha512-W0APui8jQeBKbCGZudW37EeMCjDeVxKgiYfIIEo8Bdh5SpB9sxds/Iq8SEuzS0Q4YFOlG7EPFulbbxujpkrV2w== + dependencies: + postcss "^7.0.2" + +postcss-font-variant@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/postcss-font-variant/-/postcss-font-variant-4.0.0.tgz#71dd3c6c10a0d846c5eda07803439617bbbabacc" + integrity sha512-M8BFYKOvCrI2aITzDad7kWuXXTm0YhGdP9Q8HanmN4EF1Hmcgs1KK5rSHylt/lUJe8yLxiSwWAHdScoEiIxztg== + dependencies: + postcss "^7.0.2" + +postcss-gap-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/postcss-gap-properties/-/postcss-gap-properties-2.0.0.tgz#431c192ab3ed96a3c3d09f2ff615960f902c1715" + integrity sha512-QZSqDaMgXCHuHTEzMsS2KfVDOq7ZFiknSpkrPJY6jmxbugUPTuSzs/vuE5I3zv0WAS+3vhrlqhijiprnuQfzmg== + dependencies: + postcss "^7.0.2" + +postcss-image-set-function@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/postcss-image-set-function/-/postcss-image-set-function-3.0.1.tgz#28920a2f29945bed4c3198d7df6496d410d3f288" + integrity sha512-oPTcFFip5LZy8Y/whto91L9xdRHCWEMs3e1MdJxhgt4jy2WYXfhkng59fH5qLXSCPN8k4n94p1Czrfe5IOkKUw== + dependencies: + postcss "^7.0.2" + postcss-values-parser "^2.0.0" + +postcss-initial@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/postcss-initial/-/postcss-initial-3.0.2.tgz#f018563694b3c16ae8eaabe3c585ac6319637b2d" + integrity sha512-ugA2wKonC0xeNHgirR4D3VWHs2JcU08WAi1KFLVcnb7IN89phID6Qtg2RIctWbnvp1TM2BOmDtX8GGLCKdR8YA== + dependencies: + lodash.template "^4.5.0" + postcss "^7.0.2" + +postcss-lab-function@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/postcss-lab-function/-/postcss-lab-function-2.0.1.tgz#bb51a6856cd12289ab4ae20db1e3821ef13d7d2e" + integrity sha512-whLy1IeZKY+3fYdqQFuDBf8Auw+qFuVnChWjmxm/UhHWqNHZx+B99EwxTvGYmUBqe3Fjxs4L1BoZTJmPu6usVg== + dependencies: + "@csstools/convert-colors" "^1.4.0" + postcss "^7.0.2" + postcss-values-parser "^2.0.0" + +postcss-load-config@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-2.1.0.tgz#c84d692b7bb7b41ddced94ee62e8ab31b417b003" + integrity sha512-4pV3JJVPLd5+RueiVVB+gFOAa7GWc25XQcMp86Zexzke69mKf6Nx9LRcQywdz7yZI9n1udOxmLuAwTBypypF8Q== + dependencies: + cosmiconfig "^5.0.0" + import-cwd "^2.0.0" + +postcss-loader@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-3.0.0.tgz#6b97943e47c72d845fa9e03f273773d4e8dd6c2d" + integrity sha512-cLWoDEY5OwHcAjDnkyRQzAXfs2jrKjXpO/HQFcc5b5u/r7aa471wdmChmwfnv7x2u840iat/wi0lQ5nbRgSkUA== + dependencies: + loader-utils "^1.1.0" + postcss "^7.0.0" + postcss-load-config "^2.0.0" + schema-utils "^1.0.0" + +postcss-logical@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postcss-logical/-/postcss-logical-3.0.0.tgz#2495d0f8b82e9f262725f75f9401b34e7b45d5b5" + integrity sha512-1SUKdJc2vuMOmeItqGuNaC+N8MzBWFWEkAnRnLpFYj1tGGa7NqyVBujfRtgNa2gXR+6RkGUiB2O5Vmh7E2RmiA== + dependencies: + postcss "^7.0.2" + +postcss-media-minmax@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/postcss-media-minmax/-/postcss-media-minmax-4.0.0.tgz#b75bb6cbc217c8ac49433e12f22048814a4f5ed5" + integrity sha512-fo9moya6qyxsjbFAYl97qKO9gyre3qvbMnkOZeZwlsW6XYFsvs2DMGDlchVLfAd8LHPZDxivu/+qW2SMQeTHBw== + dependencies: + postcss "^7.0.2" + +postcss-merge-longhand@^4.0.11: + version "4.0.11" + resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-4.0.11.tgz#62f49a13e4a0ee04e7b98f42bb16062ca2549e24" + integrity sha512-alx/zmoeXvJjp7L4mxEMjh8lxVlDFX1gqWHzaaQewwMZiVhLo42TEClKaeHbRf6J7j82ZOdTJ808RtN0ZOZwvw== + dependencies: + css-color-names "0.0.4" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + stylehacks "^4.0.0" + +postcss-merge-rules@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-4.0.3.tgz#362bea4ff5a1f98e4075a713c6cb25aefef9a650" + integrity sha512-U7e3r1SbvYzO0Jr3UT/zKBVgYYyhAz0aitvGIYOYK5CPmkNih+WDSsS5tvPrJ8YMQYlEMvsZIiqmn7HdFUaeEQ== + dependencies: + browserslist "^4.0.0" + caniuse-api "^3.0.0" + cssnano-util-same-parent "^4.0.0" + postcss "^7.0.0" + postcss-selector-parser "^3.0.0" + vendors "^1.0.0" + +postcss-minify-font-values@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-4.0.2.tgz#cd4c344cce474343fac5d82206ab2cbcb8afd5a6" + integrity sha512-j85oO6OnRU9zPf04+PZv1LYIYOprWm6IA6zkXkrJXyRveDEuQggG6tvoy8ir8ZwjLxLuGfNkCZEQG7zan+Hbtg== + dependencies: + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-minify-gradients@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-4.0.2.tgz#93b29c2ff5099c535eecda56c4aa6e665a663471" + integrity sha512-qKPfwlONdcf/AndP1U8SJ/uzIJtowHlMaSioKzebAXSG4iJthlWC9iSWznQcX4f66gIWX44RSA841HTHj3wK+Q== + dependencies: + cssnano-util-get-arguments "^4.0.0" + is-color-stop "^1.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-minify-params@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-4.0.2.tgz#6b9cef030c11e35261f95f618c90036d680db874" + integrity sha512-G7eWyzEx0xL4/wiBBJxJOz48zAKV2WG3iZOqVhPet/9geefm/Px5uo1fzlHu+DOjT+m0Mmiz3jkQzVHe6wxAWg== + dependencies: + alphanum-sort "^1.0.0" + browserslist "^4.0.0" + cssnano-util-get-arguments "^4.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + uniqs "^2.0.0" + +postcss-minify-selectors@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-4.0.2.tgz#e2e5eb40bfee500d0cd9243500f5f8ea4262fbd8" + integrity sha512-D5S1iViljXBj9kflQo4YutWnJmwm8VvIsU1GeXJGiG9j8CIg9zs4voPMdQDUmIxetUOh60VilsNzCiAFTOqu3g== + dependencies: + alphanum-sort "^1.0.0" + has "^1.0.0" + postcss "^7.0.0" + postcss-selector-parser "^3.0.0" + +postcss-modules-extract-imports@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz#818719a1ae1da325f9832446b01136eeb493cd7e" + integrity sha512-LaYLDNS4SG8Q5WAWqIJgdHPJrDDr/Lv775rMBFUbgjTz6j34lUznACHcdRWroPvXANP2Vj7yNK57vp9eFqzLWQ== + dependencies: + postcss "^7.0.5" + +postcss-modules-local-by-default@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.2.tgz#e8a6561be914aaf3c052876377524ca90dbb7915" + integrity sha512-jM/V8eqM4oJ/22j0gx4jrp63GSvDH6v86OqyTHHUvk4/k1vceipZsaymiZ5PvocqZOl5SFHiFJqjs3la0wnfIQ== + dependencies: + icss-utils "^4.1.1" + postcss "^7.0.16" + postcss-selector-parser "^6.0.2" + postcss-value-parser "^4.0.0" + +postcss-modules-scope@^2.1.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-2.2.0.tgz#385cae013cc7743f5a7d7602d1073a89eaae62ee" + integrity sha512-YyEgsTMRpNd+HmyC7H/mh3y+MeFWevy7V1evVhJWewmMbjDHIbZbOXICC2y+m1xI1UVfIT1HMW/O04Hxyu9oXQ== + dependencies: + postcss "^7.0.6" + postcss-selector-parser "^6.0.0" + +postcss-modules-values@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-3.0.0.tgz#5b5000d6ebae29b4255301b4a3a54574423e7f10" + integrity sha512-1//E5jCBrZ9DmRX+zCtmQtRSV6PV42Ix7Bzj9GbwJceduuf7IqP8MgeTXuRDHOWj2m0VzZD5+roFWDuU8RQjcg== + dependencies: + icss-utils "^4.0.0" + postcss "^7.0.6" + +postcss-nesting@^7.0.0: + version "7.0.1" + resolved "https://registry.yarnpkg.com/postcss-nesting/-/postcss-nesting-7.0.1.tgz#b50ad7b7f0173e5b5e3880c3501344703e04c052" + integrity sha512-FrorPb0H3nuVq0Sff7W2rnc3SmIcruVC6YwpcS+k687VxyxO33iE1amna7wHuRVzM8vfiYofXSBHNAZ3QhLvYg== + dependencies: + postcss "^7.0.2" + +postcss-normalize-charset@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-4.0.1.tgz#8b35add3aee83a136b0471e0d59be58a50285dd4" + integrity sha512-gMXCrrlWh6G27U0hF3vNvR3w8I1s2wOBILvA87iNXaPvSNo5uZAMYsZG7XjCUf1eVxuPfyL4TJ7++SGZLc9A3g== + dependencies: + postcss "^7.0.0" + +postcss-normalize-display-values@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.2.tgz#0dbe04a4ce9063d4667ed2be476bb830c825935a" + integrity sha512-3F2jcsaMW7+VtRMAqf/3m4cPFhPD3EFRgNs18u+k3lTJJlVe7d0YPO+bnwqo2xg8YiRpDXJI2u8A0wqJxMsQuQ== + dependencies: + cssnano-util-get-match "^4.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-normalize-positions@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-4.0.2.tgz#05f757f84f260437378368a91f8932d4b102917f" + integrity sha512-Dlf3/9AxpxE+NF1fJxYDeggi5WwV35MXGFnnoccP/9qDtFrTArZ0D0R+iKcg5WsUd8nUYMIl8yXDCtcrT8JrdA== + dependencies: + cssnano-util-get-arguments "^4.0.0" + has "^1.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-normalize-repeat-style@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-4.0.2.tgz#c4ebbc289f3991a028d44751cbdd11918b17910c" + integrity sha512-qvigdYYMpSuoFs3Is/f5nHdRLJN/ITA7huIoCyqqENJe9PvPmLhNLMu7QTjPdtnVf6OcYYO5SHonx4+fbJE1+Q== + dependencies: + cssnano-util-get-arguments "^4.0.0" + cssnano-util-get-match "^4.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-normalize-string@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-4.0.2.tgz#cd44c40ab07a0c7a36dc5e99aace1eca4ec2690c" + integrity sha512-RrERod97Dnwqq49WNz8qo66ps0swYZDSb6rM57kN2J+aoyEAJfZ6bMx0sx/F9TIEX0xthPGCmeyiam/jXif0eA== + dependencies: + has "^1.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-normalize-timing-functions@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-4.0.2.tgz#8e009ca2a3949cdaf8ad23e6b6ab99cb5e7d28d9" + integrity sha512-acwJY95edP762e++00Ehq9L4sZCEcOPyaHwoaFOhIwWCDfik6YvqsYNxckee65JHLKzuNSSmAdxwD2Cud1Z54A== + dependencies: + cssnano-util-get-match "^4.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-normalize-unicode@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-4.0.1.tgz#841bd48fdcf3019ad4baa7493a3d363b52ae1cfb" + integrity sha512-od18Uq2wCYn+vZ/qCOeutvHjB5jm57ToxRaMeNuf0nWVHaP9Hua56QyMF6fs/4FSUnVIw0CBPsU0K4LnBPwYwg== + dependencies: + browserslist "^4.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-normalize-url@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-4.0.1.tgz#10e437f86bc7c7e58f7b9652ed878daaa95faae1" + integrity sha512-p5oVaF4+IHwu7VpMan/SSpmpYxcJMtkGppYf0VbdH5B6hN8YNmVyJLuY9FmLQTzY3fag5ESUUHDqM+heid0UVA== + dependencies: + is-absolute-url "^2.0.0" + normalize-url "^3.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-normalize-whitespace@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-4.0.2.tgz#bf1d4070fe4fcea87d1348e825d8cc0c5faa7d82" + integrity sha512-tO8QIgrsI3p95r8fyqKV+ufKlSHh9hMJqACqbv2XknufqEDhDvbguXGBBqxw9nsQoXWf0qOqppziKJKHMD4GtA== + dependencies: + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-normalize@8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/postcss-normalize/-/postcss-normalize-8.0.1.tgz#90e80a7763d7fdf2da6f2f0f82be832ce4f66776" + integrity sha512-rt9JMS/m9FHIRroDDBGSMsyW1c0fkvOJPy62ggxSHUldJO7B195TqFMqIf+lY5ezpDcYOV4j86aUp3/XbxzCCQ== + dependencies: + "@csstools/normalize.css" "^10.1.0" + browserslist "^4.6.2" + postcss "^7.0.17" + postcss-browser-comments "^3.0.0" + sanitize.css "^10.0.0" + +postcss-ordered-values@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-4.1.2.tgz#0cf75c820ec7d5c4d280189559e0b571ebac0eee" + integrity sha512-2fCObh5UanxvSxeXrtLtlwVThBvHn6MQcu4ksNT2tsaV2Fg76R2CV98W7wNSlX+5/pFwEyaDwKLLoEV7uRybAw== + dependencies: + cssnano-util-get-arguments "^4.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-overflow-shorthand@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/postcss-overflow-shorthand/-/postcss-overflow-shorthand-2.0.0.tgz#31ecf350e9c6f6ddc250a78f0c3e111f32dd4c30" + integrity sha512-aK0fHc9CBNx8jbzMYhshZcEv8LtYnBIRYQD5i7w/K/wS9c2+0NSR6B3OVMu5y0hBHYLcMGjfU+dmWYNKH0I85g== + dependencies: + postcss "^7.0.2" + +postcss-page-break@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/postcss-page-break/-/postcss-page-break-2.0.0.tgz#add52d0e0a528cabe6afee8b46e2abb277df46bf" + integrity sha512-tkpTSrLpfLfD9HvgOlJuigLuk39wVTbbd8RKcy8/ugV2bNBUW3xU+AIqyxhDrQr1VUj1RmyJrBn1YWrqUm9zAQ== + dependencies: + postcss "^7.0.2" + +postcss-place@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-place/-/postcss-place-4.0.1.tgz#e9f39d33d2dc584e46ee1db45adb77ca9d1dcc62" + integrity sha512-Zb6byCSLkgRKLODj/5mQugyuj9bvAAw9LqJJjgwz5cYryGeXfFZfSXoP1UfveccFmeq0b/2xxwcTEVScnqGxBg== + dependencies: + postcss "^7.0.2" + postcss-values-parser "^2.0.0" + +postcss-preset-env@6.7.0: + version "6.7.0" + resolved "https://registry.yarnpkg.com/postcss-preset-env/-/postcss-preset-env-6.7.0.tgz#c34ddacf8f902383b35ad1e030f178f4cdf118a5" + integrity sha512-eU4/K5xzSFwUFJ8hTdTQzo2RBLbDVt83QZrAvI07TULOkmyQlnYlpwep+2yIK+K+0KlZO4BvFcleOCCcUtwchg== + dependencies: + autoprefixer "^9.6.1" + browserslist "^4.6.4" + caniuse-lite "^1.0.30000981" + css-blank-pseudo "^0.1.4" + css-has-pseudo "^0.10.0" + css-prefers-color-scheme "^3.1.1" + cssdb "^4.4.0" + postcss "^7.0.17" + postcss-attribute-case-insensitive "^4.0.1" + postcss-color-functional-notation "^2.0.1" + postcss-color-gray "^5.0.0" + postcss-color-hex-alpha "^5.0.3" + postcss-color-mod-function "^3.0.3" + postcss-color-rebeccapurple "^4.0.1" + postcss-custom-media "^7.0.8" + postcss-custom-properties "^8.0.11" + postcss-custom-selectors "^5.1.2" + postcss-dir-pseudo-class "^5.0.0" + postcss-double-position-gradients "^1.0.0" + postcss-env-function "^2.0.2" + postcss-focus-visible "^4.0.0" + postcss-focus-within "^3.0.0" + postcss-font-variant "^4.0.0" + postcss-gap-properties "^2.0.0" + postcss-image-set-function "^3.0.1" + postcss-initial "^3.0.0" + postcss-lab-function "^2.0.1" + postcss-logical "^3.0.0" + postcss-media-minmax "^4.0.0" + postcss-nesting "^7.0.0" + postcss-overflow-shorthand "^2.0.0" + postcss-page-break "^2.0.0" + postcss-place "^4.0.1" + postcss-pseudo-class-any-link "^6.0.0" + postcss-replace-overflow-wrap "^3.0.0" + postcss-selector-matches "^4.0.0" + postcss-selector-not "^4.0.0" + +postcss-pseudo-class-any-link@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-6.0.0.tgz#2ed3eed393b3702879dec4a87032b210daeb04d1" + integrity sha512-lgXW9sYJdLqtmw23otOzrtbDXofUdfYzNm4PIpNE322/swES3VU9XlXHeJS46zT2onFO7V1QFdD4Q9LiZj8mew== + dependencies: + postcss "^7.0.2" + postcss-selector-parser "^5.0.0-rc.3" + +postcss-reduce-initial@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-4.0.3.tgz#7fd42ebea5e9c814609639e2c2e84ae270ba48df" + integrity sha512-gKWmR5aUulSjbzOfD9AlJiHCGH6AEVLaM0AV+aSioxUDd16qXP1PCh8d1/BGVvpdWn8k/HiK7n6TjeoXN1F7DA== + dependencies: + browserslist "^4.0.0" + caniuse-api "^3.0.0" + has "^1.0.0" + postcss "^7.0.0" + +postcss-reduce-transforms@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-4.0.2.tgz#17efa405eacc6e07be3414a5ca2d1074681d4e29" + integrity sha512-EEVig1Q2QJ4ELpJXMZR8Vt5DQx8/mo+dGWSR7vWXqcob2gQLyQGsionYcGKATXvQzMPn6DSN1vTN7yFximdIAg== + dependencies: + cssnano-util-get-match "^4.0.0" + has "^1.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + +postcss-replace-overflow-wrap@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-3.0.0.tgz#61b360ffdaedca84c7c918d2b0f0d0ea559ab01c" + integrity sha512-2T5hcEHArDT6X9+9dVSPQdo7QHzG4XKclFT8rU5TzJPDN7RIRTbO9c4drUISOVemLj03aezStHCR2AIcr8XLpw== + dependencies: + postcss "^7.0.2" + +postcss-safe-parser@4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-safe-parser/-/postcss-safe-parser-4.0.1.tgz#8756d9e4c36fdce2c72b091bbc8ca176ab1fcdea" + integrity sha512-xZsFA3uX8MO3yAda03QrG3/Eg1LN3EPfjjf07vke/46HERLZyHrTsQ9E1r1w1W//fWEhtYNndo2hQplN2cVpCQ== + dependencies: + postcss "^7.0.0" + +postcss-selector-matches@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/postcss-selector-matches/-/postcss-selector-matches-4.0.0.tgz#71c8248f917ba2cc93037c9637ee09c64436fcff" + integrity sha512-LgsHwQR/EsRYSqlwdGzeaPKVT0Ml7LAT6E75T8W8xLJY62CE4S/l03BWIt3jT8Taq22kXP08s2SfTSzaraoPww== + dependencies: + balanced-match "^1.0.0" + postcss "^7.0.2" + +postcss-selector-not@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/postcss-selector-not/-/postcss-selector-not-4.0.0.tgz#c68ff7ba96527499e832724a2674d65603b645c0" + integrity sha512-W+bkBZRhqJaYN8XAnbbZPLWMvZD1wKTu0UxtFKdhtGjWYmxhkUneoeOhRJKdAE5V7ZTlnbHfCR+6bNwK9e1dTQ== + dependencies: + balanced-match "^1.0.0" + postcss "^7.0.2" + +postcss-selector-parser@^3.0.0: + version "3.1.2" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz#b310f5c4c0fdaf76f94902bbaa30db6aa84f5270" + integrity sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA== + dependencies: + dot-prop "^5.2.0" + indexes-of "^1.0.1" + uniq "^1.0.1" + +postcss-selector-parser@^5.0.0-rc.3, postcss-selector-parser@^5.0.0-rc.4: + version "5.0.0" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz#249044356697b33b64f1a8f7c80922dddee7195c" + integrity sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ== + dependencies: + cssesc "^2.0.0" + indexes-of "^1.0.1" + uniq "^1.0.1" + +postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz#934cf799d016c83411859e09dcecade01286ec5c" + integrity sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg== + dependencies: + cssesc "^3.0.0" + indexes-of "^1.0.1" + uniq "^1.0.1" + +postcss-svgo@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-4.0.2.tgz#17b997bc711b333bab143aaed3b8d3d6e3d38258" + integrity sha512-C6wyjo3VwFm0QgBy+Fu7gCYOkCmgmClghO+pjcxvrcBKtiKt0uCF+hvbMO1fyv5BMImRK90SMb+dwUnfbGd+jw== + dependencies: + is-svg "^3.0.0" + postcss "^7.0.0" + postcss-value-parser "^3.0.0" + svgo "^1.0.0" + +postcss-unique-selectors@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-4.0.1.tgz#9446911f3289bfd64c6d680f073c03b1f9ee4bac" + integrity sha512-+JanVaryLo9QwZjKrmJgkI4Fn8SBgRO6WXQBJi7KiAVPlmxikB5Jzc4EvXMT2H0/m0RjrVVm9rGNhZddm/8Spg== + dependencies: + alphanum-sort "^1.0.0" + postcss "^7.0.0" + uniqs "^2.0.0" + +postcss-value-parser@^3.0.0: + version "3.3.1" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281" + integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ== + +postcss-value-parser@^4.0.0, postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb" + integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ== + +postcss-values-parser@^2.0.0, postcss-values-parser@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/postcss-values-parser/-/postcss-values-parser-2.0.1.tgz#da8b472d901da1e205b47bdc98637b9e9e550e5f" + integrity sha512-2tLuBsA6P4rYTNKCXYG/71C7j1pU6pK503suYOmn4xYrQIzW+opD+7FAFNuGSdZC/3Qfy334QbeMu7MEb8gOxg== + dependencies: + flatten "^1.0.2" + indexes-of "^1.0.1" + uniq "^1.0.1" + +postcss@7.0.21: + version "7.0.21" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.21.tgz#06bb07824c19c2021c5d056d5b10c35b989f7e17" + integrity sha512-uIFtJElxJo29QC753JzhidoAhvp/e/Exezkdhfmt8AymWT6/5B7W1WmponYWkHk2eg6sONyTch0A3nkMPun3SQ== + dependencies: + chalk "^2.4.2" + source-map "^0.6.1" + supports-color "^6.1.0" + +postcss@^7, postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.16, postcss@^7.0.17, postcss@^7.0.2, postcss@^7.0.23, postcss@^7.0.27, postcss@^7.0.30, postcss@^7.0.5, postcss@^7.0.6: + version "7.0.32" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.32.tgz#4310d6ee347053da3433db2be492883d62cec59d" + integrity sha512-03eXong5NLnNCD05xscnGKGDZ98CyzoqPSMjOe6SuoQY7Z2hIj0Ld1g/O/UQRuOle2aRtiIRDg9tDcTGAkLfKw== + dependencies: + chalk "^2.4.2" + source-map "^0.6.1" + supports-color "^6.1.0" + +prelude-ls@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" + +prepend-http@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" + +prettier@~2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.0.5.tgz#d6d56282455243f2f92cc1716692c08aa31522d4" + integrity sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg== + +pretty-bytes@^5.1.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.3.0.tgz#f2849e27db79fb4d6cfe24764fc4134f165989f2" + integrity sha512-hjGrh+P926p4R4WbaB6OckyRtO0F0/lQBiT+0gnxjV+5kjPBrfVBFCsCLbMqVQeydvIoouYTCmmEURiH3R1Bdg== + +pretty-error@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/pretty-error/-/pretty-error-2.1.1.tgz#5f4f87c8f91e5ae3f3ba87ab4cf5e03b1a17f1a3" + integrity sha1-X0+HyPkeWuPzuoerTPXgOxoX8aM= + dependencies: + renderkid "^2.0.1" + utila "~0.4" + +pretty-format@^24.9.0: + version "24.9.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.9.0.tgz#12fac31b37019a4eea3c11aa9a959eb7628aa7c9" + integrity sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA== + dependencies: + "@jest/types" "^24.9.0" + ansi-regex "^4.0.0" + ansi-styles "^3.2.0" + react-is "^16.8.4" + +private@^0.1.8: + version "0.1.8" + resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" + +process-nextick-args@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" + +process@^0.11.10: + version "0.11.10" + resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" + +progress@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" + +promise-inflight@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" + integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM= + +promise@^8.0.3: + version "8.1.0" + resolved "https://registry.yarnpkg.com/promise/-/promise-8.1.0.tgz#697c25c3dfe7435dd79fcd58c38a135888eaf05e" + integrity sha512-W04AqnILOL/sPRXziNicCjSNRruLAuIHEOVBazepu0545DDNGYHz7ar9ZgZ1fMU8/MA4mVxp5rkBWRi6OXIy3Q== + dependencies: + asap "~2.0.6" + +prompts@^2.0.1: + version "2.3.2" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.3.2.tgz#480572d89ecf39566d2bd3fe2c9fccb7c4c0b068" + integrity sha512-Q06uKs2CkNYVID0VqwfAl9mipo99zkBv/n2JtWY89Yxa3ZabWSrs0e2KTudKVa3peLUvYXMefDqIleLPVUBZMA== + dependencies: + kleur "^3.0.3" + sisteransi "^1.0.4" + +prop-types@^15.5.6, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.2: + version "15.7.1" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.1.tgz#2fa61e0a699d428b40320127733ee2931f05d9d1" + dependencies: + object-assign "^4.1.1" + react-is "^16.8.1" + +prop-types@^15.7.2, prop-types@~15.7.2: + version "15.7.2" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" + integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== + dependencies: + loose-envify "^1.4.0" + object-assign "^4.1.1" + react-is "^16.8.1" + +proxy-addr@~2.0.5: + version "2.0.6" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf" + integrity sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw== + dependencies: + forwarded "~0.1.2" + ipaddr.js "1.9.1" + +prr@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" + +pseudomap@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" + +psl@^1.1.24, psl@^1.1.28: + version "1.1.31" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.1.31.tgz#e9aa86d0101b5b105cbe93ac6b784cd547276184" + +public-encrypt@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0" + dependencies: + bn.js "^4.1.0" + browserify-rsa "^4.0.0" + create-hash "^1.1.0" + parse-asn1 "^5.0.0" + randombytes "^2.0.1" + safe-buffer "^5.1.2" + +pump@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909" + integrity sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +pumpify@^1.3.3: + version "1.5.1" + resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.5.1.tgz#36513be246ab27570b1a374a5ce278bfd74370ce" + integrity sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ== + dependencies: + duplexify "^3.6.0" + inherits "^2.0.3" + pump "^2.0.0" + +punycode@1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" + +punycode@^1.2.4, punycode@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" + +punycode@^2.1.0, punycode@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + +q@^1.1.2: + version "1.5.1" + resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" + +qs@6.7.0: + version "6.7.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" + integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== + +qs@~6.5.2: + version "6.5.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" + +query-string@^4.1.0: + version "4.3.4" + resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb" + dependencies: + object-assign "^4.1.0" + strict-uri-encode "^1.0.0" + +querystring-es3@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" + +querystring@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" + +querystringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.0.tgz#7ded8dfbf7879dcc60d0a644ac6754b283ad17ef" + +raf@^3.4.1: + version "3.4.1" + resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39" + integrity sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA== + dependencies: + performance-now "^2.1.0" + +randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5: + version "2.0.6" + resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.0.6.tgz#d302c522948588848a8d300c932b44c24231da80" + dependencies: + safe-buffer "^5.1.0" + +randombytes@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" + integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== + dependencies: + safe-buffer "^5.1.0" + +randomfill@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/randomfill/-/randomfill-1.0.4.tgz#c92196fc86ab42be983f1bf31778224931d61458" + dependencies: + randombytes "^2.0.5" + safe-buffer "^5.1.0" + +range-parser@^1.2.1, range-parser@~1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + +raw-body@2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332" + integrity sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q== + dependencies: + bytes "3.1.0" + http-errors "1.7.2" + iconv-lite "0.4.24" + unpipe "1.0.0" + +rc@^1.2.7: + version "1.2.8" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" + dependencies: + deep-extend "^0.6.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + +react-app-polyfill@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/react-app-polyfill/-/react-app-polyfill-1.0.6.tgz#890f8d7f2842ce6073f030b117de9130a5f385f0" + integrity sha512-OfBnObtnGgLGfweORmdZbyEz+3dgVePQBb3zipiaDsMHV1NpWm0rDFYIVXFV/AK+x4VIIfWHhrdMIeoTLyRr2g== + dependencies: + core-js "^3.5.0" + object-assign "^4.1.1" + promise "^8.0.3" + raf "^3.4.1" + regenerator-runtime "^0.13.3" + whatwg-fetch "^3.0.0" + +react-dev-utils@^10.2.1: + version "10.2.1" + resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-10.2.1.tgz#f6de325ae25fa4d546d09df4bb1befdc6dd19c19" + integrity sha512-XxTbgJnYZmxuPtY3y/UV0D8/65NKkmaia4rXzViknVnZeVlklSh8u6TnaEYPfAi/Gh1TP4mEOXHI6jQOPbeakQ== + dependencies: + "@babel/code-frame" "7.8.3" + address "1.1.2" + browserslist "4.10.0" + chalk "2.4.2" + cross-spawn "7.0.1" + detect-port-alt "1.1.6" + escape-string-regexp "2.0.0" + filesize "6.0.1" + find-up "4.1.0" + fork-ts-checker-webpack-plugin "3.1.1" + global-modules "2.0.0" + globby "8.0.2" + gzip-size "5.1.1" + immer "1.10.0" + inquirer "7.0.4" + is-root "2.1.0" + loader-utils "1.2.3" + open "^7.0.2" + pkg-up "3.1.0" + react-error-overlay "^6.0.7" + recursive-readdir "2.2.2" + shell-quote "1.7.2" + strip-ansi "6.0.0" + text-table "0.2.0" + +react-document-title@~2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/react-document-title/-/react-document-title-2.0.3.tgz#bbf922a0d71412fc948245e4283b2412df70f2b9" + integrity sha1-u/kioNcUEvyUgkXkKDskEt9w8rk= + dependencies: + prop-types "^15.5.6" + react-side-effect "^1.0.2" + +react-dom@~16.13.1: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.13.1.tgz#c1bd37331a0486c078ee54c4740720993b2e0e7f" + integrity sha512-81PIMmVLnCNLO/fFOQxdQkvEq/+Hfpv24XNJfpyZhTRfO0QcmQIF/PgCa1zCOj2w1hrn12MFLyaJ/G0+Mxtfag== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + prop-types "^15.6.2" + scheduler "^0.19.1" + +react-error-overlay@^6.0.7: + version "6.0.7" + resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.7.tgz#1dcfb459ab671d53f660a991513cb2f0a0553108" + integrity sha512-TAv1KJFh3RhqxNvhzxj6LeT5NWklP6rDr2a0jaTfsZ5wSZWHOGeqQyejUp3xxLfPt2UpyJEcVQB/zyPcmonNFA== + +react-fast-compare@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9" + integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw== + +react-fontawesome@~1.7.1: + version "1.7.1" + resolved "https://registry.yarnpkg.com/react-fontawesome/-/react-fontawesome-1.7.1.tgz#f74f5a338fef3ee3b379820109c1cba47290f035" + integrity sha512-kottReWW1I9Uupub6A5YX4VK7qfpFnEjAcm5zB4Aepst7iofONT27GJYdTcRsj7q5uQu9PXBL7GsxAFKANNUVg== + dependencies: + prop-types "^15.5.6" + +react-google-login@~5.1.14: + version "5.1.20" + resolved "https://registry.yarnpkg.com/react-google-login/-/react-google-login-5.1.20.tgz#06afbf5fd9013455ae3bfba93054630df203542d" + integrity sha512-/5vDx8Hy7Wo1fO1VC/0e5D6/ZGWgIgvcscI8mYZUQ653QOFf0c4GhTnKkebX5uE7m5rAB/2bzzZIUlIesGqWig== + dependencies: + "@types/react" "*" + prop-types "^15.6.0" + +react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1: + version "16.8.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.1.tgz#a80141e246eb894824fb4f2901c0c50ef31d4cdb" + +react-is@^16.8.4, react-is@^16.9.0: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" + integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== + +react-konva@~16.13.0-2: + version "16.13.0-3" + resolved "https://registry.yarnpkg.com/react-konva/-/react-konva-16.13.0-3.tgz#9ef1e813c8b2dd61b54b26151ccbdeed52b89a80" + integrity sha512-U9az1RidQD4c64oZoHiiv6GU6h2ggHO30nZDqfQWuBTH+Bl2wij6Z0NgbUyVyN1IpKIgXRiEKMS9idlxhAzTXQ== + dependencies: + react-reconciler "^0.25.1" + scheduler "^0.19.1" + +react-reconciler@^0.25.1: + version "0.25.1" + resolved "https://registry.yarnpkg.com/react-reconciler/-/react-reconciler-0.25.1.tgz#f9814d59d115e1210762287ce987801529363aaa" + integrity sha512-R5UwsIvRcSs3w8n9k3tBoTtUHdVhu9u84EG7E5M0Jk9F5i6DA1pQzPfUZd6opYWGy56MJOtV3VADzy6DRwYDjw== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + prop-types "^15.6.2" + scheduler "^0.19.1" + +react-redux@~7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.0.tgz#f970f62192b3981642fec46fd0db18a074fe879d" + integrity sha512-EvCAZYGfOLqwV7gh849xy9/pt55rJXPwmYvI4lilPM5rUT/1NxuuN59ipdBksRVSvz0KInbPnp4IfoXJXCqiDA== + dependencies: + "@babel/runtime" "^7.5.5" + hoist-non-react-statics "^3.3.0" + loose-envify "^1.4.0" + prop-types "^15.7.2" + react-is "^16.9.0" + +react-router-dom@~5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.1.2.tgz#06701b834352f44d37fbb6311f870f84c76b9c18" + integrity sha512-7BPHAaIwWpZS074UKaw1FjVdZBSVWEk8IuDXdB+OkLb8vd/WRQIpA4ag9WQk61aEfQs47wHyjWUoUGGZxpQXew== + dependencies: + "@babel/runtime" "^7.1.2" + history "^4.9.0" + loose-envify "^1.3.1" + prop-types "^15.6.2" + react-router "5.1.2" + tiny-invariant "^1.0.2" + tiny-warning "^1.0.0" + +react-router@5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.1.2.tgz#6ea51d789cb36a6be1ba5f7c0d48dd9e817d3418" + integrity sha512-yjEuMFy1ONK246B+rsa0cUam5OeAQ8pyclRDgpxuSCrAlJ1qN9uZ5IgyKC7gQg0w8OM50NXHEegPh/ks9YuR2A== + dependencies: + "@babel/runtime" "^7.1.2" + history "^4.9.0" + hoist-non-react-statics "^3.1.0" + loose-envify "^1.3.1" + mini-create-react-context "^0.3.0" + path-to-regexp "^1.7.0" + prop-types "^15.6.2" + react-is "^16.6.0" + tiny-invariant "^1.0.2" + tiny-warning "^1.0.0" + +react-scripts@~3.4.1: + version "3.4.1" + resolved "https://registry.yarnpkg.com/react-scripts/-/react-scripts-3.4.1.tgz#f551298b5c71985cc491b9acf3c8e8c0ae3ada0a" + integrity sha512-JpTdi/0Sfd31mZA6Ukx+lq5j1JoKItX7qqEK4OiACjVQletM1P38g49d9/D0yTxp9FrSF+xpJFStkGgKEIRjlQ== + dependencies: + "@babel/core" "7.9.0" + "@svgr/webpack" "4.3.3" + "@typescript-eslint/eslint-plugin" "^2.10.0" + "@typescript-eslint/parser" "^2.10.0" + babel-eslint "10.1.0" + babel-jest "^24.9.0" + babel-loader "8.1.0" + babel-plugin-named-asset-import "^0.3.6" + babel-preset-react-app "^9.1.2" + camelcase "^5.3.1" + case-sensitive-paths-webpack-plugin "2.3.0" + css-loader "3.4.2" + dotenv "8.2.0" + dotenv-expand "5.1.0" + eslint "^6.6.0" + eslint-config-react-app "^5.2.1" + eslint-loader "3.0.3" + eslint-plugin-flowtype "4.6.0" + eslint-plugin-import "2.20.1" + eslint-plugin-jsx-a11y "6.2.3" + eslint-plugin-react "7.19.0" + eslint-plugin-react-hooks "^1.6.1" + file-loader "4.3.0" + fs-extra "^8.1.0" + html-webpack-plugin "4.0.0-beta.11" + identity-obj-proxy "3.0.0" + jest "24.9.0" + jest-environment-jsdom-fourteen "1.0.1" + jest-resolve "24.9.0" + jest-watch-typeahead "0.4.2" + mini-css-extract-plugin "0.9.0" + optimize-css-assets-webpack-plugin "5.0.3" + pnp-webpack-plugin "1.6.4" + postcss-flexbugs-fixes "4.1.0" + postcss-loader "3.0.0" + postcss-normalize "8.0.1" + postcss-preset-env "6.7.0" + postcss-safe-parser "4.0.1" + react-app-polyfill "^1.0.6" + react-dev-utils "^10.2.1" + resolve "1.15.0" + resolve-url-loader "3.1.1" + sass-loader "8.0.2" + semver "6.3.0" + style-loader "0.23.1" + terser-webpack-plugin "2.3.5" + ts-pnp "1.1.6" + url-loader "2.3.0" + webpack "4.42.0" + webpack-dev-server "3.10.3" + webpack-manifest-plugin "2.2.0" + workbox-webpack-plugin "4.3.1" + optionalDependencies: + fsevents "2.1.2" + +react-shortcuts@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/react-shortcuts/-/react-shortcuts-2.1.0.tgz#e1ac50be4f847b96a473ce0ad877edadc5067ec6" + integrity sha512-yETQgoy/KRCOPjdlGSnfTjyVHwJYsFoHtVmuLzJABYBAnilpm1M14DhDavuAAMElbaQlXunOwKjh0Oq3I6Vt0A== + dependencies: + combokeys "^3.0.1" + events "^1.0.2" + invariant "^2.1.0" + just-reduce-object "^1.0.3" + platform "^1.3.0" + prop-types "^15.5.8" + +react-side-effect@^1.0.2: + version "1.1.5" + resolved "https://registry.yarnpkg.com/react-side-effect/-/react-side-effect-1.1.5.tgz#f26059e50ed9c626d91d661b9f3c8bb38cd0ff2d" + dependencies: + exenv "^1.2.1" + shallowequal "^1.0.1" + +react@~16.13.1: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e" + integrity sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + prop-types "^15.6.2" + +read-pkg-up@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02" + dependencies: + find-up "^1.0.0" + read-pkg "^1.0.0" + +read-pkg-up@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be" + dependencies: + find-up "^2.0.0" + read-pkg "^2.0.0" + +read-pkg-up@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-4.0.0.tgz#1b221c6088ba7799601c808f91161c66e58f8978" + integrity sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA== + dependencies: + find-up "^3.0.0" + read-pkg "^3.0.0" + +read-pkg@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28" + dependencies: + load-json-file "^1.0.0" + normalize-package-data "^2.3.2" + path-type "^1.0.0" + +read-pkg@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8" + dependencies: + load-json-file "^2.0.0" + normalize-package-data "^2.3.2" + path-type "^2.0.0" + +read-pkg@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389" + dependencies: + load-json-file "^4.0.0" + normalize-package-data "^2.3.2" + path-type "^3.0.0" + +"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.1.5, readable-stream@~2.3.6: + version "2.3.7" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" + integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +readable-stream@1.0: + version "1.0.34" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.1" + isarray "0.0.1" + string_decoder "~0.10.x" + +readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6: + version "2.3.6" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +readable-stream@^3.0.6: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" + integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +readdirp@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525" + dependencies: + graceful-fs "^4.1.11" + micromatch "^3.1.10" + readable-stream "^2.0.2" + +readdirp@~3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.4.0.tgz#9fdccdf9e9155805449221ac645e8303ab5b9ada" + integrity sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ== + dependencies: + picomatch "^2.2.1" + +realpath-native@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/realpath-native/-/realpath-native-1.1.0.tgz#2003294fea23fb0672f2476ebe22fcf498a2d65c" + integrity sha512-wlgPA6cCIIg9gKz0fgAPjnzh4yR/LnXovwuo9hvyGvx3h8nX4+/iLZplfUWasXpqD8BdnGnP5njOFjkUwPzvjA== + dependencies: + util.promisify "^1.0.0" + +recursive-readdir@2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/recursive-readdir/-/recursive-readdir-2.2.2.tgz#9946fb3274e1628de6e36b2f6714953b4845094f" + integrity sha512-nRCcW9Sj7NuZwa2XvH9co8NPeXUBhZP7CRKJtU+cS6PW9FpCIFoI5ib0NT1ZrbNuPoRy0ylyCaUL8Gih4LSyFg== + dependencies: + minimatch "3.0.4" + +redent@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde" + dependencies: + indent-string "^2.1.0" + strip-indent "^1.0.1" + +redux-localstorage@~0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/redux-localstorage/-/redux-localstorage-0.4.1.tgz#faf6d719c581397294d811473ffcedee065c933c" + integrity sha1-+vbXGcWBOXKU2BFHP/zt7gZckzw= + +redux-logger@~3.0.6: + version "3.0.6" + resolved "https://registry.yarnpkg.com/redux-logger/-/redux-logger-3.0.6.tgz#f7555966f3098f3c88604c449cf0baf5778274bf" + integrity sha1-91VZZvMJjzyIYExEnPC69XeCdL8= + dependencies: + deep-diff "^0.3.5" + +redux-saga@~1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/redux-saga/-/redux-saga-1.1.3.tgz#9f3e6aebd3c994bbc0f6901a625f9a42b51d1112" + integrity sha512-RkSn/z0mwaSa5/xH/hQLo8gNf4tlvT18qXDNvedihLcfzh+jMchDgaariQoehCpgRltEm4zHKJyINEz6aqswTw== + dependencies: + "@redux-saga/core" "^1.1.3" + +redux-thunk@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622" + integrity sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw== + +redux@^4.0.4, redux@~4.0.5: + version "4.0.5" + resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.5.tgz#4db5de5816e17891de8a80c424232d06f051d93f" + integrity sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w== + dependencies: + loose-envify "^1.4.0" + symbol-observable "^1.2.0" + +regenerate-unicode-properties@^8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz#e5de7111d655e7ba60c057dbe9ff37c87e65cdec" + integrity sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA== + dependencies: + regenerate "^1.4.0" + +regenerate@^1.4.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.1.tgz#cad92ad8e6b591773485fbe05a485caf4f457e6f" + integrity sha512-j2+C8+NtXQgEKWk49MMP5P/u2GhnahTtVkRIHr5R5lVRlbKvmQ+oS+A5aLKWp2ma5VkT8sh6v+v4hbH0YHR66A== + +regenerator-runtime@^0.11.0: + version "0.11.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" + +regenerator-runtime@^0.12.0: + version "0.12.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz#fa1a71544764c036f8c49b13a08b2594c9f8a0de" + +regenerator-runtime@^0.13.3, regenerator-runtime@^0.13.4: + version "0.13.5" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz#d878a1d094b4306d10b9096484b33ebd55e26697" + integrity sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA== + +regenerator-transform@^0.14.2: + version "0.14.4" + resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.4.tgz#5266857896518d1616a78a0479337a30ea974cc7" + integrity sha512-EaJaKPBI9GvKpvUz2mz4fhx7WPgvwRLY9v3hlNHWmAuJHI13T4nwKnNvm5RWJzEdnI5g5UwtOww+S8IdoUC2bw== + dependencies: + "@babel/runtime" "^7.8.4" + private "^0.1.8" + +regex-not@^1.0.0, regex-not@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" + dependencies: + extend-shallow "^3.0.2" + safe-regex "^1.1.0" + +regex-parser@2.2.10: + version "2.2.10" + resolved "https://registry.yarnpkg.com/regex-parser/-/regex-parser-2.2.10.tgz#9e66a8f73d89a107616e63b39d4deddfee912b37" + integrity sha512-8t6074A68gHfU8Neftl0Le6KTDwfGAj7IyjPIMSfikI2wJUTHDMaIq42bUsfVnj8mhx0R+45rdUXHGpN164avA== + +regexp.prototype.flags@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz#7aba89b3c13a64509dabcf3ca8d9fbb9bdf5cb75" + integrity sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + +regexpp@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f" + integrity sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw== + +regexpp@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.1.0.tgz#206d0ad0a5648cffbdb8ae46438f3dc51c9f78e2" + integrity sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q== + +regexpu-core@^4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.7.0.tgz#fcbf458c50431b0bb7b45d6967b8192d91f3d938" + integrity sha512-TQ4KXRnIn6tz6tjnrXEkD/sshygKH/j5KzK86X8MkeHyZ8qst/LZ89j3X4/8HEIfHANTFIP/AbXakeRhWIl5YQ== + dependencies: + regenerate "^1.4.0" + regenerate-unicode-properties "^8.2.0" + regjsgen "^0.5.1" + regjsparser "^0.6.4" + unicode-match-property-ecmascript "^1.0.4" + unicode-match-property-value-ecmascript "^1.2.0" + +regjsgen@^0.5.1: + version "0.5.2" + resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.5.2.tgz#92ff295fb1deecbf6ecdab2543d207e91aa33733" + integrity sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A== + +regjsparser@^0.6.4: + version "0.6.4" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.6.4.tgz#a769f8684308401a66e9b529d2436ff4d0666272" + integrity sha512-64O87/dPDgfk8/RQqC4gkZoGyyWFIEUTTh80CU6CWuK5vkCGyekIx+oKcEIYtP/RAxSQltCZHCNu/mdd7fqlJw== + dependencies: + jsesc "~0.5.0" + +relateurl@^0.2.7: + version "0.2.7" + resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9" + integrity sha1-VNvzd+UUQKypCkzSdGANP/LYiKk= + +remove-trailing-separator@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" + +renderkid@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/renderkid/-/renderkid-2.0.2.tgz#12d310f255360c07ad8fde253f6c9e9de372d2aa" + dependencies: + css-select "^1.1.0" + dom-converter "~0.2" + htmlparser2 "~3.3.0" + strip-ansi "^3.0.0" + utila "^0.4.0" + +repeat-element@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce" + +repeat-string@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" + +repeating@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda" + dependencies: + is-finite "^1.0.0" + +request-promise-core@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.3.tgz#e9a3c081b51380dfea677336061fea879a829ee9" + integrity sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ== + dependencies: + lodash "^4.17.15" + +request-promise-native@^1.0.5: + version "1.0.8" + resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.8.tgz#a455b960b826e44e2bf8999af64dff2bfe58cb36" + integrity sha512-dapwLGqkHtwL5AEbfenuzjTYg35Jd6KPytsC2/TLkVMz8rm+tNt72MGUWT1RP/aYawMpN6HqbNGBQaRcBtjQMQ== + dependencies: + request-promise-core "1.1.3" + stealthy-require "^1.1.1" + tough-cookie "^2.3.3" + +request@^2.87.0, request@^2.88.0: + version "2.88.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.8.0" + caseless "~0.12.0" + combined-stream "~1.0.6" + extend "~3.0.2" + forever-agent "~0.6.1" + form-data "~2.3.2" + har-validator "~5.1.0" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.19" + oauth-sign "~0.9.0" + performance-now "^2.1.0" + qs "~6.5.2" + safe-buffer "^5.1.2" + tough-cookie "~2.4.3" + tunnel-agent "^0.6.0" + uuid "^3.3.2" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + +require-main-filename@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" + +require-main-filename@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" + integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== + +requires-port@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" + +resolve-cwd@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a" + dependencies: + resolve-from "^3.0.0" + +resolve-from@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve-pathname@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-3.0.0.tgz#99d02224d3cf263689becbb393bc560313025dcd" + integrity sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng== + +resolve-url-loader@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/resolve-url-loader/-/resolve-url-loader-3.1.1.tgz#28931895fa1eab9be0647d3b2958c100ae3c0bf0" + integrity sha512-K1N5xUjj7v0l2j/3Sgs5b8CjrrgtC70SmdCuZiJ8tSyb5J+uk3FoeZ4b7yTnH6j7ngI+Bc5bldHJIa8hYdu2gQ== + dependencies: + adjust-sourcemap-loader "2.0.0" + camelcase "5.3.1" + compose-function "3.0.3" + convert-source-map "1.7.0" + es6-iterator "2.0.3" + loader-utils "1.2.3" + postcss "7.0.21" + rework "1.0.1" + rework-visit "1.0.0" + source-map "0.6.1" + +resolve-url@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" + +resolve@1.1.7: + version "1.1.7" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" + +resolve@1.15.0: + version "1.15.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.15.0.tgz#1b7ca96073ebb52e741ffd799f6b39ea462c67f5" + integrity sha512-+hTmAldEGE80U2wJJDC1lebb5jWqvTYAfm3YZ1ckk1gBr0MnCqUKlwK1e+anaFljIl+F5tR5IoZcm4ZDA1zMQw== + dependencies: + path-parse "^1.0.6" + +resolve@^1.10.0, resolve@^1.3.2: + version "1.10.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.10.0.tgz#3bdaaeaf45cc07f375656dfd2e54ed0810b101ba" + dependencies: + path-parse "^1.0.6" + +resolve@^1.12.0, resolve@^1.13.1, resolve@^1.15.1, resolve@^1.8.1: + version "1.17.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" + integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w== + dependencies: + path-parse "^1.0.6" + +restore-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" + integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== + dependencies: + onetime "^5.1.0" + signal-exit "^3.0.2" + +ret@~0.1.10: + version "0.1.15" + resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" + +retry@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" + integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs= + +rework-visit@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/rework-visit/-/rework-visit-1.0.0.tgz#9945b2803f219e2f7aca00adb8bc9f640f842c9a" + integrity sha1-mUWygD8hni96ygCtuLyfZA+ELJo= + +rework@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/rework/-/rework-1.0.1.tgz#30806a841342b54510aa4110850cd48534144aa7" + integrity sha1-MIBqhBNCtUUQqkEQhQzUhTQUSqc= + dependencies: + convert-source-map "^0.3.3" + css "^2.0.0" + +rgb-regex@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/rgb-regex/-/rgb-regex-1.0.1.tgz#c0e0d6882df0e23be254a475e8edd41915feaeb1" + integrity sha1-wODWiC3w4jviVKR16O3UGRX+rrE= + +rgba-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/rgba-regex/-/rgba-regex-1.0.0.tgz#43374e2e2ca0968b0ef1523460b7d730ff22eeb3" + integrity sha1-QzdOLiyglosO8VI0YLfXMP8i7rM= + +rimraf@2, rimraf@2.6.3, rimraf@^2.6.1: + version "2.6.3" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" + dependencies: + glob "^7.1.3" + +rimraf@^2.5.4, rimraf@^2.6.3, rimraf@^2.7.1: + version "2.7.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" + integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== + dependencies: + glob "^7.1.3" + +ripemd160@^2.0.0, ripemd160@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" + dependencies: + hash-base "^3.0.0" + inherits "^2.0.1" + +rsvp@^4.8.4: + version "4.8.5" + resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734" + integrity sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA== + +run-async@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0" + dependencies: + is-promise "^2.1.0" + +run-async@^2.4.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" + integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== + +run-queue@^1.0.0, run-queue@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/run-queue/-/run-queue-1.0.3.tgz#e848396f057d223f24386924618e25694161ec47" + integrity sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec= + dependencies: + aproba "^1.1.1" + +rxjs@^6.5.3, rxjs@^6.5.5: + version "6.5.5" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.5.tgz#c5c884e3094c8cfee31bf27eb87e54ccfc87f9ec" + integrity sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ== + dependencies: + tslib "^1.9.0" + +safe-buffer@5.1.2, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + +safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +safe-regex@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" + dependencies: + ret "~0.1.10" + +"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + +sane@^4.0.3: + version "4.1.0" + resolved "https://registry.yarnpkg.com/sane/-/sane-4.1.0.tgz#ed881fd922733a6c461bc189dc2b6c006f3ffded" + integrity sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA== + dependencies: + "@cnakazawa/watch" "^1.0.3" + anymatch "^2.0.0" + capture-exit "^2.0.0" + exec-sh "^0.3.2" + execa "^1.0.0" + fb-watchman "^2.0.0" + micromatch "^3.1.4" + minimist "^1.1.1" + walker "~1.0.5" + +sanitize.css@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/sanitize.css/-/sanitize.css-10.0.0.tgz#b5cb2547e96d8629a60947544665243b1dc3657a" + integrity sha512-vTxrZz4dX5W86M6oVWVdOVe72ZiPs41Oi7Z6Km4W5Turyz28mrXSJhhEBZoRtzJWIv3833WKVwLSDWWkEfupMg== + +sass-graph@^2.1.1, sass-graph@^2.2.4: + version "2.2.4" + resolved "https://registry.yarnpkg.com/sass-graph/-/sass-graph-2.2.4.tgz#13fbd63cd1caf0908b9fd93476ad43a51d1e0b49" + dependencies: + glob "^7.0.0" + lodash "^4.0.0" + scss-tokenizer "^0.2.3" + yargs "^7.0.0" + +sass-loader@8.0.2: + version "8.0.2" + resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-8.0.2.tgz#debecd8c3ce243c76454f2e8290482150380090d" + integrity sha512-7o4dbSK8/Ol2KflEmSco4jTjQoV988bM82P9CZdmo9hR3RLnvNc0ufMNdMrB0caq38JQ/FgF4/7RcbcfKzxoFQ== + dependencies: + clone-deep "^4.0.1" + loader-utils "^1.2.3" + neo-async "^2.6.1" + schema-utils "^2.6.1" + semver "^6.3.0" + +sax@^1.2.4, sax@~1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" + +saxes@^3.1.9: + version "3.1.11" + resolved "https://registry.yarnpkg.com/saxes/-/saxes-3.1.11.tgz#d59d1fd332ec92ad98a2e0b2ee644702384b1c5b" + integrity sha512-Ydydq3zC+WYDJK1+gRxRapLIED9PWeSuuS41wqyoRmzvhhh9nc+QQrVMKJYzJFULazeGhzSV0QleN2wD3boh2g== + dependencies: + xmlchars "^2.1.1" + +scheduler@^0.19.1: + version "0.19.1" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.19.1.tgz#4f3e2ed2c1a7d65681f4c854fa8c5a1ccb40f196" + integrity sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + +schema-utils@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770" + integrity sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g== + dependencies: + ajv "^6.1.0" + ajv-errors "^1.0.0" + ajv-keywords "^3.1.0" + +schema-utils@^2.5.0, schema-utils@^2.6.0, schema-utils@^2.6.1, schema-utils@^2.6.4, schema-utils@^2.6.5: + version "2.7.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.0.tgz#17151f76d8eae67fbbf77960c33c676ad9f4efc7" + integrity sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A== + dependencies: + "@types/json-schema" "^7.0.4" + ajv "^6.12.2" + ajv-keywords "^3.4.1" + +scss-tokenizer@^0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz#8eb06db9a9723333824d3f5530641149847ce5d1" + dependencies: + js-base64 "^2.1.8" + source-map "^0.4.2" + +select-hose@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" + +selfsigned@^1.10.7: + version "1.10.7" + resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.7.tgz#da5819fd049d5574f28e88a9bcc6dbc6e6f3906b" + integrity sha512-8M3wBCzeWIJnQfl43IKwOmC4H/RAp50S8DF60znzjW5GVqTcSe2vWclt7hmYVPkKPlHWOu5EaWOMZ2Y6W8ZXTA== + dependencies: + node-forge "0.9.0" + +semver-compare@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" + integrity sha1-De4hahyUGrN+nvsXiPavxf9VN/w= + +semver-regex@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/semver-regex/-/semver-regex-2.0.0.tgz#a93c2c5844539a770233379107b38c7b4ac9d338" + integrity sha512-mUdIBBvdn0PLOeP3TEkMH7HHeUP3GjsXCwKarjv/kGmUFOYg1VqEemKhoQpWMu6X2I8kHeuVdGibLGkVK+/5Qw== + +"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.5.0: + version "5.6.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" + +semver@6.3.0, semver@^6.0.0, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + +semver@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" + integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== + +semver@^5.4.1, semver@^5.5.1, semver@^5.6.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + +semver@^7.3.2: + version "7.3.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" + integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== + +semver@~5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" + +send@0.17.1: + version "0.17.1" + resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" + integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg== + dependencies: + debug "2.6.9" + depd "~1.1.2" + destroy "~1.0.4" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "~1.7.2" + mime "1.6.0" + ms "2.1.1" + on-finished "~2.3.0" + range-parser "~1.2.1" + statuses "~1.5.0" + +serialize-javascript@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-2.1.2.tgz#ecec53b0e0317bdc95ef76ab7074b7384785fa61" + integrity sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ== + +serialize-javascript@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-3.1.0.tgz#8bf3a9170712664ef2561b44b691eafe399214ea" + integrity sha512-JIJT1DGiWmIKhzRsG91aS6Ze4sFUrYbltlkg2onR5OrnNM02Kl/hnY/T4FN2omvyeBbQmMJv+K4cPOpGzOTFBg== + dependencies: + randombytes "^2.1.0" + +serve-index@^1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239" + integrity sha1-03aNabHn2C5c4FD/9bRTvqEqkjk= + dependencies: + accepts "~1.3.4" + batch "0.6.1" + debug "2.6.9" + escape-html "~1.0.3" + http-errors "~1.6.2" + mime-types "~2.1.17" + parseurl "~1.3.2" + +serve-static@1.14.1: + version "1.14.1" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9" + integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg== + dependencies: + encodeurl "~1.0.2" + escape-html "~1.0.3" + parseurl "~1.3.3" + send "0.17.1" + +set-blocking@^2.0.0, set-blocking@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + +set-value@^0.4.3: + version "0.4.3" + resolved "https://registry.yarnpkg.com/set-value/-/set-value-0.4.3.tgz#7db08f9d3d22dc7f78e53af3c3bf4666ecdfccf1" + dependencies: + extend-shallow "^2.0.1" + is-extendable "^0.1.1" + is-plain-object "^2.0.1" + to-object-path "^0.3.0" + +set-value@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.0.tgz#71ae4a88f0feefbbf52d1ea604f3fb315ebb6274" + dependencies: + extend-shallow "^2.0.1" + is-extendable "^0.1.1" + is-plain-object "^2.0.3" + split-string "^3.0.1" + +setimmediate@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" + +setprototypeof@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" + +setprototypeof@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" + integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw== + +sha.js@^2.4.0, sha.js@^2.4.8: + version "2.4.11" + resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + +shallow-clone@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-0.1.2.tgz#5909e874ba77106d73ac414cfec1ffca87d97060" + integrity sha1-WQnodLp3EG1zrEFM/sH/yofZcGA= + dependencies: + is-extendable "^0.1.1" + kind-of "^2.0.1" + lazy-cache "^0.2.3" + mixin-object "^2.0.1" + +shallow-clone@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" + integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA== + dependencies: + kind-of "^6.0.2" + +shallowequal@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8" + +shebang-command@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" + dependencies: + shebang-regex "^1.0.0" + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +shell-quote@1.7.2: + version "1.7.2" + resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.2.tgz#67a7d02c76c9da24f99d20808fcaded0e0e04be2" + integrity sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg== + +shell-quote@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.6.1.tgz#f4781949cce402697127430ea3b3c5476f481767" + dependencies: + array-filter "~0.0.0" + array-map "~0.0.0" + array-reduce "~0.0.0" + jsonify "~0.0.0" + +shellwords@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" + +side-channel@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.2.tgz#df5d1abadb4e4bf4af1cd8852bf132d2f7876947" + integrity sha512-7rL9YlPHg7Ancea1S96Pa8/QWb4BtXL/TZvS6B8XFetGBeuhAsfmUspK6DokBeZ64+Kj9TCNRD/30pVz1BvQNA== + dependencies: + es-abstract "^1.17.0-next.1" + object-inspect "^1.7.0" + +signal-exit@^3.0.0, signal-exit@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" + +simple-swizzle@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" + integrity sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo= + dependencies: + is-arrayish "^0.3.1" + +sisteransi@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" + integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== + +slash@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" + +slash@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44" + integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A== + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +slice-ansi@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636" + integrity sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ== + dependencies: + ansi-styles "^3.2.0" + astral-regex "^1.0.0" + is-fullwidth-code-point "^2.0.0" + +slice-ansi@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-3.0.0.tgz#31ddc10930a1b7e0b67b08c96c2f49b77a789787" + integrity sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ== + dependencies: + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" + +slice-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" + integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ== + dependencies: + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" + +snapdragon-node@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" + dependencies: + define-property "^1.0.0" + isobject "^3.0.0" + snapdragon-util "^3.0.1" + +snapdragon-util@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" + dependencies: + kind-of "^3.2.0" + +snapdragon@^0.8.1: + version "0.8.2" + resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" + dependencies: + base "^0.11.1" + debug "^2.2.0" + define-property "^0.2.5" + extend-shallow "^2.0.1" + map-cache "^0.2.2" + source-map "^0.5.6" + source-map-resolve "^0.5.0" + use "^3.1.0" + +socket.io-client@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-2.3.0.tgz#14d5ba2e00b9bcd145ae443ab96b3f86cbcc1bb4" + integrity sha512-cEQQf24gET3rfhxZ2jJ5xzAOo/xhZwK+mOqtGRg5IowZsMgwvHwnf/mCRapAAkadhM26y+iydgwsXGObBB5ZdA== + dependencies: + backo2 "1.0.2" + base64-arraybuffer "0.1.5" + component-bind "1.0.0" + component-emitter "1.2.1" + debug "~4.1.0" + engine.io-client "~3.4.0" + has-binary2 "~1.0.2" + has-cors "1.1.0" + indexof "0.0.1" + object-component "0.0.3" + parseqs "0.0.5" + parseuri "0.0.5" + socket.io-parser "~3.3.0" + to-array "0.1.4" + +socket.io-parser@~3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-3.3.0.tgz#2b52a96a509fdf31440ba40fed6094c7d4f1262f" + dependencies: + component-emitter "1.2.1" + debug "~3.1.0" + isarray "2.0.1" + +sockjs-client@1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.4.0.tgz#c9f2568e19c8fd8173b4997ea3420e0bb306c7d5" + integrity sha512-5zaLyO8/nri5cua0VtOrFXBPK1jbL4+1cebT/mmKA1E1ZXOvJrII75bPu0l0k843G/+iAbhEqzyKr0w/eCCj7g== + dependencies: + debug "^3.2.5" + eventsource "^1.0.7" + faye-websocket "~0.11.1" + inherits "^2.0.3" + json3 "^3.3.2" + url-parse "^1.4.3" + +sockjs@0.3.19: + version "0.3.19" + resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.19.tgz#d976bbe800af7bd20ae08598d582393508993c0d" + dependencies: + faye-websocket "^0.10.0" + uuid "^3.0.1" + +sort-keys@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad" + dependencies: + is-plain-obj "^1.0.0" + +source-list-map@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" + +source-map-resolve@^0.5.0: + version "0.5.2" + resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.2.tgz#72e2cc34095543e43b2c62b2c4c10d4a9054f259" + dependencies: + atob "^2.1.1" + decode-uri-component "^0.2.0" + resolve-url "^0.2.1" + source-map-url "^0.4.0" + urix "^0.1.0" + +source-map-resolve@^0.5.2: + version "0.5.3" + resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" + integrity sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw== + dependencies: + atob "^2.1.2" + decode-uri-component "^0.2.0" + resolve-url "^0.2.1" + source-map-url "^0.4.0" + urix "^0.1.0" + +source-map-support@^0.5.6, source-map-support@~0.5.12: + version "0.5.19" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" + integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map-url@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" + +source-map@0.6.1, source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + +source-map@^0.4.2: + version "0.4.4" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b" + dependencies: + amdefine ">=0.0.4" + +source-map@^0.5.0, source-map@^0.5.6: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + +spdx-correct@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.0.tgz#fb83e504445268f154b074e218c87c003cd31df4" + dependencies: + spdx-expression-parse "^3.0.0" + spdx-license-ids "^3.0.0" + +spdx-exceptions@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz#2ea450aee74f2a89bfb94519c07fcd6f41322977" + +spdx-expression-parse@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz#99e119b7a5da00e05491c9fa338b7904823b41d0" + dependencies: + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" + +spdx-license-ids@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.3.tgz#81c0ce8f21474756148bbb5f3bfc0f36bf15d76e" + +spdy-transport@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-3.0.0.tgz#00d4863a6400ad75df93361a1608605e5dcdcf31" + integrity sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw== + dependencies: + debug "^4.1.0" + detect-node "^2.0.4" + hpack.js "^2.1.6" + obuf "^1.1.2" + readable-stream "^3.0.6" + wbuf "^1.7.3" + +spdy@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/spdy/-/spdy-4.0.2.tgz#b74f466203a3eda452c02492b91fb9e84a27677b" + integrity sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA== + dependencies: + debug "^4.1.0" + handle-thing "^2.0.0" + http-deceiver "^1.2.7" + select-hose "^2.0.0" + spdy-transport "^3.0.0" + +split-string@^3.0.1, split-string@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" + dependencies: + extend-shallow "^3.0.0" + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + +sshpk@^1.7.0: + version "1.16.1" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" + dependencies: + asn1 "~0.2.3" + assert-plus "^1.0.0" + bcrypt-pbkdf "^1.0.0" + dashdash "^1.12.0" + ecc-jsbn "~0.1.1" + getpass "^0.1.1" + jsbn "~0.1.0" + safer-buffer "^2.0.2" + tweetnacl "~0.14.0" + +ssri@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.1.tgz#2a3c41b28dd45b62b63676ecb74001265ae9edd8" + integrity sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA== + dependencies: + figgy-pudding "^3.5.1" + +ssri@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/ssri/-/ssri-7.1.0.tgz#92c241bf6de82365b5c7fb4bd76e975522e1294d" + integrity sha512-77/WrDZUWocK0mvA5NTRQyveUf+wsrIc6vyrxpS8tVvYBcX215QbafrJR3KtkpskIzoFLqqNuuYQvxaMjXJ/0g== + dependencies: + figgy-pudding "^3.5.1" + minipass "^3.1.1" + +stable@^0.1.8: + version "0.1.8" + resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" + integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== + +stack-utils@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-1.0.2.tgz#33eba3897788558bebfc2db059dc158ec36cebb8" + integrity sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA== + +static-extend@^0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" + dependencies: + define-property "^0.2.5" + object-copy "^0.1.0" + +"statuses@>= 1.4.0 < 2", "statuses@>= 1.5.0 < 2", statuses@~1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" + +stdout-stream@^1.4.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/stdout-stream/-/stdout-stream-1.4.1.tgz#5ac174cdd5cd726104aa0c0b2bd83815d8d535de" + dependencies: + readable-stream "^2.0.1" + +stealthy-require@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b" + integrity sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks= + +stream-browserify@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.2.tgz#87521d38a44aa7ee91ce1cd2a47df0cb49dd660b" + dependencies: + inherits "~2.0.1" + readable-stream "^2.0.2" + +stream-each@^1.1.0: + version "1.2.3" + resolved "https://registry.yarnpkg.com/stream-each/-/stream-each-1.2.3.tgz#ebe27a0c389b04fbcc233642952e10731afa9bae" + integrity sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw== + dependencies: + end-of-stream "^1.1.0" + stream-shift "^1.0.0" + +stream-http@^2.7.2: + version "2.8.3" + resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.3.tgz#b2d242469288a5a27ec4fe8933acf623de6514fc" + dependencies: + builtin-status-codes "^3.0.0" + inherits "^2.0.1" + readable-stream "^2.3.6" + to-arraybuffer "^1.0.0" + xtend "^4.0.0" + +stream-shift@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" + integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ== + +strict-uri-encode@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" + +string-argv@0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.1.tgz#95e2fbec0427ae19184935f816d74aaa4c5c19da" + integrity sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg== + +string-length@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/string-length/-/string-length-2.0.0.tgz#d40dbb686a3ace960c1cffca562bf2c45f8363ed" + integrity sha1-1A27aGo6zpYMHP/KVivyxF+DY+0= + dependencies: + astral-regex "^1.0.0" + strip-ansi "^4.0.0" + +string-length@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/string-length/-/string-length-3.1.0.tgz#107ef8c23456e187a8abd4a61162ff4ac6e25837" + integrity sha512-Ttp5YvkGm5v9Ijagtaz1BnN+k9ObpvS0eIBblPMp2YWL8FBmi9qblQ9fexc2k/CXFgrTIteU3jAw3payCnwSTA== + dependencies: + astral-regex "^1.0.0" + strip-ansi "^5.2.0" + +string-width@^1.0.1, string-width@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" + dependencies: + code-point-at "^1.0.0" + is-fullwidth-code-point "^1.0.0" + strip-ansi "^3.0.0" + +"string-width@^1.0.2 || 2", string-width@^2.0.0, string-width@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" + dependencies: + is-fullwidth-code-point "^2.0.0" + strip-ansi "^4.0.0" + +string-width@^3.0.0, string-width@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" + integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== + dependencies: + emoji-regex "^7.0.1" + is-fullwidth-code-point "^2.0.0" + strip-ansi "^5.1.0" + +string-width@^4.1.0, string-width@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5" + integrity sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.0" + +string.prototype.matchall@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.2.tgz#48bb510326fb9fdeb6a33ceaa81a6ea04ef7648e" + integrity sha512-N/jp6O5fMf9os0JU3E72Qhf590RSRZU/ungsL/qJUYVTNv7hTG0P/dbPjxINVN9jpscu3nzYwKESU3P3RY5tOg== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0" + has-symbols "^1.0.1" + internal-slot "^1.0.2" + regexp.prototype.flags "^1.3.0" + side-channel "^1.0.2" + +string.prototype.padend@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/string.prototype.padend/-/string.prototype.padend-3.0.0.tgz#f3aaef7c1719f170c5eab1c32bf780d96e21f2f0" + dependencies: + define-properties "^1.1.2" + es-abstract "^1.4.3" + function-bind "^1.0.2" + +string.prototype.trimend@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz#85812a6b847ac002270f5808146064c995fb6913" + integrity sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.5" + +string.prototype.trimleft@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz#4408aa2e5d6ddd0c9a80739b087fbc067c03b3cc" + integrity sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.5" + string.prototype.trimstart "^1.0.0" + +string.prototype.trimright@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz#c76f1cef30f21bbad8afeb8db1511496cfb0f2a3" + integrity sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.5" + string.prototype.trimend "^1.0.0" + +string.prototype.trimstart@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz#14af6d9f34b053f7cfc89b72f8f2ee14b9039a54" + integrity sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.5" + +string_decoder@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.2.0.tgz#fe86e738b19544afe70469243b2a1ee9240eae8d" + dependencies: + safe-buffer "~5.1.0" + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +string_decoder@~0.10.x: + version "0.10.31" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + dependencies: + safe-buffer "~5.1.0" + +stringify-object@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/stringify-object/-/stringify-object-3.3.0.tgz#703065aefca19300d3ce88af4f5b3956d7556629" + integrity sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw== + dependencies: + get-own-enumerable-property-symbols "^3.0.0" + is-obj "^1.0.1" + is-regexp "^1.0.0" + +strip-ansi@6.0.0, strip-ansi@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" + integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w== + dependencies: + ansi-regex "^5.0.0" + +strip-ansi@^3.0.0, strip-ansi@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + dependencies: + ansi-regex "^2.0.0" + +strip-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" + dependencies: + ansi-regex "^3.0.0" + +strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" + integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== + dependencies: + ansi-regex "^4.1.0" + +strip-bom@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e" + dependencies: + is-utf8 "^0.2.0" + +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + +strip-comments@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/strip-comments/-/strip-comments-1.0.2.tgz#82b9c45e7f05873bee53f37168af930aa368679d" + integrity sha512-kL97alc47hoyIQSV165tTt9rG5dn4w1dNnBhOQ3bOU1Nc1hel09jnXANaHJ7vzHLd4Ju8kseDGzlev96pghLFw== + dependencies: + babel-extract-comments "^1.0.0" + babel-plugin-transform-object-rest-spread "^6.26.0" + +strip-eof@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" + +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + +strip-indent@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2" + dependencies: + get-stdin "^4.0.1" + +strip-json-comments@^3.0.1: + version "3.1.0" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.0.tgz#7638d31422129ecf4457440009fba03f9f9ac180" + integrity sha512-e6/d0eBu7gHtdCqFt0xJr642LdToM5/cN4Qb9DbHjVx1CP5RyeM+zH7pbecEmDv/lBqb0QH+6Uqq75rxFPkM0w== + +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + +style-loader@0.23.1: + version "0.23.1" + resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-0.23.1.tgz#cb9154606f3e771ab6c4ab637026a1049174d925" + integrity sha512-XK+uv9kWwhZMZ1y7mysB+zoihsEj4wneFWAS5qoiLwzW0WzSqMrrsIy+a3zkQJq0ipFtBpX5W3MqyRIBF/WFGg== + dependencies: + loader-utils "^1.1.0" + schema-utils "^1.0.0" + +stylehacks@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-4.0.3.tgz#6718fcaf4d1e07d8a1318690881e8d96726a71d5" + integrity sha512-7GlLk9JwlElY4Y6a/rmbH2MhVlTyVmiJd1PfTCqFaIBEGMYNsrO/v3SeGTdhBThLg4Z+NbOk/qFMwCa+J+3p/g== + dependencies: + browserslist "^4.0.0" + postcss "^7.0.0" + postcss-selector-parser "^3.0.0" + +supports-color@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + dependencies: + has-flag "^3.0.0" + +supports-color@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" + integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.0.0, supports-color@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1" + integrity sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g== + dependencies: + has-flag "^4.0.0" + +svg-parser@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/svg-parser/-/svg-parser-2.0.4.tgz#fdc2e29e13951736140b76cb122c8ee6630eb6b5" + integrity sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ== + +svgo@^1.0.0, svgo@^1.2.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/svgo/-/svgo-1.3.2.tgz#b6dc511c063346c9e415b81e43401145b96d4167" + integrity sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw== + dependencies: + chalk "^2.4.1" + coa "^2.0.2" + css-select "^2.0.0" + css-select-base-adapter "^0.1.1" + css-tree "1.0.0-alpha.37" + csso "^4.0.2" + js-yaml "^3.13.1" + mkdirp "~0.5.1" + object.values "^1.1.0" + sax "~1.2.4" + stable "^0.1.8" + unquote "~1.1.1" + util.promisify "~1.0.0" + +svgsaver@~0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/svgsaver/-/svgsaver-0.9.0.tgz#93d5dbb3f840953b8df0a14a942f4cc8d552335e" + integrity sha1-k9Xbs/hAlTuN8KFKlC9MyNVSM14= + dependencies: + computed-styles "^1.1.2" + file-saver "^1.3.3" + +symbol-observable@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" + integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ== + +symbol-tree@^3.2.2: + version "3.2.4" + resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" + integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== + +table@^5.2.3: + version "5.4.6" + resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e" + integrity sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug== + dependencies: + ajv "^6.10.2" + lodash "^4.17.14" + slice-ansi "^2.1.0" + string-width "^3.0.0" + +tapable@^1.0.0, tapable@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" + integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== + +tar@^2.0.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.1.tgz#8e4d2a256c0e2185c6b18ad694aec968b83cb1d1" + dependencies: + block-stream "*" + fstream "^1.0.2" + inherits "2" + +tar@^4: + version "4.4.8" + resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.8.tgz#b19eec3fde2a96e64666df9fdb40c5ca1bc3747d" + dependencies: + chownr "^1.1.1" + fs-minipass "^1.2.5" + minipass "^2.3.4" + minizlib "^1.1.1" + mkdirp "^0.5.0" + safe-buffer "^5.1.2" + yallist "^3.0.2" + +terser-webpack-plugin@2.3.5: + version "2.3.5" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-2.3.5.tgz#5ad971acce5c517440ba873ea4f09687de2f4a81" + integrity sha512-WlWksUoq+E4+JlJ+h+U+QUzXpcsMSSNXkDy9lBVkSqDn1w23Gg29L/ary9GeJVYCGiNJJX7LnVc4bwL1N3/g1w== + dependencies: + cacache "^13.0.1" + find-cache-dir "^3.2.0" + jest-worker "^25.1.0" + p-limit "^2.2.2" + schema-utils "^2.6.4" + serialize-javascript "^2.1.2" + source-map "^0.6.1" + terser "^4.4.3" + webpack-sources "^1.4.3" + +terser-webpack-plugin@^1.4.3: + version "1.4.4" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.4.4.tgz#2c63544347324baafa9a56baaddf1634c8abfc2f" + integrity sha512-U4mACBHIegmfoEe5fdongHESNJWqsGU+W0S/9+BmYGVQDw1+c2Ow05TpMhxjPK1sRb7cuYq1BPl1e5YHJMTCqA== + dependencies: + cacache "^12.0.2" + find-cache-dir "^2.1.0" + is-wsl "^1.1.0" + schema-utils "^1.0.0" + serialize-javascript "^3.1.0" + source-map "^0.6.1" + terser "^4.1.2" + webpack-sources "^1.4.0" + worker-farm "^1.7.0" + +terser@^4.1.2, terser@^4.4.3, terser@^4.6.3: + version "4.7.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-4.7.0.tgz#15852cf1a08e3256a80428e865a2fa893ffba006" + integrity sha512-Lfb0RiZcjRDXCC3OSHJpEkxJ9Qeqs6mp2v4jf2MHfy8vGERmVDuvjXdd/EnP5Deme5F2yBRBymKmKHCBg2echw== + dependencies: + commander "^2.20.0" + source-map "~0.6.1" + source-map-support "~0.5.12" + +test-exclude@^5.2.3: + version "5.2.3" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-5.2.3.tgz#c3d3e1e311eb7ee405e092dac10aefd09091eac0" + integrity sha512-M+oxtseCFO3EDtAaGH7iiej3CBkzXqFMbzqYAACdzKui4eZA+pq3tZEwChvOdNfa7xxy8BfbmgJSIr43cC/+2g== + dependencies: + glob "^7.1.3" + minimatch "^3.0.4" + read-pkg-up "^4.0.0" + require-main-filename "^2.0.0" + +text-table@0.2.0, text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + +throat@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/throat/-/throat-4.1.0.tgz#89037cbc92c56ab18926e6ba4cbb200e15672a6a" + integrity sha1-iQN8vJLFarGJJua6TLsgDhVnKmo= + +through2@^2.0.0: + version "2.0.5" + resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" + integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== + dependencies: + readable-stream "~2.3.6" + xtend "~4.0.1" + +through@^2.3.6, through@^2.3.8: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= + +thunky@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.0.3.tgz#f5df732453407b09191dae73e2a8cc73f381a826" + +timers-browserify@^2.0.4: + version "2.0.10" + resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.10.tgz#1d28e3d2aadf1d5a5996c4e9f95601cd053480ae" + dependencies: + setimmediate "^1.0.4" + +timsort@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" + integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q= + +tiny-invariant@^1.0.2: + version "1.1.0" + resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875" + integrity sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw== + +tiny-warning@^1.0.0, tiny-warning@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" + integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== + +tmp@^0.0.33: + version "0.0.33" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" + dependencies: + os-tmpdir "~1.0.2" + +tmpl@1.0.x: + version "1.0.4" + resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1" + +to-array@0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/to-array/-/to-array-0.1.4.tgz#17e6c11f73dd4f3d74cda7a4ff3238e9ad9bf890" + +to-arraybuffer@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= + +to-object-path@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" + dependencies: + kind-of "^3.0.2" + +to-regex-range@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" + dependencies: + is-number "^3.0.0" + repeat-string "^1.6.1" + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +to-regex@^3.0.1, to-regex@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" + dependencies: + define-property "^2.0.2" + extend-shallow "^3.0.2" + regex-not "^1.0.2" + safe-regex "^1.1.0" + +toidentifier@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" + integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== + +tough-cookie@^2.3.3, tough-cookie@^2.3.4, tough-cookie@^2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" + integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== + dependencies: + psl "^1.1.28" + punycode "^2.1.1" + +tough-cookie@~2.4.3: + version "2.4.3" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781" + dependencies: + psl "^1.1.24" + punycode "^1.4.1" + +tr46@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09" + integrity sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk= + dependencies: + punycode "^2.1.0" + +trim-newlines@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613" + +"true-case-path@^1.0.2": + version "1.0.3" + resolved "https://registry.yarnpkg.com/true-case-path/-/true-case-path-1.0.3.tgz#f813b5a8c86b40da59606722b144e3225799f47d" + dependencies: + glob "^7.1.2" + +ts-pnp@1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/ts-pnp/-/ts-pnp-1.1.6.tgz#389a24396d425a0d3162e96d2b4638900fdc289a" + integrity sha512-CrG5GqAAzMT7144Cl+UIFP7mz/iIhiy+xQ6GGcnjTezhALT02uPMRw7tgDSESgB5MsfKt55+GPWw4ir1kVtMIQ== + +ts-pnp@^1.1.6: + version "1.2.0" + resolved "https://registry.yarnpkg.com/ts-pnp/-/ts-pnp-1.2.0.tgz#a500ad084b0798f1c3071af391e65912c86bca92" + integrity sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw== + +tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.0: + version "1.13.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043" + integrity sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q== + +tsutils@^3.17.1: + version "3.17.1" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759" + integrity sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g== + dependencies: + tslib "^1.8.1" + +tty-browserify@0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" + +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + dependencies: + safe-buffer "^5.0.1" + +tweetnacl@^0.14.3, tweetnacl@~0.14.0: + version "0.14.5" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" + +type-check@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" + dependencies: + prelude-ls "~1.1.2" + +type-fest@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.11.0.tgz#97abf0872310fed88a5c466b25681576145e33f1" + integrity sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ== + +type-fest@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" + integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== + +type-is@~1.6.17, type-is@~1.6.18: + version "1.6.18" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" + integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== + dependencies: + media-typer "0.3.0" + mime-types "~2.1.24" + +typedarray@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" + +typescript-compare@^0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/typescript-compare/-/typescript-compare-0.0.2.tgz#7ee40a400a406c2ea0a7e551efd3309021d5f425" + integrity sha512-8ja4j7pMHkfLJQO2/8tut7ub+J3Lw2S3061eJLFQcvs3tsmJKp8KG5NtpLn7KcY2w08edF74BSVN7qJS0U6oHA== + dependencies: + typescript-logic "^0.0.0" + +typescript-logic@^0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/typescript-logic/-/typescript-logic-0.0.0.tgz#66ebd82a2548f2b444a43667bec120b496890196" + integrity sha512-zXFars5LUkI3zP492ls0VskH3TtdeHCqu0i7/duGt60i5IGPIpAHE/DWo5FqJ6EjQ15YKXrt+AETjv60Dat34Q== + +typescript-tuple@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/typescript-tuple/-/typescript-tuple-2.2.1.tgz#7d9813fb4b355f69ac55032e0363e8bb0f04dad2" + integrity sha512-Zcr0lbt8z5ZdEzERHAMAniTiIKerFCMgd7yjq1fPnDJ43et/k9twIFQMUYff9k5oXcsQ0WpvFcgzK2ZKASoW6Q== + dependencies: + typescript-compare "^0.0.2" + +unicode-canonical-property-names-ecmascript@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818" + integrity sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ== + +unicode-match-property-ecmascript@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz#8ed2a32569961bce9227d09cd3ffbb8fed5f020c" + integrity sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg== + dependencies: + unicode-canonical-property-names-ecmascript "^1.0.4" + unicode-property-aliases-ecmascript "^1.0.4" + +unicode-match-property-value-ecmascript@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz#0d91f600eeeb3096aa962b1d6fc88876e64ea531" + integrity sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ== + +unicode-property-aliases-ecmascript@^1.0.4: + version "1.1.0" + resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz#dd57a99f6207bedff4628abefb94c50db941c8f4" + integrity sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg== + +union-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.0.tgz#5c71c34cb5bad5dcebe3ea0cd08207ba5aa1aea4" + dependencies: + arr-union "^3.1.0" + get-value "^2.0.6" + is-extendable "^0.1.1" + set-value "^0.4.3" + +uniq@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff" + +uniqs@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/uniqs/-/uniqs-2.0.0.tgz#ffede4b36b25290696e6e165d4a59edb998e6b02" + +unique-filename@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230" + integrity sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ== + dependencies: + unique-slug "^2.0.0" + +unique-slug@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.2.tgz#baabce91083fc64e945b0f3ad613e264f7cd4e6c" + integrity sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w== + dependencies: + imurmurhash "^0.1.4" + +universalify@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" + +unpipe@1.0.0, unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + +unquote@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/unquote/-/unquote-1.1.1.tgz#8fded7324ec6e88a0ff8b905e7c098cdc086d544" + integrity sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ= + +unset-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" + dependencies: + has-value "^0.3.1" + isobject "^3.0.0" + +upath@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" + integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg== + +uri-js@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" + dependencies: + punycode "^2.1.0" + +urix@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" + +url-loader@2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-2.3.0.tgz#e0e2ef658f003efb8ca41b0f3ffbf76bab88658b" + integrity sha512-goSdg8VY+7nPZKUEChZSEtW5gjbS66USIGCeSJ1OVOJ7Yfuh/36YxCwMi5HVEJh6mqUYOoy3NJ0vlOMrWsSHog== + dependencies: + loader-utils "^1.2.3" + mime "^2.4.4" + schema-utils "^2.5.0" + +url-parse@^1.4.3: + version "1.4.4" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.4.tgz#cac1556e95faa0303691fec5cf9d5a1bc34648f8" + dependencies: + querystringify "^2.0.0" + requires-port "^1.0.0" + +url@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" + dependencies: + punycode "1.3.2" + querystring "0.2.0" + +use@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" + +util-deprecate@^1.0.1, util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + +util.promisify@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.0.tgz#440f7165a459c9a16dc145eb8e72f35687097030" + integrity sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA== + dependencies: + define-properties "^1.1.2" + object.getownpropertydescriptors "^2.0.3" + +util.promisify@^1.0.0, util.promisify@~1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.1.tgz#6baf7774b80eeb0f7520d8b81d07982a59abbaee" + integrity sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.2" + has-symbols "^1.0.1" + object.getownpropertydescriptors "^2.1.0" + +util@0.10.3: + version "0.10.3" + resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9" + dependencies: + inherits "2.0.1" + +util@^0.11.0: + version "0.11.1" + resolved "https://registry.yarnpkg.com/util/-/util-0.11.1.tgz#3236733720ec64bb27f6e26f421aaa2e1b588d61" + dependencies: + inherits "2.0.3" + +utila@^0.4.0, utila@~0.4: + version "0.4.0" + resolved "https://registry.yarnpkg.com/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c" + +utils-merge@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" + +uuid@^3.0.1, uuid@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" + +v8-compile-cache@^2.0.3: + version "2.1.1" + resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz#54bc3cdd43317bca91e35dcaf305b1a7237de745" + integrity sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ== + +validate-npm-package-license@^3.0.1: + version "3.0.4" + resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" + dependencies: + spdx-correct "^3.0.0" + spdx-expression-parse "^3.0.0" + +value-equal@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-1.0.1.tgz#1e0b794c734c5c0cade179c437d356d931a34d6c" + integrity sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw== + +vary@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + +vendors@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.2.tgz#7fcb5eef9f5623b156bcea89ec37d63676f21801" + +verror@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" + dependencies: + assert-plus "^1.0.0" + core-util-is "1.0.2" + extsprintf "^1.2.0" + +victory-area@^34.2.2: + version "34.3.8" + resolved "https://registry.yarnpkg.com/victory-area/-/victory-area-34.3.8.tgz#4e07da6250af6729ca96c87826430ecb18abe5e4" + integrity sha512-iN5jqYxQ2qE5fGCWgJJ2YoObRgcY4h23GiFHee8OG8dKLDCQvsR4/ZKdI4I78iLrIJf4eHeKWW+1xXheoG6A6w== + dependencies: + d3-shape "^1.2.0" + lodash "^4.17.15" + prop-types "^15.5.8" + victory-core "^34.3.8" + +victory-axis@^34.2.2, victory-axis@^34.3.8: + version "34.3.8" + resolved "https://registry.yarnpkg.com/victory-axis/-/victory-axis-34.3.8.tgz#a0754a4e4d4986898fdb0bd5cd204b94d5866c06" + integrity sha512-GD0/dJAV7hY22owiC3rKalJkf6C8WZsuu3HgzBYxySr7aiQgcJDhJ+T2DIH2DH7zUQi0FeD7lA1dyDagYdcnGA== + dependencies: + lodash "^4.17.15" + prop-types "^15.5.8" + victory-core "^34.3.8" + +victory-bar@^34.2.2: + version "34.3.8" + resolved "https://registry.yarnpkg.com/victory-bar/-/victory-bar-34.3.8.tgz#9dee4ef85505fc83c74102d35964d2e20d9d43fb" + integrity sha512-0LBT/DrScUTwgA1wkmZ1dI34Jt0sRM/1f+LKl92GAFQIQiwznzOFs0KBhywExf7weYw68mhVA6iUvwK+4uf17A== + dependencies: + d3-shape "^1.2.0" + lodash "^4.17.15" + prop-types "^15.5.8" + victory-core "^34.3.8" + +victory-box-plot@^34.2.2: + version "34.3.8" + resolved "https://registry.yarnpkg.com/victory-box-plot/-/victory-box-plot-34.3.8.tgz#0aeeba93e4dcf4ebd787380f6e535acad479b41c" + integrity sha512-Y0n6tVeUZrLbUkK69rJiyY0rcLMAnbUrQ2kqq8d0JF+UrYfvZQBvr6M4IVVrI0zoGUpqmx7ach5VTD6g6VEiug== + dependencies: + d3-array "^1.2.0" + lodash "^4.17.15" + prop-types "^15.5.8" + victory-core "^34.3.8" + +victory-brush-container@^34.2.2, victory-brush-container@^34.3.8: + version "34.3.8" + resolved "https://registry.yarnpkg.com/victory-brush-container/-/victory-brush-container-34.3.8.tgz#83d607eb896c21694926295cf248063de6d605b7" + integrity sha512-/vQy/yEmoiUkuzVKBdMIevpez0JsqcTqFdRQtXbRmgK+1Rxt6z0HgtQazGEm7K80xHw+2yajSyG5G68ksjFjqg== + dependencies: + lodash "^4.17.15" + prop-types "^15.5.8" + react-fast-compare "^2.0.0" + victory-core "^34.3.8" + +victory-brush-line@^34.2.2: + version "34.3.8" + resolved "https://registry.yarnpkg.com/victory-brush-line/-/victory-brush-line-34.3.8.tgz#5fd6d33d235dbc25346772efb9b2aecd289703fc" + integrity sha512-mY7ozEgTqvJCxxNhyHhOLk6M3CPDDFJKuUFT9B2vmQyPU5/EmFQuErVo6bl1hWkj1nzm5q916qt6GsVPC3mTdg== + dependencies: + lodash "^4.17.15" + prop-types "^15.5.8" + react-fast-compare "^2.0.0" + victory-core "^34.3.8" + +victory-candlestick@^34.2.2: + version "34.3.8" + resolved "https://registry.yarnpkg.com/victory-candlestick/-/victory-candlestick-34.3.8.tgz#c16d918044f864c065f5c9c93d55c9f4aeb479ef" + integrity sha512-qvaEJsNPjKN25yu9TcH8+PZ2yBTOXvgH9Faup37jeIoKXtTVaHI1uYuZcLlp542tSbAnq/5EkQTiPXCx/C31Lg== + dependencies: + lodash "^4.17.15" + prop-types "^15.5.8" + victory-core "^34.3.8" + +victory-chart@^34.2.2: + version "34.3.9" + resolved "https://registry.yarnpkg.com/victory-chart/-/victory-chart-34.3.9.tgz#f549ee42a99465ad5ed2caeff5de8620360a11cc" + integrity sha512-okg17A21t1St7FqcKMgPDMNdHqQjx3l5EB7Xy+gxX1TvCWpG9AlWILYOWuEWFjO28X36Dd732TxEJRBs0eV0Lw== + dependencies: + lodash "^4.17.15" + prop-types "^15.5.8" + react-fast-compare "^2.0.0" + victory-axis "^34.3.8" + victory-core "^34.3.8" + victory-polar-axis "^34.3.8" + victory-shared-events "^34.3.9" + +victory-core@^34.2.2, victory-core@^34.3.8: + version "34.3.8" + resolved "https://registry.yarnpkg.com/victory-core/-/victory-core-34.3.8.tgz#86c951599416c4ddd0f6e8bee7214baab0b8d478" + integrity sha512-m3xRt05VywEb3rgAzDAZ1pgWcjeyazbRzLLmXyrS+78U/AkmvZFjgq+26o+3+tqD7s7O4jvSeFqg0XmVi51+EQ== + dependencies: + d3-ease "^1.0.0" + d3-interpolate "^1.1.1" + d3-scale "^1.0.0" + d3-shape "^1.2.0" + d3-timer "^1.0.0" + lodash "^4.17.15" + prop-types "^15.5.8" + react-fast-compare "^2.0.0" + +victory-create-container@^34.2.2: + version "34.3.10" + resolved "https://registry.yarnpkg.com/victory-create-container/-/victory-create-container-34.3.10.tgz#e024db9755224a14ccc7434ab703be7cfc99ef55" + integrity sha512-JUlUHfw3Lzw8as8kyYlj2sFkrU7ER+kC/bSGDUIyYZ9ChVdXc4xy/9DzgXA/lswp5JhE92p+TGaDXToUn8UygA== + dependencies: + lodash "^4.17.15" + victory-brush-container "^34.3.8" + victory-core "^34.3.8" + victory-cursor-container "^34.3.10" + victory-selection-container "^34.3.8" + victory-voronoi-container "^34.3.8" + victory-zoom-container "^34.3.8" + +victory-cursor-container@^34.2.2, victory-cursor-container@^34.3.10: + version "34.3.10" + resolved "https://registry.yarnpkg.com/victory-cursor-container/-/victory-cursor-container-34.3.10.tgz#708a7c15bf1e172d797103e8ebf8f273103529e7" + integrity sha512-Gve4i6fjhklCTW/k+RYZBf6UBAq4M/1HA32KEa/TtrQVaewLUu7O564ndcQDCqWiQEIBjKdSbEfgiq6fqT9zag== + dependencies: + lodash "^4.17.15" + prop-types "^15.5.8" + victory-core "^34.3.8" + +victory-errorbar@^34.2.2: + version "34.3.8" + resolved "https://registry.yarnpkg.com/victory-errorbar/-/victory-errorbar-34.3.8.tgz#23c042c667e85f2d677dee959644e9825ac513d8" + integrity sha512-AIHK3LMj3HapFQthls58C6oma0reDmCNFJoqhor+9LFC0KTCsCrIZXFthSyDtzT+arOxuB6vu3j4ayOGXsurkA== + dependencies: + lodash "^4.17.15" + prop-types "^15.5.8" + victory-core "^34.3.8" + +victory-group@^34.2.2: + version "34.3.10" + resolved "https://registry.yarnpkg.com/victory-group/-/victory-group-34.3.10.tgz#c25ac1e888f95f6284226bcf8b04744d604b7500" + integrity sha512-PAUjqs5O+lPc50ZB9/TK7dx/ICCB0/jscl9ZwjxcpogfQhXL/hDnOCoaF+TpH9KuvK4Tth1DQIVzw7TaUxeeWA== + dependencies: + lodash "^4.17.15" + prop-types "^15.5.8" + react-fast-compare "^2.0.0" + victory-core "^34.3.8" + victory-shared-events "^34.3.9" + +victory-legend@^34.2.2: + version "34.3.8" + resolved "https://registry.yarnpkg.com/victory-legend/-/victory-legend-34.3.8.tgz#bed74657cf13cb02fc49fb4825893ab854df1d1a" + integrity sha512-FZRCK2rETIUIS9rSbLPBgvwTetKfnT/MwvOnTG+wD29fio7cHloGHHAhxbTbhFFcs9AQRKZVpgsOA+nlb4Byew== + dependencies: + lodash "^4.17.15" + prop-types "^15.5.8" + victory-core "^34.3.8" + +victory-line@^34.2.2: + version "34.3.8" + resolved "https://registry.yarnpkg.com/victory-line/-/victory-line-34.3.8.tgz#4b44b8a7166ce7192b7b5a3d75c9459250b7ee16" + integrity sha512-lxA+ncyusT230wHOzrY0SzFWQLHTHjJVA558AkrV9zB7lcUblxMoeUhaV2hgv9yWfhi6Dy/Ap3drH/Vnnz/n0Q== + dependencies: + d3-shape "^1.2.0" + lodash "^4.17.15" + prop-types "^15.5.8" + victory-core "^34.3.8" + +victory-pie@^34.2.2: + version "34.3.8" + resolved "https://registry.yarnpkg.com/victory-pie/-/victory-pie-34.3.8.tgz#d7dc9f34030abba8f2c81962cd394f384570264a" + integrity sha512-S23Y9cBsYEe/EWcVjz9Nfu/F905yjRWtVhoCEf6e+tJp3Fy7cUBQOyE1UiNKm9LEoUxrOSHhsmS4d23qIWuw6w== + dependencies: + d3-shape "^1.0.0" + lodash "^4.17.15" + prop-types "^15.5.8" + victory-core "^34.3.8" + +victory-polar-axis@^34.2.2, victory-polar-axis@^34.3.8: + version "34.3.8" + resolved "https://registry.yarnpkg.com/victory-polar-axis/-/victory-polar-axis-34.3.8.tgz#d6b8cf5686abafd89c311bbfebd87880e500826b" + integrity sha512-8ziguUnYzVlHL699AwVVUhaRSXdV1+zwi1ycE/ouW/9QyzC32NVsq+De2S8Sxf3PJ2wbOF51B6/kAWrppsNhGw== + dependencies: + lodash "^4.17.15" + prop-types "^15.5.8" + victory-core "^34.3.8" + +victory-scatter@^34.2.2: + version "34.3.8" + resolved "https://registry.yarnpkg.com/victory-scatter/-/victory-scatter-34.3.8.tgz#0c7afe1ed8268c2d045717bddc593cd3c4b73092" + integrity sha512-xocPcCD1um6DhcB6h+3ENbXlesd1EzhwVdIuLHmvdM2t32UpVxH+kX9gv/rvtDRoIaZNpb8vlbu/jWJJDY8K/g== + dependencies: + lodash "^4.17.15" + prop-types "^15.5.8" + victory-core "^34.3.8" + +victory-selection-container@^34.2.2, victory-selection-container@^34.3.8: + version "34.3.8" + resolved "https://registry.yarnpkg.com/victory-selection-container/-/victory-selection-container-34.3.8.tgz#b035271a1a3d2c0873d65e40b0053fa62539be01" + integrity sha512-Nb+EITn2REY8NtE29qlxv2ypM2fWrqVlhx8QYpeIFngii7VXeinudTF7bkDqL5XDurmce+P//WQ86nMtOLgGig== + dependencies: + lodash "^4.17.15" + prop-types "^15.5.8" + victory-core "^34.3.8" + +victory-shared-events@^34.2.2, victory-shared-events@^34.3.9: + version "34.3.9" + resolved "https://registry.yarnpkg.com/victory-shared-events/-/victory-shared-events-34.3.9.tgz#3f42f86eacff56156ba30e0567fc928cf28dd08d" + integrity sha512-cW4/tI2VDB+R3LzD0ZtjRc1Bo5X5CCPIZ6hJ/8q9nAfyvitHdpPyL42kO5/o2v62ksSl0kR0Zlyn4eEonXKKpg== + dependencies: + lodash "^4.17.15" + prop-types "^15.5.8" + react-fast-compare "^2.0.0" + victory-core "^34.3.8" + +victory-stack@^34.2.2: + version "34.3.9" + resolved "https://registry.yarnpkg.com/victory-stack/-/victory-stack-34.3.9.tgz#9c0b360baf0da9ca1d57606d68c8c2d6581128c8" + integrity sha512-wTbEeYFOyG/i5o2YA5blJFXb/+arCEfuzxBTg44Yu2fOcd9MJs9amBwlOo+e94Dd+pwsQeMvkzhpRKYlzFXsdQ== + dependencies: + lodash "^4.17.15" + prop-types "^15.5.8" + react-fast-compare "^2.0.0" + victory-core "^34.3.8" + victory-shared-events "^34.3.9" + +victory-tooltip@^34.2.2, victory-tooltip@^34.3.8: + version "34.3.8" + resolved "https://registry.yarnpkg.com/victory-tooltip/-/victory-tooltip-34.3.8.tgz#fb5c8d249f9d61cad089e0646ea7ea564385c864" + integrity sha512-iJ/VoDS7DphDHnYZqcfvTY/q0XMvOjU3DjwVE/A9MaQAbu+xmdYSgIn7BG5YIjSpuUglzi7h3xmK248m0RnyCw== + dependencies: + lodash "^4.17.15" + prop-types "^15.5.8" + victory-core "^34.3.8" + +victory-voronoi-container@^34.2.2, victory-voronoi-container@^34.3.8: + version "34.3.8" + resolved "https://registry.yarnpkg.com/victory-voronoi-container/-/victory-voronoi-container-34.3.8.tgz#a03124be87f34cf07139a2d9c53cc6a48457b41e" + integrity sha512-wx46Mf3BEprtuQ2CRod6KcO1MHm0wxOEn/NO9qzOi3GnnD+CVsVfvIG6gFTDbjoo+fsKE7fW8AWYWo8oqx9fxg== + dependencies: + delaunay-find "0.0.5" + lodash "^4.17.15" + prop-types "^15.5.8" + react-fast-compare "^2.0.0" + victory-core "^34.3.8" + victory-tooltip "^34.3.8" + +victory-voronoi@^34.2.2: + version "34.3.8" + resolved "https://registry.yarnpkg.com/victory-voronoi/-/victory-voronoi-34.3.8.tgz#bc35fa299adcb9c630f3c955514ec8e3cb3d1ab6" + integrity sha512-xXn3iYk9SPm8eRofU+tyk6dWPt7hwm29068M1l6AHGfjwI1Z/wLBc+VrlIPiqswBtLc3umxvmxurXQsqH3k8NQ== + dependencies: + d3-voronoi "^1.1.2" + lodash "^4.17.15" + prop-types "^15.5.8" + victory-core "^34.3.8" + +victory-zoom-container@^34.2.2, victory-zoom-container@^34.3.8: + version "34.3.8" + resolved "https://registry.yarnpkg.com/victory-zoom-container/-/victory-zoom-container-34.3.8.tgz#716613594ed6139bd448f6cefd6b64e2bc317a82" + integrity sha512-U39CScIXcOVgQqKqgboBTeGxLTkrBRxKi991HGEFI9fD22aNj4bCs+TR0k/qeAz6oi+oDkLvxI74D+7uSNQD1w== + dependencies: + lodash "^4.17.15" + prop-types "^15.5.8" + victory-core "^34.3.8" + +victory@~34.2.1: + version "34.2.2" + resolved "https://registry.yarnpkg.com/victory/-/victory-34.2.2.tgz#e0ec49518ef359ce5ba2dbf49f1d256cdce570f0" + integrity sha512-ZS3koXrdt9XbYp2RcoHt1Ow7LOgpNmTKz1KKcWKa7NwygI+Yf5bUmbZzlw9Qd6lgvblsUfV5i04CvO5GJsYpOA== + dependencies: + victory-area "^34.2.2" + victory-axis "^34.2.2" + victory-bar "^34.2.2" + victory-box-plot "^34.2.2" + victory-brush-container "^34.2.2" + victory-brush-line "^34.2.2" + victory-candlestick "^34.2.2" + victory-chart "^34.2.2" + victory-core "^34.2.2" + victory-create-container "^34.2.2" + victory-cursor-container "^34.2.2" + victory-errorbar "^34.2.2" + victory-group "^34.2.2" + victory-legend "^34.2.2" + victory-line "^34.2.2" + victory-pie "^34.2.2" + victory-polar-axis "^34.2.2" + victory-scatter "^34.2.2" + victory-selection-container "^34.2.2" + victory-shared-events "^34.2.2" + victory-stack "^34.2.2" + victory-tooltip "^34.2.2" + victory-voronoi "^34.2.2" + victory-voronoi-container "^34.2.2" + victory-zoom-container "^34.2.2" + +vm-browserify@^1.0.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" + integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== + +w3c-hr-time@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd" + integrity sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ== + dependencies: + browser-process-hrtime "^1.0.0" + +w3c-xmlserializer@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-1.1.2.tgz#30485ca7d70a6fd052420a3d12fd90e6339ce794" + integrity sha512-p10l/ayESzrBMYWRID6xbuCKh2Fp77+sA0doRuGn4tTIMrrZVeqfpKjXHY+oDh3K4nLdPgNwMTVP6Vp4pvqbNg== + dependencies: + domexception "^1.0.1" + webidl-conversions "^4.0.2" + xml-name-validator "^3.0.0" + +walker@^1.0.7, walker@~1.0.5: + version "1.0.7" + resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb" + integrity sha1-L3+bj9ENZ3JisYqITijRlhjgKPs= + dependencies: + makeerror "1.0.x" + +watchpack-chokidar2@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/watchpack-chokidar2/-/watchpack-chokidar2-2.0.0.tgz#9948a1866cbbd6cb824dea13a7ed691f6c8ddff0" + integrity sha512-9TyfOyN/zLUbA288wZ8IsMZ+6cbzvsNyEzSBp6e/zkifi6xxbl8SmQ/CxQq32k8NNqrdVEVUVSEf56L4rQ/ZxA== + dependencies: + chokidar "^2.1.8" + +watchpack@^1.6.0: + version "1.7.2" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.7.2.tgz#c02e4d4d49913c3e7e122c3325365af9d331e9aa" + integrity sha512-ymVbbQP40MFTp+cNMvpyBpBtygHnPzPkHqoIwRRj/0B8KhqQwV8LaKjtbaxF2lK4vl8zN9wCxS46IFCU5K4W0g== + dependencies: + graceful-fs "^4.1.2" + neo-async "^2.5.0" + optionalDependencies: + chokidar "^3.4.0" + watchpack-chokidar2 "^2.0.0" + +wbuf@^1.1.0, wbuf@^1.7.3: + version "1.7.3" + resolved "https://registry.yarnpkg.com/wbuf/-/wbuf-1.7.3.tgz#c1d8d149316d3ea852848895cb6a0bfe887b87df" + dependencies: + minimalistic-assert "^1.0.0" + +webidl-conversions@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" + integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg== + +webpack-dev-middleware@^3.7.2: + version "3.7.2" + resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.7.2.tgz#0019c3db716e3fa5cecbf64f2ab88a74bab331f3" + integrity sha512-1xC42LxbYoqLNAhV6YzTYacicgMZQTqRd27Sim9wn5hJrX3I5nxYy1SxSd4+gjUFsz1dQFj+yEe6zEVmSkeJjw== + dependencies: + memory-fs "^0.4.1" + mime "^2.4.4" + mkdirp "^0.5.1" + range-parser "^1.2.1" + webpack-log "^2.0.0" + +webpack-dev-server@3.10.3: + version "3.10.3" + resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-3.10.3.tgz#f35945036813e57ef582c2420ef7b470e14d3af0" + integrity sha512-e4nWev8YzEVNdOMcNzNeCN947sWJNd43E5XvsJzbAL08kGc2frm1tQ32hTJslRS+H65LCb/AaUCYU7fjHCpDeQ== + dependencies: + ansi-html "0.0.7" + bonjour "^3.5.0" + chokidar "^2.1.8" + compression "^1.7.4" + connect-history-api-fallback "^1.6.0" + debug "^4.1.1" + del "^4.1.1" + express "^4.17.1" + html-entities "^1.2.1" + http-proxy-middleware "0.19.1" + import-local "^2.0.0" + internal-ip "^4.3.0" + ip "^1.1.5" + is-absolute-url "^3.0.3" + killable "^1.0.1" + loglevel "^1.6.6" + opn "^5.5.0" + p-retry "^3.0.1" + portfinder "^1.0.25" + schema-utils "^1.0.0" + selfsigned "^1.10.7" + semver "^6.3.0" + serve-index "^1.9.1" + sockjs "0.3.19" + sockjs-client "1.4.0" + spdy "^4.0.1" + strip-ansi "^3.0.1" + supports-color "^6.1.0" + url "^0.11.0" + webpack-dev-middleware "^3.7.2" + webpack-log "^2.0.0" + ws "^6.2.1" + yargs "12.0.5" + +webpack-log@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/webpack-log/-/webpack-log-2.0.0.tgz#5b7928e0637593f119d32f6227c1e0ac31e1b47f" + integrity sha512-cX8G2vR/85UYG59FgkoMamwHUIkSSlV3bBMRsbxVXVUk2j6NleCKjQ/WE9eYg9WY4w25O9w8wKP4rzNZFmUcUg== + dependencies: + ansi-colors "^3.0.0" + uuid "^3.3.2" + +webpack-manifest-plugin@2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/webpack-manifest-plugin/-/webpack-manifest-plugin-2.2.0.tgz#19ca69b435b0baec7e29fbe90fb4015de2de4f16" + integrity sha512-9S6YyKKKh/Oz/eryM1RyLVDVmy3NSPV0JXMRhZ18fJsq+AwGxUY34X54VNwkzYcEmEkDwNxuEOboCZEebJXBAQ== + dependencies: + fs-extra "^7.0.0" + lodash ">=3.5 <5" + object.entries "^1.1.0" + tapable "^1.0.0" + +webpack-sources@^1.1.0, webpack-sources@^1.4.0, webpack-sources@^1.4.1, webpack-sources@^1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933" + integrity sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ== + dependencies: + source-list-map "^2.0.0" + source-map "~0.6.1" + +webpack@4.42.0: + version "4.42.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.42.0.tgz#b901635dd6179391d90740a63c93f76f39883eb8" + integrity sha512-EzJRHvwQyBiYrYqhyjW9AqM90dE4+s1/XtCfn7uWg6cS72zH+2VPFAlsnW0+W0cDi0XRjNKUMoJtpSi50+Ph6w== + dependencies: + "@webassemblyjs/ast" "1.8.5" + "@webassemblyjs/helper-module-context" "1.8.5" + "@webassemblyjs/wasm-edit" "1.8.5" + "@webassemblyjs/wasm-parser" "1.8.5" + acorn "^6.2.1" + ajv "^6.10.2" + ajv-keywords "^3.4.1" + chrome-trace-event "^1.0.2" + enhanced-resolve "^4.1.0" + eslint-scope "^4.0.3" + json-parse-better-errors "^1.0.2" + loader-runner "^2.4.0" + loader-utils "^1.2.3" + memory-fs "^0.4.1" + micromatch "^3.1.10" + mkdirp "^0.5.1" + neo-async "^2.6.1" + node-libs-browser "^2.2.1" + schema-utils "^1.0.0" + tapable "^1.1.3" + terser-webpack-plugin "^1.4.3" + watchpack "^1.6.0" + webpack-sources "^1.4.1" + +websocket-driver@>=0.5.1: + version "0.7.0" + resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.0.tgz#0caf9d2d755d93aee049d4bdd0d3fe2cca2a24eb" + dependencies: + http-parser-js ">=0.4.0" + websocket-extensions ">=0.1.1" + +websocket-extensions@>=0.1.1: + version "0.1.3" + resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.3.tgz#5d2ff22977003ec687a4b87073dfbbac146ccf29" + +whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3, whatwg-encoding@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0" + dependencies: + iconv-lite "0.4.24" + +whatwg-fetch@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz#fc804e458cc460009b1a2b966bc8817d2578aefb" + integrity sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q== + +whatwg-mimetype@^2.1.0, whatwg-mimetype@^2.2.0, whatwg-mimetype@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" + integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== + +whatwg-url@^6.4.1: + version "6.5.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-6.5.0.tgz#f2df02bff176fd65070df74ad5ccbb5a199965a8" + integrity sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ== + dependencies: + lodash.sortby "^4.7.0" + tr46 "^1.0.1" + webidl-conversions "^4.0.2" + +whatwg-url@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-7.1.0.tgz#c2c492f1eca612988efd3d2266be1b9fc6170d06" + integrity sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg== + dependencies: + lodash.sortby "^4.7.0" + tr46 "^1.0.1" + webidl-conversions "^4.0.2" + +which-module@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f" + +which-module@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" + +which-pm-runs@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/which-pm-runs/-/which-pm-runs-1.0.0.tgz#670b3afbc552e0b55df6b7780ca74615f23ad1cb" + integrity sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs= + +which@1, which@^1.2.9, which@^1.3.0, which@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + dependencies: + isexe "^2.0.0" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +wide-align@^1.1.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" + dependencies: + string-width "^1.0.2 || 2" + +word-wrap@~1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" + integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + +wordwrap@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" + +workbox-background-sync@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/workbox-background-sync/-/workbox-background-sync-4.3.1.tgz#26821b9bf16e9e37fd1d640289edddc08afd1950" + integrity sha512-1uFkvU8JXi7L7fCHVBEEnc3asPpiAL33kO495UMcD5+arew9IbKW2rV5lpzhoWcm/qhGB89YfO4PmB/0hQwPRg== + dependencies: + workbox-core "^4.3.1" + +workbox-broadcast-update@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/workbox-broadcast-update/-/workbox-broadcast-update-4.3.1.tgz#e2c0280b149e3a504983b757606ad041f332c35b" + integrity sha512-MTSfgzIljpKLTBPROo4IpKjESD86pPFlZwlvVG32Kb70hW+aob4Jxpblud8EhNb1/L5m43DUM4q7C+W6eQMMbA== + dependencies: + workbox-core "^4.3.1" + +workbox-build@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/workbox-build/-/workbox-build-4.3.1.tgz#414f70fb4d6de47f6538608b80ec52412d233e64" + integrity sha512-UHdwrN3FrDvicM3AqJS/J07X0KXj67R8Cg0waq1MKEOqzo89ap6zh6LmaLnRAjpB+bDIz+7OlPye9iii9KBnxw== + dependencies: + "@babel/runtime" "^7.3.4" + "@hapi/joi" "^15.0.0" + common-tags "^1.8.0" + fs-extra "^4.0.2" + glob "^7.1.3" + lodash.template "^4.4.0" + pretty-bytes "^5.1.0" + stringify-object "^3.3.0" + strip-comments "^1.0.2" + workbox-background-sync "^4.3.1" + workbox-broadcast-update "^4.3.1" + workbox-cacheable-response "^4.3.1" + workbox-core "^4.3.1" + workbox-expiration "^4.3.1" + workbox-google-analytics "^4.3.1" + workbox-navigation-preload "^4.3.1" + workbox-precaching "^4.3.1" + workbox-range-requests "^4.3.1" + workbox-routing "^4.3.1" + workbox-strategies "^4.3.1" + workbox-streams "^4.3.1" + workbox-sw "^4.3.1" + workbox-window "^4.3.1" + +workbox-cacheable-response@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/workbox-cacheable-response/-/workbox-cacheable-response-4.3.1.tgz#f53e079179c095a3f19e5313b284975c91428c91" + integrity sha512-Rp5qlzm6z8IOvnQNkCdO9qrDgDpoPNguovs0H8C+wswLuPgSzSp9p2afb5maUt9R1uTIwOXrVQMmPfPypv+npw== + dependencies: + workbox-core "^4.3.1" + +workbox-core@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/workbox-core/-/workbox-core-4.3.1.tgz#005d2c6a06a171437afd6ca2904a5727ecd73be6" + integrity sha512-I3C9jlLmMKPxAC1t0ExCq+QoAMd0vAAHULEgRZ7kieCdUd919n53WC0AfvokHNwqRhGn+tIIj7vcb5duCjs2Kg== + +workbox-expiration@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/workbox-expiration/-/workbox-expiration-4.3.1.tgz#d790433562029e56837f341d7f553c4a78ebe921" + integrity sha512-vsJLhgQsQouv9m0rpbXubT5jw0jMQdjpkum0uT+d9tTwhXcEZks7qLfQ9dGSaufTD2eimxbUOJfWLbNQpIDMPw== + dependencies: + workbox-core "^4.3.1" + +workbox-google-analytics@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/workbox-google-analytics/-/workbox-google-analytics-4.3.1.tgz#9eda0183b103890b5c256e6f4ea15a1f1548519a" + integrity sha512-xzCjAoKuOb55CBSwQrbyWBKqp35yg1vw9ohIlU2wTy06ZrYfJ8rKochb1MSGlnoBfXGWss3UPzxR5QL5guIFdg== + dependencies: + workbox-background-sync "^4.3.1" + workbox-core "^4.3.1" + workbox-routing "^4.3.1" + workbox-strategies "^4.3.1" + +workbox-navigation-preload@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/workbox-navigation-preload/-/workbox-navigation-preload-4.3.1.tgz#29c8e4db5843803b34cd96dc155f9ebd9afa453d" + integrity sha512-K076n3oFHYp16/C+F8CwrRqD25GitA6Rkd6+qAmLmMv1QHPI2jfDwYqrytOfKfYq42bYtW8Pr21ejZX7GvALOw== + dependencies: + workbox-core "^4.3.1" + +workbox-precaching@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/workbox-precaching/-/workbox-precaching-4.3.1.tgz#9fc45ed122d94bbe1f0ea9584ff5940960771cba" + integrity sha512-piSg/2csPoIi/vPpp48t1q5JLYjMkmg5gsXBQkh/QYapCdVwwmKlU9mHdmy52KsDGIjVaqEUMFvEzn2LRaigqQ== + dependencies: + workbox-core "^4.3.1" + +workbox-range-requests@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/workbox-range-requests/-/workbox-range-requests-4.3.1.tgz#f8a470188922145cbf0c09a9a2d5e35645244e74" + integrity sha512-S+HhL9+iTFypJZ/yQSl/x2Bf5pWnbXdd3j57xnb0V60FW1LVn9LRZkPtneODklzYuFZv7qK6riZ5BNyc0R0jZA== + dependencies: + workbox-core "^4.3.1" + +workbox-routing@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/workbox-routing/-/workbox-routing-4.3.1.tgz#a675841af623e0bb0c67ce4ed8e724ac0bed0cda" + integrity sha512-FkbtrODA4Imsi0p7TW9u9MXuQ5P4pVs1sWHK4dJMMChVROsbEltuE79fBoIk/BCztvOJ7yUpErMKa4z3uQLX+g== + dependencies: + workbox-core "^4.3.1" + +workbox-strategies@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/workbox-strategies/-/workbox-strategies-4.3.1.tgz#d2be03c4ef214c115e1ab29c9c759c9fe3e9e646" + integrity sha512-F/+E57BmVG8dX6dCCopBlkDvvhg/zj6VDs0PigYwSN23L8hseSRwljrceU2WzTvk/+BSYICsWmRq5qHS2UYzhw== + dependencies: + workbox-core "^4.3.1" + +workbox-streams@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/workbox-streams/-/workbox-streams-4.3.1.tgz#0b57da70e982572de09c8742dd0cb40a6b7c2cc3" + integrity sha512-4Kisis1f/y0ihf4l3u/+ndMkJkIT4/6UOacU3A4BwZSAC9pQ9vSvJpIi/WFGQRH/uPXvuVjF5c2RfIPQFSS2uA== + dependencies: + workbox-core "^4.3.1" + +workbox-sw@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/workbox-sw/-/workbox-sw-4.3.1.tgz#df69e395c479ef4d14499372bcd84c0f5e246164" + integrity sha512-0jXdusCL2uC5gM3yYFT6QMBzKfBr2XTk0g5TPAV4y8IZDyVNDyj1a8uSXy3/XrvkVTmQvLN4O5k3JawGReXr9w== + +workbox-webpack-plugin@4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/workbox-webpack-plugin/-/workbox-webpack-plugin-4.3.1.tgz#47ff5ea1cc074b6c40fb5a86108863a24120d4bd" + integrity sha512-gJ9jd8Mb8wHLbRz9ZvGN57IAmknOipD3W4XNE/Lk/4lqs5Htw4WOQgakQy/o/4CoXQlMCYldaqUg+EJ35l9MEQ== + dependencies: + "@babel/runtime" "^7.0.0" + json-stable-stringify "^1.0.1" + workbox-build "^4.3.1" + +workbox-window@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/workbox-window/-/workbox-window-4.3.1.tgz#ee6051bf10f06afa5483c9b8dfa0531994ede0f3" + integrity sha512-C5gWKh6I58w3GeSc0wp2Ne+rqVw8qwcmZnQGpjiek8A2wpbxSJb1FdCoQVO+jDJs35bFgo/WETgl1fqgsxN0Hg== + dependencies: + workbox-core "^4.3.1" + +worker-farm@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.7.0.tgz#26a94c5391bbca926152002f69b84a4bf772e5a8" + integrity sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw== + dependencies: + errno "~0.1.7" + +worker-rpc@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/worker-rpc/-/worker-rpc-0.1.1.tgz#cb565bd6d7071a8f16660686051e969ad32f54d5" + integrity sha512-P1WjMrUB3qgJNI9jfmpZ/htmBEjFh//6l/5y8SD9hg1Ef5zTTVVoRjTrTEzPrNBQvmhMxkoTsjOXN10GWU7aCg== + dependencies: + microevent.ts "~0.1.1" + +wrap-ansi@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" + dependencies: + string-width "^1.0.1" + strip-ansi "^3.0.1" + +wrap-ansi@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" + integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q== + dependencies: + ansi-styles "^3.2.0" + string-width "^3.0.0" + strip-ansi "^5.0.0" + +wrap-ansi@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" + integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + +write-file-atomic@2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.4.1.tgz#d0b05463c188ae804396fd5ab2a370062af87529" + integrity sha512-TGHFeZEZMnv+gBFRfjAcxL5bPHrsGKtnb4qsFAws7/vlh+QfwAaySIw4AXP9ZskTTh5GWu3FLuJhsWVdiJPGvg== + dependencies: + graceful-fs "^4.1.11" + imurmurhash "^0.1.4" + signal-exit "^3.0.2" + +write@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/write/-/write-1.0.3.tgz#0800e14523b923a387e415123c865616aae0f5c3" + integrity sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig== + dependencies: + mkdirp "^0.5.1" + +ws@^5.2.0: + version "5.2.2" + resolved "https://registry.yarnpkg.com/ws/-/ws-5.2.2.tgz#dffef14866b8e8dc9133582514d1befaf96e980f" + integrity sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA== + dependencies: + async-limiter "~1.0.0" + +ws@^6.1.2, ws@^6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb" + integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA== + dependencies: + async-limiter "~1.0.0" + +ws@~6.1.0: + version "6.1.3" + resolved "https://registry.yarnpkg.com/ws/-/ws-6.1.3.tgz#d2d2e5f0e3c700ef2de89080ebc0ac6e1bf3a72d" + dependencies: + async-limiter "~1.0.0" + +xml-name-validator@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" + integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== + +xmlchars@^2.1.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" + integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== + +xmlhttprequest-ssl@~1.5.4: + version "1.5.5" + resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz#c2876b06168aadc40e57d97e81191ac8f4398b3e" + +xregexp@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-4.3.0.tgz#7e92e73d9174a99a59743f67a4ce879a04b5ae50" + integrity sha512-7jXDIFXh5yJ/orPn4SXjuVrWWoi4Cr8jfV1eHv9CixKSbU+jY4mxfrBwAuDvupPNKpMUY+FeIqsVw/JLT9+B8g== + dependencies: + "@babel/runtime-corejs3" "^7.8.3" + +xtend@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" + +xtend@~4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" + integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== + +y18n@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41" + +"y18n@^3.2.1 || ^4.0.0", y18n@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" + integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w== + +yallist@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" + +yallist@^3.0.0, yallist@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.3.tgz#b4b049e314be545e3ce802236d6cd22cd91c3de9" + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + +yaml@^1.7.2: + version "1.10.0" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.0.tgz#3b593add944876077d4d683fee01081bd9fff31e" + integrity sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg== + +yargs-parser@^11.1.1: + version "11.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-11.1.1.tgz#879a0865973bca9f6bab5cbdf3b1c67ec7d3bcf4" + integrity sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ== + dependencies: + camelcase "^5.0.0" + decamelize "^1.2.0" + +yargs-parser@^13.1.2: + version "13.1.2" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38" + integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg== + dependencies: + camelcase "^5.0.0" + decamelize "^1.2.0" + +yargs-parser@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-5.0.0.tgz#275ecf0d7ffe05c77e64e7c86e4cd94bf0e1228a" + dependencies: + camelcase "^3.0.0" + +yargs@12.0.5: + version "12.0.5" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-12.0.5.tgz#05f5997b609647b64f66b81e3b4b10a368e7ad13" + integrity sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw== + dependencies: + cliui "^4.0.0" + decamelize "^1.2.0" + find-up "^3.0.0" + get-caller-file "^1.0.1" + os-locale "^3.0.0" + require-directory "^2.1.1" + require-main-filename "^1.0.1" + set-blocking "^2.0.0" + string-width "^2.0.0" + which-module "^2.0.0" + y18n "^3.2.1 || ^4.0.0" + yargs-parser "^11.1.1" + +yargs@^13.3.0: + version "13.3.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd" + integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw== + dependencies: + cliui "^5.0.0" + find-up "^3.0.0" + get-caller-file "^2.0.1" + require-directory "^2.1.1" + require-main-filename "^2.0.0" + set-blocking "^2.0.0" + string-width "^3.0.0" + which-module "^2.0.0" + y18n "^4.0.0" + yargs-parser "^13.1.2" + +yargs@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-7.1.0.tgz#6ba318eb16961727f5d284f8ea003e8d6154d0c8" + dependencies: + camelcase "^3.0.0" + cliui "^3.2.0" + decamelize "^1.1.1" + get-caller-file "^1.0.1" + os-locale "^1.4.0" + read-pkg-up "^1.0.1" + require-directory "^2.1.1" + require-main-filename "^1.0.1" + set-blocking "^2.0.0" + string-width "^1.0.2" + which-module "^1.0.0" + y18n "^3.2.1" + yargs-parser "^5.0.0" + +yeast@0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419" |
