diff options
Diffstat (limited to 'opendc-web')
510 files changed, 16207 insertions, 20404 deletions
diff --git a/opendc-web/opendc-web-api/.coveragerc b/opendc-web/opendc-web-api/.coveragerc new file mode 100644 index 00000000..55d99c2e --- /dev/null +++ b/opendc-web/opendc-web-api/.coveragerc @@ -0,0 +1,5 @@ +[run] +source = . +omit = + tests/* + conftest.py diff --git a/opendc-web/opendc-web-api/.gitignore b/opendc-web/opendc-web-api/.gitignore index b0390689..9f8dfc5c 100644 --- a/opendc-web/opendc-web-api/.gitignore +++ b/opendc-web/opendc-web-api/.gitignore @@ -15,4 +15,5 @@ config.json test.json .env* .coverage -.junit-report.xml +coverage.xml +junit-report.xml diff --git a/opendc-web/opendc-web-api/.pylintrc b/opendc-web/opendc-web-api/.pylintrc index 7fe24187..4dbb0b50 100644 --- a/opendc-web/opendc-web-api/.pylintrc +++ b/opendc-web/opendc-web-api/.pylintrc @@ -65,7 +65,8 @@ disable=duplicate-code, invalid-name, bare-except, too-few-public-methods, - fixme + fixme, + no-self-use # 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 diff --git a/opendc-web/opendc-web-api/Dockerfile b/opendc-web/opendc-web-api/Dockerfile index 49702c90..505a69de 100644 --- a/opendc-web/opendc-web-api/Dockerfile +++ b/opendc-web/opendc-web-api/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.8 +FROM python:3.9-slim MAINTAINER OpenDC Maintainers <opendc@atlarge-research.com> # Ensure the STDOUT is not buffered by Python so that our logs become visible @@ -9,9 +9,15 @@ ENV PYTHONUNBUFFERED 1 COPY ./ /opendc # Fetch web server dependencies -RUN pip install -r /opendc/requirements.txt +RUN pip install -r /opendc/requirements.txt && pip install pyuwsgi + +# Create opendc user +RUN groupadd --gid 1000 opendc \ + && useradd --uid 1000 --gid opendc --shell /bin/bash --create-home opendc +RUN chown -R opendc:opendc /opendc +USER opendc # Set working directory WORKDIR /opendc -CMD ["python3", "main.py"] +CMD uwsgi -M --socket 0.0.0.0:80 --protocol=http --wsgi-file app.py --enable-threads --processes 2 --lazy-app diff --git a/opendc-web/opendc-web-api/README.md b/opendc-web/opendc-web-api/README.md index 4932f823..d1c469c1 100644 --- a/opendc-web/opendc-web-api/README.md +++ b/opendc-web/opendc-web-api/README.md @@ -9,15 +9,19 @@ <br> -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](../../opendc-api-spec.yml). +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](../../opendc-api-spec.yml). -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)). +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 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. @@ -25,74 +29,91 @@ The OpenDC API is implemented by the `Main Server Loop`, which is the only compo The `Util` package handles several miscellaneous tasks: -* `Database API`: Wraps database access functionality used by `Models` to read themselves from/write themselves into the database. +* `Database API`: Wraps database access functionality used by `Models` to read themselves from/write themselves into the + database. * `Exceptions`: Holds definitions for exceptions used throughout the web server. * `Parameter Checker`: Recursively checks whether required `Request` parameters are present and correctly typed. -* `REST`: Parses SocketIO and HTTP messages into `Request` objects, and calls the appropriate `API` endpoint to get a `Response` object to return to the `Main Server Loop`. +* `REST`: Parses HTTP messages into `Request` objects, and calls the appropriate `API` endpoint to get a `Response` + object to return to the `Main Server Loop`. ### 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 api/projects`, for example, would be located at the `endpoint.py` inside the `projects` package (so at `api/projects/endpoint.py`). +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 api/projects`, for example, would be located at the `endpoint.py` inside +the `projects` package (so at `api/projects/endpoint.py`). -An `endpoint.py` file 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 `Parameter 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. +An `endpoint.py` file 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 `Parameter 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. +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 generic CRUD operations. Extensions of `model`, such as a `User` or `Project`, specify some more specific operations and their collection metadata. +The `models` package contains the logic for mapping Python objects to their database representations. This involves an +abstract `model` which has generic CRUD operations. Extensions of `model`, such as a `User` or `Project`, specify some +more specific operations and their collection metadata. `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`. +The following steps will guide you through setting up the OpenDC web server locally for development. ### Local Setup #### Install requirements -Make sure you have Python 3.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. +Make sure you have Python 3.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 pip install -r requirements.txt ``` -The web server also requires a running MongoDB instance. We recommend setting this up through docker, by running `docker-compose build` and `docker-compose up` in the [`mongodb` directory](../database) of the main OpenDC repository. +The web server also requires a running MongoDB instance. We recommend setting this up through docker, by +running `docker-compose build` and `docker-compose up` in the [`mongodb` directory](../../database) of the main OpenDC +repository. #### Get and configure the code -Clone OpenDC and follow the [instructions in the main repository](../) to set up a Google OAuth ID and environment variables. +Clone OpenDC and follow the [instructions from the deployment guide](../../docs/deploy.md) to set up an [Auth0](https://auth0.com) +application and environment variables. **Important:** Be sure to set up environment variables according to those instructions, in a `.env` file. -If you want to test REST calls manually, add your own `OAUTH_CLIENT_ID` in `content=` on line `2` in `api/static/index.html`. - #### Set up the database -You can selectively run only the database services from the standard OpenDC `docker-compose` setup (in the root directory): +You can selectively run only the database services from the standard OpenDC `docker-compose` setup (in the root +directory): ```bash docker-compose build mongo mongo-express docker-compose up mongo mongo-express ``` -This will set you up with a running MongoDB instance and a visual inspection tool running on [localhost:8082](http://localhost:8082), with which you can view and manipulate the database. Add the simulator images to the command lists above if you want to test simulation capabilities, as well. +This will set you up with a running MongoDB instance and a visual inspection tool running +on [localhost:8082](http://localhost:8082), with which you can view and manipulate the database. Add the simulator +images to the command lists above if you want to test simulation capabilities, as well. ### Local Development Run the server. ```bash -cd api -python main.py +python3 -m flask run --port 8081 ``` -When editing the web server code, restart the server (`CTRL` + `c` followed by `python main.py` in the console running the server) to see the result of your changes. +When editing the web server code, restart the server (`CTRL` + `c` followed by `python app.py` 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. The script uses `yapf` internally to format everything automatically. +To format all files, run `format.sh` in this directory. The script uses `yapf` internally to format everything +automatically. To check if code style is up to modern standards, run `check.sh` in this directory. The script uses `pylint` internally. diff --git a/opendc-web/opendc-web-api/app.py b/opendc-web/opendc-web-api/app.py new file mode 100755 index 00000000..36c80b7a --- /dev/null +++ b/opendc-web/opendc-web-api/app.py @@ -0,0 +1,137 @@ +#!/usr/bin/env python3 +import mimetypes +import os + +from dotenv import load_dotenv +from flask import Flask, jsonify, redirect +from flask_compress import Compress +from flask_cors import CORS +from flask_restful import Api +from flask_swagger_ui import get_swaggerui_blueprint +from marshmallow import ValidationError + +from opendc.api.jobs import JobList, Job +from opendc.api.portfolios import Portfolio, PortfolioScenarios +from opendc.api.prefabs import Prefab, PrefabList +from opendc.api.projects import ProjectList, Project, ProjectTopologies, ProjectPortfolios +from opendc.api.scenarios import Scenario +from opendc.api.schedulers import SchedulerList +from opendc.api.topologies import Topology +from opendc.api.traces import TraceList, Trace +from opendc.auth import AuthError +from opendc.util import JSONEncoder + + +# Load environmental variables from dotenv file +load_dotenv() + + +def setup_sentry(): + """ + Setup the Sentry integration for Flask if a DSN is supplied via the environmental variables. + """ + if 'SENTRY_DSN' not in os.environ: + return + + import sentry_sdk + from sentry_sdk.integrations.flask import FlaskIntegration + + sentry_sdk.init( + integrations=[FlaskIntegration()], + traces_sample_rate=0.1 + ) + + +def setup_api(app): + """ + Setup the API interface. + """ + api = Api(app) + # Map to ('string', 'ObjectId') passing type and format + api.add_resource(ProjectList, '/projects/') + api.add_resource(Project, '/projects/<string:project_id>') + api.add_resource(ProjectTopologies, '/projects/<string:project_id>/topologies') + api.add_resource(ProjectPortfolios, '/projects/<string:project_id>/portfolios') + api.add_resource(Topology, '/topologies/<string:topology_id>') + api.add_resource(PrefabList, '/prefabs/') + api.add_resource(Prefab, '/prefabs/<string:prefab_id>') + api.add_resource(Portfolio, '/portfolios/<string:portfolio_id>') + api.add_resource(PortfolioScenarios, '/portfolios/<string:portfolio_id>/scenarios') + api.add_resource(Scenario, '/scenarios/<string:scenario_id>') + api.add_resource(TraceList, '/traces/') + api.add_resource(Trace, '/traces/<string:trace_id>') + api.add_resource(SchedulerList, '/schedulers/') + api.add_resource(JobList, '/jobs/') + api.add_resource(Job, '/jobs/<string:job_id>') + + @app.errorhandler(AuthError) + def handle_auth_error(ex): + response = jsonify(ex.error) + response.status_code = ex.status_code + return response + + @app.errorhandler(ValidationError) + def handle_validation_error(ex): + return {'message': 'Input validation failed', 'errors': ex.messages}, 400 + + return api + + +def setup_swagger(app): + """ + Setup Swagger UI + """ + SWAGGER_URL = '/docs' + API_URL = '../schema.yml' + + swaggerui_blueprint = get_swaggerui_blueprint( + SWAGGER_URL, + API_URL, + config={ + 'app_name': "OpenDC API v2" + }, + oauth_config={ + 'clientId': os.environ.get("AUTH0_DOCS_CLIENT_ID", ""), + 'additionalQueryStringParams': {'audience': os.environ.get("AUTH0_AUDIENCE", "https://api.opendc.org/v2/")}, + } + ) + app.register_blueprint(swaggerui_blueprint) + + +def create_app(testing=False): + app = Flask(__name__, static_url_path='/') + app.config['TESTING'] = testing + app.config['SECRET_KEY'] = os.environ['OPENDC_FLASK_SECRET'] + app.config['RESTFUL_JSON'] = {'cls': JSONEncoder} + app.json_encoder = JSONEncoder + + # Define YAML content type + mimetypes.add_type('text/yaml', '.yml') + + # Setup Sentry if DSN is specified + setup_sentry() + + # Set up CORS support + CORS(app) + + # Setup compression + compress = Compress() + compress.init_app(app) + + setup_api(app) + setup_swagger(app) + + @app.route('/') + def index(): + """ + Redirect the user to the API documentation if it accesses the API root. + """ + return redirect('docs/') + + return app + + +application = create_app(testing="OPENDC_FLASK_TESTING" in os.environ) + +if __name__ == '__main__': + application.run() diff --git a/opendc-web/opendc-web-api/conftest.py b/opendc-web/opendc-web-api/conftest.py index 1f4831b8..958a5894 100644 --- a/opendc-web/opendc-web-api/conftest.py +++ b/opendc-web/opendc-web-api/conftest.py @@ -1,15 +1,45 @@ """ Configuration file for all unit tests. """ + +from functools import wraps import pytest +from flask import _request_ctx_stack, g +from opendc.database import Database + + +def requires_auth_mock(f): + @wraps(f) + def decorated_function(*args, **kwargs): + _request_ctx_stack.top.current_user = {'sub': 'test'} + return f(*args, **kwargs) + return decorated_function + -from main import FLASK_CORE_APP +def requires_scope_mock(required_scope): + def decorator(f): + @wraps(f) + def decorated_function(*args, **kwargs): + return f(*args, **kwargs) + return decorated_function + return decorator @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 + # Disable authorization for test API endpoints + from opendc import exts + exts.requires_auth = requires_auth_mock + exts.requires_scope = requires_scope_mock + exts.has_scope = lambda x: False + + from app import create_app + + app = create_app(testing=True) + + with app.app_context(): + g.db = Database() + with app.test_client() as client: + yield client diff --git a/opendc-web/opendc-web-api/misc/artwork/opendc-web-server-component-diagram.png b/opendc-web/opendc-web-api/docs/component-diagram.png Binary files differindex 91b26006..91b26006 100644 --- a/opendc-web/opendc-web-api/misc/artwork/opendc-web-server-component-diagram.png +++ b/opendc-web/opendc-web-api/docs/component-diagram.png diff --git a/opendc-web/opendc-web-api/main.py b/opendc-web/opendc-web-api/main.py deleted file mode 100755 index 5c6dac31..00000000 --- a/opendc-web/opendc-web-api/main.py +++ /dev/null @@ -1,188 +0,0 @@ -#!/usr/bin/env python3 -import json -import os -import sys -import traceback -import urllib.request - -import flask_socketio -from dotenv import load_dotenv -from flask import Flask, request, jsonify -from flask_compress import Compress -from flask_cors import CORS -from oauth2client import client, crypt - -from opendc.models.user import User -from opendc.util import rest, path_parser, database -from opendc.util.exceptions import AuthorizationTokenError, RequestInitializationError -from opendc.util.json import JSONEncoder - -load_dotenv() - -TEST_MODE = "OPENDC_FLASK_TESTING" in os.environ - -# Setup Sentry if DSN is specified -if 'SENTRY_DSN' in os.environ: - import sentry_sdk - from sentry_sdk.integrations.flask import FlaskIntegration - - sentry_sdk.init( - integrations=[FlaskIntegration()], - traces_sample_rate=0.1 - ) - -# Set up database if not testing -if not TEST_MODE: - database.DB.initialize_database( - user=os.environ['OPENDC_DB_USERNAME'], - password=os.environ['OPENDC_DB_PASSWORD'], - database=os.environ['OPENDC_DB'], - host=os.environ.get('OPENDC_DB_HOST', 'localhost')) - -# Set up the core app -FLASK_CORE_APP = Flask(__name__) -FLASK_CORE_APP.testing = TEST_MODE -FLASK_CORE_APP.config['SECRET_KEY'] = os.environ['OPENDC_FLASK_SECRET'] -FLASK_CORE_APP.json_encoder = JSONEncoder - -# Set up CORS support -CORS(FLASK_CORE_APP) - -compress = Compress() -compress.init_app(FLASK_CORE_APP) - -SOCKET_IO_CORE = flask_socketio.SocketIO(FLASK_CORE_APP, cors_allowed_origins="*") - -API_VERSIONS = {'v2'} - - -@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': user.obj is None} - - if user.obj is not None: - data['userId'] = user.get_id() - - return jsonify(**data) - - -@FLASK_CORE_APP.route('/<string:version>/<path:endpoint_path>', methods=['GET', 'POST', 'PUT', 'DELETE']) -def api_call(version, endpoint_path): - """Call an API endpoint directly over HTTP.""" - - # Check whether given version is valid - if version not in API_VERSIONS: - return jsonify(error='API version not found'), 404 - - # 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 - - -@SOCKET_IO_CORE.on('request') -def receive_message(message): - """"Receive a SocketIO request""" - (req, res) = _process_message(message) - - print(f'Socket: {req.method} to `/{req.path}` resulted in {res.status["code"]}: {res.status["description"]}') - sys.stdout.flush() - - flask_socketio.emit('response', 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__': - print("Web server started on 8081") - SOCKET_IO_CORE.run(FLASK_CORE_APP, host='0.0.0.0', port=8081, use_reloader=False) diff --git a/opendc-web/opendc-web-api/opendc/api/jobs.py b/opendc-web/opendc-web-api/opendc/api/jobs.py new file mode 100644 index 00000000..6fb0522b --- /dev/null +++ b/opendc-web/opendc-web-api/opendc/api/jobs.py @@ -0,0 +1,105 @@ +# Copyright (c) 2021 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. +from flask import request +from flask_restful import Resource +from marshmallow import fields, Schema, validate +from werkzeug.exceptions import BadRequest, Conflict + +from opendc.exts import requires_auth, requires_scope +from opendc.models.scenario import Scenario + + +def convert_to_job(scenario): + """Convert a scenario to a job. + """ + return JobSchema().dump({ + '_id': scenario['_id'], + 'scenarioId': scenario['_id'], + 'state': scenario['simulation']['state'], + 'heartbeat': scenario['simulation'].get('heartbeat', None), + 'results': scenario.get('results', {}) + }) + + +class JobSchema(Schema): + """ + Schema representing a simulation job. + """ + _id = fields.String(dump_only=True) + scenarioId = fields.String(dump_only=True) + state = fields.String(required=True, + validate=validate.OneOf(["QUEUED", "CLAIMED", "RUNNING", "FINISHED", "FAILED"])) + heartbeat = fields.DateTime() + results = fields.Dict() + + +class JobList(Resource): + """ + Resource representing the list of available jobs. + """ + method_decorators = [requires_auth, requires_scope('runner')] + + def get(self): + """Get all available jobs.""" + jobs = Scenario.get_jobs() + data = list(map(convert_to_job, jobs.obj)) + return {'data': data} + + +class Job(Resource): + """ + Resource representing a single job. + """ + method_decorators = [requires_auth, requires_scope('runner')] + + def get(self, job_id): + """Get the details of a single job.""" + job = Scenario.from_id(job_id) + job.check_exists() + data = convert_to_job(job.obj) + return {'data': data} + + def post(self, job_id): + """Update the details of a single job.""" + action = JobSchema(only=('state', 'results')).load(request.json) + + job = Scenario.from_id(job_id) + job.check_exists() + + old_state = job.obj['simulation']['state'] + new_state = action['state'] + + if old_state == new_state: + data = job.update_state(new_state) + elif (old_state, new_state) == ('QUEUED', 'CLAIMED'): + data = job.update_state('CLAIMED') + elif (old_state, new_state) == ('CLAIMED', 'RUNNING'): + data = job.update_state('RUNNING') + elif (old_state, new_state) == ('RUNNING', 'FINISHED'): + data = job.update_state('FINISHED', results=action.get('results', None)) + elif old_state in ('CLAIMED', 'RUNNING') and new_state == 'FAILED': + data = job.update_state('FAILED') + else: + raise BadRequest('Invalid state transition') + + if not data: + raise Conflict('State conflict') + + return {'data': convert_to_job(data)} diff --git a/opendc-web/opendc-web-api/opendc/api/portfolios.py b/opendc-web/opendc-web-api/opendc/api/portfolios.py new file mode 100644 index 00000000..4d8f54fd --- /dev/null +++ b/opendc-web/opendc-web-api/opendc/api/portfolios.py @@ -0,0 +1,153 @@ +# Copyright (c) 2021 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. + +from flask import request +from flask_restful import Resource +from marshmallow import Schema, fields + +from opendc.exts import requires_auth, current_user, has_scope +from opendc.models.portfolio import Portfolio as PortfolioModel, PortfolioSchema +from opendc.models.project import Project +from opendc.models.scenario import ScenarioSchema, Scenario +from opendc.models.topology import Topology + + +class Portfolio(Resource): + """ + Resource representing a portfolio. + """ + method_decorators = [requires_auth] + + def get(self, portfolio_id): + """ + Get a portfolio by identifier. + """ + portfolio = PortfolioModel.from_id(portfolio_id) + + portfolio.check_exists() + + # Users with scope runner can access all portfolios + if not has_scope('runner'): + portfolio.check_user_access(current_user['sub'], False) + + data = PortfolioSchema().dump(portfolio.obj) + return {'data': data} + + def put(self, portfolio_id): + """ + Replace the portfolio. + """ + schema = Portfolio.PutSchema() + result = schema.load(request.json) + + portfolio = PortfolioModel.from_id(portfolio_id) + portfolio.check_exists() + portfolio.check_user_access(current_user['sub'], True) + + portfolio.set_property('name', result['portfolio']['name']) + portfolio.set_property('targets.enabledMetrics', result['portfolio']['targets']['enabledMetrics']) + portfolio.set_property('targets.repeatsPerScenario', result['portfolio']['targets']['repeatsPerScenario']) + + portfolio.update() + data = PortfolioSchema().dump(portfolio.obj) + return {'data': data} + + def delete(self, portfolio_id): + """ + Delete a portfolio. + """ + portfolio = PortfolioModel.from_id(portfolio_id) + + portfolio.check_exists() + portfolio.check_user_access(current_user['sub'], True) + + portfolio_id = portfolio.get_id() + + project = Project.from_id(portfolio.obj['projectId']) + project.check_exists() + if portfolio_id in project.obj['portfolioIds']: + project.obj['portfolioIds'].remove(portfolio_id) + project.update() + + old_object = portfolio.delete() + data = PortfolioSchema().dump(old_object) + return {'data': data} + + class PutSchema(Schema): + """ + Schema for the PUT operation on a portfolio. + """ + portfolio = fields.Nested(PortfolioSchema, required=True) + + +class PortfolioScenarios(Resource): + """ + Resource representing the scenarios of a portfolio. + """ + method_decorators = [requires_auth] + + def get(self, portfolio_id): + """ + Get all scenarios belonging to a portfolio. + """ + portfolio = PortfolioModel.from_id(portfolio_id) + + portfolio.check_exists() + portfolio.check_user_access(current_user['sub'], True) + + scenarios = Scenario.get_for_portfolio(portfolio_id) + + data = ScenarioSchema().dump(scenarios, many=True) + return {'data': data} + + def post(self, portfolio_id): + """ + Add a new scenario to this portfolio + """ + schema = PortfolioScenarios.PostSchema() + result = schema.load(request.json) + + portfolio = PortfolioModel.from_id(portfolio_id) + + portfolio.check_exists() + portfolio.check_user_access(current_user['sub'], True) + + scenario = Scenario(result['scenario']) + + topology = Topology.from_id(scenario.obj['topology']['topologyId']) + topology.check_exists() + topology.check_user_access(current_user['sub'], True) + + scenario.set_property('portfolioId', portfolio.get_id()) + scenario.set_property('simulation', {'state': 'QUEUED'}) + scenario.set_property('topology.topologyId', topology.get_id()) + + scenario.insert() + + portfolio.obj['scenarioIds'].append(scenario.get_id()) + portfolio.update() + data = ScenarioSchema().dump(scenario.obj) + return {'data': data} + + class PostSchema(Schema): + """ + Schema for the POST operation on a portfolio's scenarios. + """ + scenario = fields.Nested(ScenarioSchema, required=True) diff --git a/opendc-web/opendc-web-api/opendc/api/prefabs.py b/opendc-web/opendc-web-api/opendc/api/prefabs.py new file mode 100644 index 00000000..730546ba --- /dev/null +++ b/opendc-web/opendc-web-api/opendc/api/prefabs.py @@ -0,0 +1,123 @@ +# Copyright (c) 2021 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. + +from datetime import datetime +from flask import request +from flask_restful import Resource +from marshmallow import Schema, fields + +from opendc.models.prefab import Prefab as PrefabModel, PrefabSchema +from opendc.exts import current_user, requires_auth, db + + +class PrefabList(Resource): + """ + Resource for the list of prefabs available to the user. + """ + method_decorators = [requires_auth] + + def get(self): + """ + Get the available prefabs for a user. + """ + user_id = current_user['sub'] + + own_prefabs = db.fetch_all({'authorId': user_id}, PrefabModel.collection_name) + public_prefabs = db.fetch_all({'visibility': 'public'}, PrefabModel.collection_name) + + authorizations = {"authorizations": []} + authorizations["authorizations"].append(own_prefabs) + authorizations["authorizations"].append(public_prefabs) + return {'data': authorizations} + + def post(self): + """ + Create a new prefab. + """ + schema = PrefabList.PostSchema() + result = schema.load(request.json) + + prefab = PrefabModel(result['prefab']) + prefab.set_property('datetimeCreated', datetime.now()) + prefab.set_property('datetimeLastEdited', datetime.now()) + + user_id = current_user['sub'] + prefab.set_property('authorId', user_id) + + prefab.insert() + data = PrefabSchema().dump(prefab.obj) + return {'data': data} + + class PostSchema(Schema): + """ + Schema for the POST operation on the prefab list. + """ + prefab = fields.Nested(PrefabSchema, required=True) + + +class Prefab(Resource): + """ + Resource representing a single prefab. + """ + method_decorators = [requires_auth] + + def get(self, prefab_id): + """Get this Prefab.""" + prefab = PrefabModel.from_id(prefab_id) + prefab.check_exists() + prefab.check_user_access(current_user['sub']) + data = PrefabSchema().dump(prefab.obj) + return {'data': data} + + def put(self, prefab_id): + """Update a prefab's name and/or contents.""" + + schema = Prefab.PutSchema() + result = schema.load(request.json) + + prefab = PrefabModel.from_id(prefab_id) + prefab.check_exists() + prefab.check_user_access(current_user['sub']) + + prefab.set_property('name', result['prefab']['name']) + prefab.set_property('rack', result['prefab']['rack']) + prefab.set_property('datetimeLastEdited', datetime.now()) + prefab.update() + + data = PrefabSchema().dump(prefab.obj) + return {'data': data} + + def delete(self, prefab_id): + """Delete this Prefab.""" + prefab = PrefabModel.from_id(prefab_id) + + prefab.check_exists() + prefab.check_user_access(current_user['sub']) + + old_object = prefab.delete() + + data = PrefabSchema().dump(old_object) + return {'data': data} + + class PutSchema(Schema): + """ + Schema for the PUT operation on a prefab. + """ + prefab = fields.Nested(PrefabSchema, required=True) diff --git a/opendc-web/opendc-web-api/opendc/api/projects.py b/opendc-web/opendc-web-api/opendc/api/projects.py new file mode 100644 index 00000000..2b47c12e --- /dev/null +++ b/opendc-web/opendc-web-api/opendc/api/projects.py @@ -0,0 +1,224 @@ +# Copyright (c) 2021 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. + +from datetime import datetime +from flask import request +from flask_restful import Resource +from marshmallow import Schema, fields + +from opendc.models.portfolio import Portfolio, PortfolioSchema +from opendc.models.topology import Topology, TopologySchema +from opendc.models.project import Project as ProjectModel, ProjectSchema +from opendc.exts import current_user, requires_auth + + +class ProjectList(Resource): + """ + Resource representing the list of projects available to a user. + """ + method_decorators = [requires_auth] + + def get(self): + """Get the authorized projects of the user""" + user_id = current_user['sub'] + projects = ProjectModel.get_for_user(user_id) + data = ProjectSchema().dump(projects, many=True) + return {'data': data} + + def post(self): + """Create a new project, and return that new project.""" + user_id = current_user['sub'] + + schema = Project.PutSchema() + result = schema.load(request.json) + + topology = Topology({'name': 'Default topology', 'rooms': []}) + topology.insert() + + project = ProjectModel(result['project']) + project.set_property('datetimeCreated', datetime.now()) + project.set_property('datetimeLastEdited', datetime.now()) + project.set_property('topologyIds', [topology.get_id()]) + project.set_property('portfolioIds', []) + project.set_property('authorizations', [{'userId': user_id, 'level': 'OWN'}]) + project.insert() + + topology.set_property('projectId', project.get_id()) + topology.update() + + data = ProjectSchema().dump(project.obj) + return {'data': data} + + +class Project(Resource): + """ + Resource representing a single project. + """ + method_decorators = [requires_auth] + + def get(self, project_id): + """Get this Project.""" + project = ProjectModel.from_id(project_id) + + project.check_exists() + project.check_user_access(current_user['sub'], False) + + data = ProjectSchema().dump(project.obj) + return {'data': data} + + def put(self, project_id): + """Update a project's name.""" + schema = Project.PutSchema() + result = schema.load(request.json) + + project = ProjectModel.from_id(project_id) + + project.check_exists() + project.check_user_access(current_user['sub'], True) + + project.set_property('name', result['project']['name']) + project.set_property('datetimeLastEdited', datetime.now()) + project.update() + + data = ProjectSchema().dump(project.obj) + return {'data': data} + + def delete(self, project_id): + """Delete this Project.""" + project = ProjectModel.from_id(project_id) + + project.check_exists() + project.check_user_access(current_user['sub'], True) + + for topology_id in project.obj['topologyIds']: + topology = Topology.from_id(topology_id) + topology.delete() + + for portfolio_id in project.obj['portfolioIds']: + portfolio = Portfolio.from_id(portfolio_id) + portfolio.delete() + + old_object = project.delete() + data = ProjectSchema().dump(old_object) + return {'data': data} + + class PutSchema(Schema): + """ + Schema for the PUT operation on a project. + """ + project = fields.Nested(ProjectSchema, required=True) + + +class ProjectTopologies(Resource): + """ + Resource representing the topologies of a project. + """ + method_decorators = [requires_auth] + + def get(self, project_id): + """Get all topologies belonging to the project.""" + project = ProjectModel.from_id(project_id) + + project.check_exists() + project.check_user_access(current_user['sub'], True) + + topologies = Topology.get_for_project(project_id) + data = TopologySchema().dump(topologies, many=True) + + return {'data': data} + + def post(self, project_id): + """Add a new Topology to the specified project and return it""" + schema = ProjectTopologies.PutSchema() + result = schema.load(request.json) + + project = ProjectModel.from_id(project_id) + + project.check_exists() + project.check_user_access(current_user['sub'], True) + + topology = Topology({ + 'projectId': project.get_id(), + 'name': result['topology']['name'], + 'rooms': result['topology']['rooms'], + }) + + topology.insert() + + project.obj['topologyIds'].append(topology.get_id()) + project.set_property('datetimeLastEdited', datetime.now()) + project.update() + + data = TopologySchema().dump(topology.obj) + return {'data': data} + + class PutSchema(Schema): + """ + Schema for the PUT operation on a project topology. + """ + topology = fields.Nested(TopologySchema, required=True) + + +class ProjectPortfolios(Resource): + """ + Resource representing the portfolios of a project. + """ + method_decorators = [requires_auth] + + def get(self, project_id): + """Get all portfolios belonging to the project.""" + project = ProjectModel.from_id(project_id) + + project.check_exists() + project.check_user_access(current_user['sub'], True) + + portfolios = Portfolio.get_for_project(project_id) + data = PortfolioSchema().dump(portfolios, many=True) + + return {'data': data} + + def post(self, project_id): + """Add a new Portfolio for this Project.""" + schema = ProjectPortfolios.PutSchema() + result = schema.load(request.json) + + project = ProjectModel.from_id(project_id) + + project.check_exists() + project.check_user_access(current_user['sub'], True) + + portfolio = Portfolio(result['portfolio']) + + portfolio.set_property('projectId', project.get_id()) + portfolio.set_property('scenarioIds', []) + + portfolio.insert() + + project.obj['portfolioIds'].append(portfolio.get_id()) + project.update() + + data = PortfolioSchema().dump(portfolio.obj) + return {'data': data} + + class PutSchema(Schema): + """ + Schema for the PUT operation on a project portfolio. + """ + portfolio = fields.Nested(PortfolioSchema, required=True) diff --git a/opendc-web/opendc-web-api/opendc/api/scenarios.py b/opendc-web/opendc-web-api/opendc/api/scenarios.py new file mode 100644 index 00000000..eacb0b49 --- /dev/null +++ b/opendc-web/opendc-web-api/opendc/api/scenarios.py @@ -0,0 +1,86 @@ +# Copyright (c) 2021 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. + +from flask import request +from flask_restful import Resource +from marshmallow import Schema, fields + +from opendc.models.scenario import Scenario as ScenarioModel, ScenarioSchema +from opendc.models.portfolio import Portfolio +from opendc.exts import current_user, requires_auth, has_scope + + +class Scenario(Resource): + """ + A Scenario resource. + """ + method_decorators = [requires_auth] + + def get(self, scenario_id): + """Get scenario by identifier.""" + scenario = ScenarioModel.from_id(scenario_id) + scenario.check_exists() + + # Users with scope runner can access all scenarios + if not has_scope('runner'): + scenario.check_user_access(current_user['sub'], False) + + data = ScenarioSchema().dump(scenario.obj) + return {'data': data} + + def put(self, scenario_id): + """Update this Scenarios name.""" + schema = Scenario.PutSchema() + result = schema.load(request.json) + + scenario = ScenarioModel.from_id(scenario_id) + + scenario.check_exists() + scenario.check_user_access(current_user['sub'], True) + + scenario.set_property('name', result['scenario']['name']) + + scenario.update() + data = ScenarioSchema().dump(scenario.obj) + return {'data': data} + + def delete(self, scenario_id): + """Delete this Scenario.""" + scenario = ScenarioModel.from_id(scenario_id) + scenario.check_exists() + scenario.check_user_access(current_user['sub'], True) + + scenario_id = scenario.get_id() + + portfolio = Portfolio.from_id(scenario.obj['portfolioId']) + portfolio.check_exists() + if scenario_id in portfolio.obj['scenarioIds']: + portfolio.obj['scenarioIds'].remove(scenario_id) + portfolio.update() + + old_object = scenario.delete() + data = ScenarioSchema().dump(old_object) + return {'data': data} + + class PutSchema(Schema): + """ + Schema for the put operation. + """ + scenario = fields.Nested(ScenarioSchema, required=True) diff --git a/opendc-web/opendc-web-api/opendc/api/schedulers.py b/opendc-web/opendc-web-api/opendc/api/schedulers.py new file mode 100644 index 00000000..b00d8c31 --- /dev/null +++ b/opendc-web/opendc-web-api/opendc/api/schedulers.py @@ -0,0 +1,46 @@ +# Copyright (c) 2021 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. + + +from flask_restful import Resource +from opendc.exts import requires_auth + +SCHEDULERS = [ + 'mem', + 'mem-inv', + 'core-mem', + 'core-mem-inv', + 'active-servers', + 'active-servers-inv', + 'provisioned-cores', + 'provisioned-cores-inv', + 'random' +] + + +class SchedulerList(Resource): + """ + Resource for the list of schedulers to pick from. + """ + method_decorators = [requires_auth] + + def get(self): + """Get all available Traces.""" + return {'data': [{'name': name} for name in SCHEDULERS]} diff --git a/opendc-web/opendc-web-api/opendc/api/topologies.py b/opendc-web/opendc-web-api/opendc/api/topologies.py new file mode 100644 index 00000000..c0b2e7ee --- /dev/null +++ b/opendc-web/opendc-web-api/opendc/api/topologies.py @@ -0,0 +1,97 @@ +# Copyright (c) 2021 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. + +from datetime import datetime + +from flask import request +from flask_restful import Resource +from marshmallow import Schema, fields + +from opendc.models.project import Project +from opendc.models.topology import Topology as TopologyModel, TopologySchema +from opendc.exts import current_user, requires_auth, has_scope + + +class Topology(Resource): + """ + Resource representing a single topology. + """ + method_decorators = [requires_auth] + + def get(self, topology_id): + """ + Get a single topology. + """ + topology = TopologyModel.from_id(topology_id) + topology.check_exists() + + # Users with scope runner can access all topologies + if not has_scope('runner'): + topology.check_user_access(current_user['sub'], False) + + data = TopologySchema().dump(topology.obj) + return {'data': data} + + def put(self, topology_id): + """ + Replace the topology. + """ + topology = TopologyModel.from_id(topology_id) + + schema = Topology.PutSchema() + result = schema.load(request.json) + + topology.check_exists() + topology.check_user_access(current_user['sub'], True) + + topology.set_property('name', result['topology']['name']) + topology.set_property('rooms', result['topology']['rooms']) + topology.set_property('datetimeLastEdited', datetime.now()) + + topology.update() + data = TopologySchema().dump(topology.obj) + return {'data': data} + + def delete(self, topology_id): + """ + Delete a topology. + """ + topology = TopologyModel.from_id(topology_id) + + topology.check_exists() + topology.check_user_access(current_user['sub'], True) + + topology_id = topology.get_id() + + project = Project.from_id(topology.obj['projectId']) + project.check_exists() + if topology_id in project.obj['topologyIds']: + project.obj['topologyIds'].remove(topology_id) + project.update() + + old_object = topology.delete() + data = TopologySchema().dump(old_object) + return {'data': data} + + class PutSchema(Schema): + """ + Schema for the PUT operation on a topology. + """ + topology = fields.Nested(TopologySchema, required=True) diff --git a/opendc-web/opendc-web-api/opendc/api/traces.py b/opendc-web/opendc-web-api/opendc/api/traces.py new file mode 100644 index 00000000..6be8c5e5 --- /dev/null +++ b/opendc-web/opendc-web-api/opendc/api/traces.py @@ -0,0 +1,51 @@ +# Copyright (c) 2021 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. + +from flask_restful import Resource + +from opendc.exts import requires_auth +from opendc.models.trace import Trace as TraceModel, TraceSchema + + +class TraceList(Resource): + """ + Resource for the list of traces to pick from. + """ + method_decorators = [requires_auth] + + def get(self): + """Get all available Traces.""" + traces = TraceModel.get_all() + data = TraceSchema().dump(traces.obj, many=True) + return {'data': data} + + +class Trace(Resource): + """ + Resource representing a single trace. + """ + method_decorators = [requires_auth] + + def get(self, trace_id): + """Get trace information by identifier.""" + trace = TraceModel.from_id(trace_id) + trace.check_exists() + data = TraceSchema().dump(trace.obj) + return {'data': data} diff --git a/opendc-web/opendc-web-api/opendc/api/v2/__init__.py b/opendc-web/opendc-web-api/opendc/api/v2/__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/__init__.py +++ /dev/null diff --git a/opendc-web/opendc-web-api/opendc/api/v2/paths.json b/opendc-web/opendc-web-api/opendc/api/v2/paths.json deleted file mode 100644 index 652be5bc..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/paths.json +++ /dev/null @@ -1,19 +0,0 @@ -[ - "/users", - "/users/{userId}", - "/projects", - "/projects/{projectId}", - "/projects/{projectId}/authorizations", - "/projects/{projectId}/topologies", - "/projects/{projectId}/portfolios", - "/topologies/{topologyId}", - "/portfolios/{portfolioId}", - "/portfolios/{portfolioId}/scenarios", - "/scenarios/{scenarioId}", - "/schedulers", - "/traces", - "/traces/{traceId}", - "/prefabs", - "/prefabs/{prefabId}", - "/prefabs/authorizations" -] diff --git a/opendc-web/opendc-web-api/opendc/api/v2/portfolios/__init__.py b/opendc-web/opendc-web-api/opendc/api/v2/portfolios/__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/portfolios/__init__.py +++ /dev/null diff --git a/opendc-web/opendc-web-api/opendc/api/v2/portfolios/portfolioId/__init__.py b/opendc-web/opendc-web-api/opendc/api/v2/portfolios/portfolioId/__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/portfolios/portfolioId/__init__.py +++ /dev/null diff --git a/opendc-web/opendc-web-api/opendc/api/v2/portfolios/portfolioId/endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/portfolios/portfolioId/endpoint.py deleted file mode 100644 index 0ba61a13..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/portfolios/portfolioId/endpoint.py +++ /dev/null @@ -1,67 +0,0 @@ -from opendc.models.portfolio import Portfolio -from opendc.models.project import Project -from opendc.util.rest import Response - - -def GET(request): - """Get this Portfolio.""" - - request.check_required_parameters(path={'portfolioId': 'string'}) - - portfolio = Portfolio.from_id(request.params_path['portfolioId']) - - portfolio.check_exists() - portfolio.check_user_access(request.google_id, False) - - return Response(200, 'Successfully retrieved portfolio.', portfolio.obj) - - -def PUT(request): - """Update this Portfolio.""" - - request.check_required_parameters(path={'portfolioId': 'string'}, body={'portfolio': { - 'name': 'string', - 'targets': { - 'enabledMetrics': 'list', - 'repeatsPerScenario': 'int', - }, - }}) - - portfolio = Portfolio.from_id(request.params_path['portfolioId']) - - portfolio.check_exists() - portfolio.check_user_access(request.google_id, True) - - portfolio.set_property('name', - request.params_body['portfolio']['name']) - portfolio.set_property('targets.enabledMetrics', - request.params_body['portfolio']['targets']['enabledMetrics']) - portfolio.set_property('targets.repeatsPerScenario', - request.params_body['portfolio']['targets']['repeatsPerScenario']) - - portfolio.update() - - return Response(200, 'Successfully updated portfolio.', portfolio.obj) - - -def DELETE(request): - """Delete this Portfolio.""" - - request.check_required_parameters(path={'portfolioId': 'string'}) - - portfolio = Portfolio.from_id(request.params_path['portfolioId']) - - portfolio.check_exists() - portfolio.check_user_access(request.google_id, True) - - portfolio_id = portfolio.get_id() - - project = Project.from_id(portfolio.obj['projectId']) - project.check_exists() - if portfolio_id in project.obj['portfolioIds']: - project.obj['portfolioIds'].remove(portfolio_id) - project.update() - - old_object = portfolio.delete() - - return Response(200, 'Successfully deleted portfolio.', old_object) diff --git a/opendc-web/opendc-web-api/opendc/api/v2/portfolios/portfolioId/scenarios/__init__.py b/opendc-web/opendc-web-api/opendc/api/v2/portfolios/portfolioId/scenarios/__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/portfolios/portfolioId/scenarios/__init__.py +++ /dev/null diff --git a/opendc-web/opendc-web-api/opendc/api/v2/portfolios/portfolioId/scenarios/endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/portfolios/portfolioId/scenarios/endpoint.py deleted file mode 100644 index 2f042e06..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/portfolios/portfolioId/scenarios/endpoint.py +++ /dev/null @@ -1,49 +0,0 @@ -from opendc.models.portfolio import Portfolio -from opendc.models.scenario import Scenario -from opendc.models.topology import Topology -from opendc.util.rest import Response - - -def POST(request): - """Add a new Scenario for this Portfolio.""" - - request.check_required_parameters(path={'portfolioId': 'string'}, - body={ - 'scenario': { - 'name': 'string', - 'trace': { - 'traceId': 'string', - 'loadSamplingFraction': 'float', - }, - 'topology': { - 'topologyId': 'string', - }, - 'operational': { - 'failuresEnabled': 'bool', - 'performanceInterferenceEnabled': 'bool', - 'schedulerName': 'string', - }, - } - }) - - portfolio = Portfolio.from_id(request.params_path['portfolioId']) - - portfolio.check_exists() - portfolio.check_user_access(request.google_id, True) - - scenario = Scenario(request.params_body['scenario']) - - topology = Topology.from_id(scenario.obj['topology']['topologyId']) - topology.check_exists() - topology.check_user_access(request.google_id, True) - - scenario.set_property('portfolioId', portfolio.get_id()) - scenario.set_property('simulation', {'state': 'QUEUED'}) - scenario.set_property('topology.topologyId', topology.get_id()) - - scenario.insert() - - portfolio.obj['scenarioIds'].append(scenario.get_id()) - portfolio.update() - - return Response(200, 'Successfully added Scenario.', scenario.obj) diff --git a/opendc-web/opendc-web-api/opendc/api/v2/portfolios/portfolioId/scenarios/test_endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/portfolios/portfolioId/scenarios/test_endpoint.py deleted file mode 100644 index e5982b7f..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/portfolios/portfolioId/scenarios/test_endpoint.py +++ /dev/null @@ -1,125 +0,0 @@ -from opendc.util.database import DB - -test_id = 24 * '1' - - -def test_add_scenario_missing_parameter(client): - assert '400' in client.post('/v2/portfolios/1/scenarios').status - - -def test_add_scenario_non_existing_portfolio(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value=None) - assert '404' in client.post(f'/v2/portfolios/{test_id}/scenarios', - json={ - 'scenario': { - 'name': 'test', - 'trace': { - 'traceId': test_id, - 'loadSamplingFraction': 1.0, - }, - 'topology': { - 'topologyId': test_id, - }, - 'operational': { - 'failuresEnabled': True, - 'performanceInterferenceEnabled': False, - 'schedulerName': 'DEFAULT', - }, - } - }).status - - -def test_add_scenario_not_authorized(client, mocker): - mocker.patch.object(DB, - 'fetch_one', - return_value={ - '_id': test_id, - 'projectId': test_id, - 'portfolioId': test_id, - 'authorizations': [{ - 'projectId': test_id, - 'authorizationLevel': 'VIEW' - }] - }) - assert '403' in client.post(f'/v2/portfolios/{test_id}/scenarios', - json={ - 'scenario': { - 'name': 'test', - 'trace': { - 'traceId': test_id, - 'loadSamplingFraction': 1.0, - }, - 'topology': { - 'topologyId': test_id, - }, - 'operational': { - 'failuresEnabled': True, - 'performanceInterferenceEnabled': False, - 'schedulerName': 'DEFAULT', - }, - } - }).status - - -def test_add_scenario(client, mocker): - mocker.patch.object(DB, - 'fetch_one', - return_value={ - '_id': test_id, - 'projectId': test_id, - 'portfolioId': test_id, - 'portfolioIds': [test_id], - 'scenarioIds': [test_id], - 'authorizations': [{ - 'projectId': test_id, - 'authorizationLevel': 'EDIT' - }], - 'simulation': { - 'state': 'QUEUED', - }, - }) - mocker.patch.object(DB, - 'insert', - return_value={ - '_id': test_id, - 'name': 'test', - 'trace': { - 'traceId': test_id, - 'loadSamplingFraction': 1.0, - }, - 'topology': { - 'topologyId': test_id, - }, - 'operational': { - 'failuresEnabled': True, - 'performanceInterferenceEnabled': False, - 'schedulerName': 'DEFAULT', - }, - 'portfolioId': test_id, - 'simulationState': { - 'state': 'QUEUED', - }, - }) - mocker.patch.object(DB, 'update', return_value=None) - res = client.post( - f'/v2/portfolios/{test_id}/scenarios', - json={ - 'scenario': { - 'name': 'test', - 'trace': { - 'traceId': test_id, - 'loadSamplingFraction': 1.0, - }, - 'topology': { - 'topologyId': test_id, - }, - 'operational': { - 'failuresEnabled': True, - 'performanceInterferenceEnabled': False, - 'schedulerName': 'DEFAULT', - }, - } - }) - assert 'portfolioId' in res.json['content'] - assert 'simulation' in res.json['content'] - assert '200' in res.status diff --git a/opendc-web/opendc-web-api/opendc/api/v2/portfolios/portfolioId/test_endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/portfolios/portfolioId/test_endpoint.py deleted file mode 100644 index 52f71aa4..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/portfolios/portfolioId/test_endpoint.py +++ /dev/null @@ -1,152 +0,0 @@ -from opendc.util.database import DB - -test_id = 24 * '1' -test_id_2 = 24 * '2' - - -def test_get_portfolio_non_existing(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value=None) - assert '404' in client.get(f'/v2/portfolios/{test_id}').status - - -def test_get_portfolio_no_authorizations(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value={'projectId': test_id, 'authorizations': []}) - res = client.get(f'/v2/portfolios/{test_id}') - assert '403' in res.status - - -def test_get_portfolio_not_authorized(client, mocker): - mocker.patch.object(DB, - 'fetch_one', - return_value={ - 'projectId': test_id, - '_id': test_id, - 'authorizations': [{ - 'projectId': test_id_2, - 'authorizationLevel': 'OWN' - }] - }) - res = client.get(f'/v2/portfolios/{test_id}') - assert '403' in res.status - - -def test_get_portfolio(client, mocker): - mocker.patch.object(DB, - 'fetch_one', - return_value={ - 'projectId': test_id, - '_id': test_id, - 'authorizations': [{ - 'projectId': test_id, - 'authorizationLevel': 'EDIT' - }] - }) - res = client.get(f'/v2/portfolios/{test_id}') - assert '200' in res.status - - -def test_update_portfolio_missing_parameter(client): - assert '400' in client.put(f'/v2/portfolios/{test_id}').status - - -def test_update_portfolio_non_existing(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value=None) - assert '404' in client.put(f'/v2/portfolios/{test_id}', json={ - 'portfolio': { - 'name': 'test', - 'targets': { - 'enabledMetrics': ['test'], - 'repeatsPerScenario': 2 - } - } - }).status - - -def test_update_portfolio_not_authorized(client, mocker): - mocker.patch.object(DB, - 'fetch_one', - return_value={ - '_id': test_id, - 'projectId': test_id, - 'authorizations': [{ - 'projectId': test_id, - 'authorizationLevel': 'VIEW' - }] - }) - mocker.patch.object(DB, 'update', return_value={}) - assert '403' in client.put(f'/v2/portfolios/{test_id}', json={ - 'portfolio': { - 'name': 'test', - 'targets': { - 'enabledMetrics': ['test'], - 'repeatsPerScenario': 2 - } - } - }).status - - -def test_update_portfolio(client, mocker): - mocker.patch.object(DB, - 'fetch_one', - return_value={ - '_id': test_id, - 'projectId': test_id, - 'authorizations': [{ - 'projectId': test_id, - 'authorizationLevel': 'OWN' - }], - 'targets': { - 'enabledMetrics': [], - 'repeatsPerScenario': 1 - } - }) - mocker.patch.object(DB, 'update', return_value={}) - - res = client.put(f'/v2/portfolios/{test_id}', json={'portfolio': { - 'name': 'test', - 'targets': { - 'enabledMetrics': ['test'], - 'repeatsPerScenario': 2 - } - }}) - assert '200' in res.status - - -def test_delete_project_non_existing(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value=None) - assert '404' in client.delete(f'/v2/portfolios/{test_id}').status - - -def test_delete_project_different_user(client, mocker): - mocker.patch.object(DB, - 'fetch_one', - return_value={ - '_id': test_id, - 'projectId': test_id, - 'googleId': 'other_test', - 'authorizations': [{ - 'projectId': test_id, - 'authorizationLevel': 'VIEW' - }] - }) - mocker.patch.object(DB, 'delete_one', return_value=None) - assert '403' in client.delete(f'/v2/portfolios/{test_id}').status - - -def test_delete_project(client, mocker): - mocker.patch.object(DB, - 'fetch_one', - return_value={ - '_id': test_id, - 'projectId': test_id, - 'googleId': 'test', - 'portfolioIds': [test_id], - 'authorizations': [{ - 'projectId': test_id, - 'authorizationLevel': 'OWN' - }] - }) - mocker.patch.object(DB, 'delete_one', return_value={}) - mocker.patch.object(DB, 'update', return_value=None) - res = client.delete(f'/v2/portfolios/{test_id}') - assert '200' in res.status diff --git a/opendc-web/opendc-web-api/opendc/api/v2/prefabs/__init__.py b/opendc-web/opendc-web-api/opendc/api/v2/prefabs/__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/prefabs/__init__.py +++ /dev/null diff --git a/opendc-web/opendc-web-api/opendc/api/v2/prefabs/authorizations/__init__.py b/opendc-web/opendc-web-api/opendc/api/v2/prefabs/authorizations/__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/prefabs/authorizations/__init__.py +++ /dev/null diff --git a/opendc-web/opendc-web-api/opendc/api/v2/prefabs/authorizations/endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/prefabs/authorizations/endpoint.py deleted file mode 100644 index 0d9ad5cd..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/prefabs/authorizations/endpoint.py +++ /dev/null @@ -1,22 +0,0 @@ -from opendc.models.prefab import Prefab -from opendc.util.database import DB -from opendc.models.user import User -from opendc.util.rest import Response - - -def GET(request): - """Return all prefabs the user is authorized to access""" - - user = User.from_google_id(request.google_id) - - user.check_exists() - - own_prefabs = DB.fetch_all({'authorId': user.get_id()}, Prefab.collection_name) - public_prefabs = DB.fetch_all({'visibility': 'public'}, Prefab.collection_name) - - authorizations = {"authorizations": []} - - authorizations["authorizations"].append(own_prefabs) - authorizations["authorizations"].append(public_prefabs) - - return Response(200, 'Successfully fetched authorizations.', authorizations) diff --git a/opendc-web/opendc-web-api/opendc/api/v2/prefabs/authorizations/test_endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/prefabs/authorizations/test_endpoint.py deleted file mode 100644 index 6d36d428..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/prefabs/authorizations/test_endpoint.py +++ /dev/null @@ -1,71 +0,0 @@ -from opendc.util.database import DB -from unittest.mock import Mock - -test_id = 24 * '1' - - -def test_get_authorizations(client, mocker): - DB.fetch_all = Mock() - mocker.patch.object(DB, 'fetch_one', return_value={'_id': test_id}) - DB.fetch_all.side_effect = [ - [{ - '_id': test_id, - 'datetimeCreated': '000', - 'datetimeLastEdited': '000', - 'authorId': test_id, - 'visibility' : 'private' - }, - { - '_id': '2' * 24, - 'datetimeCreated': '000', - 'datetimeLastEdited': '000', - 'authorId': test_id, - 'visibility' : 'private' - }, - { - '_id': '3' * 24, - 'datetimeCreated': '000', - 'datetimeLastEdited': '000', - 'authorId': test_id, - 'visibility' : 'public' - }, - { - '_id': '4' * 24, - 'datetimeCreated': '000', - 'datetimeLastEdited': '000', - 'authorId': test_id, - 'visibility' : 'public' - }], - [{ - '_id': '5' * 24, - 'datetimeCreated': '000', - 'datetimeLastEdited': '000', - 'authorId': '2' * 24, - 'visibility' : 'public' - }, - { - '_id': '6' * 24, - 'datetimeCreated': '000', - 'datetimeLastEdited': '000', - 'authorId': '2' * 24, - 'visibility' : 'public' - }, - { - '_id': '7' * 24, - 'datetimeCreated': '000', - 'datetimeLastEdited': '000', - 'authorId': '2' * 24, - 'visibility' : 'public' - }, - { - '_id': '8' * 24, - 'datetimeCreated': '000', - 'datetimeLastEdited': '000', - 'authorId': '2' * 24, - 'visibility' : 'public' - }] - ] - mocker.patch.object(DB, 'fetch_one', return_value={'_id': test_id}) - res = client.get('/v2/prefabs/authorizations') - assert '200' in res.status - diff --git a/opendc-web/opendc-web-api/opendc/api/v2/prefabs/endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/prefabs/endpoint.py deleted file mode 100644 index 723a2f0d..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/prefabs/endpoint.py +++ /dev/null @@ -1,23 +0,0 @@ -from datetime import datetime - -from opendc.models.prefab import Prefab -from opendc.models.user import User -from opendc.util.database import Database -from opendc.util.rest import Response - - -def POST(request): - """Create a new prefab, and return that new prefab.""" - - request.check_required_parameters(body={'prefab': {'name': 'string'}}) - - prefab = Prefab(request.params_body['prefab']) - prefab.set_property('datetimeCreated', Database.datetime_to_string(datetime.now())) - prefab.set_property('datetimeLastEdited', Database.datetime_to_string(datetime.now())) - - user = User.from_google_id(request.google_id) - prefab.set_property('authorId', user.get_id()) - - prefab.insert() - - return Response(200, 'Successfully created prefab.', prefab.obj) diff --git a/opendc-web/opendc-web-api/opendc/api/v2/prefabs/prefabId/__init__.py b/opendc-web/opendc-web-api/opendc/api/v2/prefabs/prefabId/__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/prefabs/prefabId/__init__.py +++ /dev/null diff --git a/opendc-web/opendc-web-api/opendc/api/v2/prefabs/prefabId/endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/prefabs/prefabId/endpoint.py deleted file mode 100644 index 7b81f546..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/prefabs/prefabId/endpoint.py +++ /dev/null @@ -1,50 +0,0 @@ -from datetime import datetime - -from opendc.models.prefab import Prefab -from opendc.util.database import Database -from opendc.util.rest import Response - - -def GET(request): - """Get this Prefab.""" - - request.check_required_parameters(path={'prefabId': 'string'}) - - prefab = Prefab.from_id(request.params_path['prefabId']) - prefab.check_exists() - prefab.check_user_access(request.google_id) - - return Response(200, 'Successfully retrieved prefab', prefab.obj) - - -def PUT(request): - """Update a prefab's name and/or contents.""" - - request.check_required_parameters(body={'prefab': {'name': 'name'}}, path={'prefabId': 'string'}) - - prefab = Prefab.from_id(request.params_path['prefabId']) - - prefab.check_exists() - prefab.check_user_access(request.google_id) - - prefab.set_property('name', request.params_body['prefab']['name']) - prefab.set_property('rack', request.params_body['prefab']['rack']) - prefab.set_property('datetime_last_edited', Database.datetime_to_string(datetime.now())) - prefab.update() - - return Response(200, 'Successfully updated prefab.', prefab.obj) - - -def DELETE(request): - """Delete this Prefab.""" - - request.check_required_parameters(path={'prefabId': 'string'}) - - prefab = Prefab.from_id(request.params_path['prefabId']) - - prefab.check_exists() - prefab.check_user_access(request.google_id) - - old_object = prefab.delete() - - return Response(200, 'Successfully deleted prefab.', old_object) diff --git a/opendc-web/opendc-web-api/opendc/api/v2/prefabs/prefabId/test_endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/prefabs/prefabId/test_endpoint.py deleted file mode 100644 index 2daeb6bf..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/prefabs/prefabId/test_endpoint.py +++ /dev/null @@ -1,145 +0,0 @@ -from opendc.util.database import DB -from unittest.mock import Mock - -test_id = 24 * '1' -test_id_2 = 24 * '2' - - -def test_get_prefab_non_existing(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value=None) - assert '404' in client.get(f'/v2/prefabs/{test_id}').status - - -def test_get_private_prefab_not_authorized(client, mocker): - DB.fetch_one = Mock() - DB.fetch_one.side_effect = [{ - '_id': test_id, - 'name': 'test prefab', - 'authorId': test_id_2, - 'visibility': 'private', - 'rack': {} - }, - { - '_id': test_id - } - ] - res = client.get(f'/v2/prefabs/{test_id}') - assert '403' in res.status - - -def test_get_private_prefab(client, mocker): - DB.fetch_one = Mock() - DB.fetch_one.side_effect = [{ - '_id': test_id, - 'name': 'test prefab', - 'authorId': test_id, - 'visibility': 'private', - 'rack': {} - }, - { - '_id': test_id - } - ] - res = client.get(f'/v2/prefabs/{test_id}') - assert '200' in res.status - - -def test_get_public_prefab(client, mocker): - DB.fetch_one = Mock() - DB.fetch_one.side_effect = [{ - '_id': test_id, - 'name': 'test prefab', - 'authorId': test_id_2, - 'visibility': 'public', - 'rack': {} - }, - { - '_id': test_id - } - ] - res = client.get(f'/v2/prefabs/{test_id}') - assert '200' in res.status - - -def test_update_prefab_missing_parameter(client): - assert '400' in client.put(f'/v2/prefabs/{test_id}').status - - -def test_update_prefab_non_existing(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value=None) - assert '404' in client.put(f'/v2/prefabs/{test_id}', json={'prefab': {'name': 'S'}}).status - - -def test_update_prefab_not_authorized(client, mocker): - DB.fetch_one = Mock() - DB.fetch_one.side_effect = [{ - '_id': test_id, - 'name': 'test prefab', - 'authorId': test_id_2, - 'visibility': 'private', - 'rack': {} - }, - { - '_id': test_id - } - ] - mocker.patch.object(DB, 'update', return_value={}) - assert '403' in client.put(f'/v2/prefabs/{test_id}', json={'prefab': {'name': 'test prefab', 'rack': {}}}).status - - -def test_update_prefab(client, mocker): - DB.fetch_one = Mock() - DB.fetch_one.side_effect = [{ - '_id': test_id, - 'name': 'test prefab', - 'authorId': test_id, - 'visibility': 'private', - 'rack': {} - }, - { - '_id': test_id - } - ] - mocker.patch.object(DB, 'update', return_value={}) - res = client.put(f'/v2/prefabs/{test_id}', json={'prefab': {'name': 'test prefab', 'rack': {}}}) - assert '200' in res.status - - -def test_delete_prefab_non_existing(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value=None) - assert '404' in client.delete(f'/v2/prefabs/{test_id}').status - - -def test_delete_prefab_different_user(client, mocker): - DB.fetch_one = Mock() - DB.fetch_one.side_effect = [{ - '_id': test_id, - 'name': 'test prefab', - 'authorId': test_id_2, - 'visibility': 'private', - 'rack': {} - }, - { - '_id': test_id - } - ] - mocker.patch.object(DB, 'delete_one', return_value=None) - assert '403' in client.delete(f'/v2/prefabs/{test_id}').status - - -def test_delete_prefab(client, mocker): - DB.fetch_one = Mock() - DB.fetch_one.side_effect = [{ - '_id': test_id, - 'name': 'test prefab', - 'authorId': test_id, - 'visibility': 'private', - 'rack': {} - }, - { - '_id': test_id - } - ] - mocker.patch.object(DB, 'delete_one', return_value={'prefab': {'name': 'name'}}) - res = client.delete(f'/v2/prefabs/{test_id}') - assert '200' in res.status diff --git a/opendc-web/opendc-web-api/opendc/api/v2/prefabs/test_endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/prefabs/test_endpoint.py deleted file mode 100644 index 39a78c21..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/prefabs/test_endpoint.py +++ /dev/null @@ -1,24 +0,0 @@ -from opendc.util.database import DB - -test_id = 24 * '1' - - -def test_add_prefab_missing_parameter(client): - assert '400' in client.post('/v2/prefabs').status - - -def test_add_prefab(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value={'_id': test_id, 'authorizations': []}) - mocker.patch.object(DB, - 'insert', - return_value={ - '_id': test_id, - 'datetimeCreated': '000', - 'datetimeLastEdited': '000', - 'authorId': test_id - }) - res = client.post('/v2/prefabs', json={'prefab': {'name': 'test prefab'}}) - assert 'datetimeCreated' in res.json['content'] - assert 'datetimeLastEdited' in res.json['content'] - assert 'authorId' in res.json['content'] - assert '200' in res.status diff --git a/opendc-web/opendc-web-api/opendc/api/v2/projects/__init__.py b/opendc-web/opendc-web-api/opendc/api/v2/projects/__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/projects/__init__.py +++ /dev/null diff --git a/opendc-web/opendc-web-api/opendc/api/v2/projects/endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/projects/endpoint.py deleted file mode 100644 index bf031382..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/projects/endpoint.py +++ /dev/null @@ -1,32 +0,0 @@ -from datetime import datetime - -from opendc.models.project import Project -from opendc.models.topology import Topology -from opendc.models.user import User -from opendc.util.database import Database -from opendc.util.rest import Response - - -def POST(request): - """Create a new project, and return that new project.""" - - request.check_required_parameters(body={'project': {'name': 'string'}}) - - topology = Topology({'name': 'Default topology', 'rooms': []}) - topology.insert() - - project = Project(request.params_body['project']) - project.set_property('datetimeCreated', Database.datetime_to_string(datetime.now())) - project.set_property('datetimeLastEdited', Database.datetime_to_string(datetime.now())) - project.set_property('topologyIds', [topology.get_id()]) - project.set_property('portfolioIds', []) - project.insert() - - topology.set_property('projectId', project.get_id()) - topology.update() - - user = User.from_google_id(request.google_id) - user.obj['authorizations'].append({'projectId': project.get_id(), 'authorizationLevel': 'OWN'}) - user.update() - - return Response(200, 'Successfully created project.', project.obj) diff --git a/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/__init__.py b/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/__init__.py +++ /dev/null diff --git a/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/authorizations/__init__.py b/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/authorizations/__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/authorizations/__init__.py +++ /dev/null diff --git a/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/authorizations/endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/authorizations/endpoint.py deleted file mode 100644 index 9f6a60ec..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/authorizations/endpoint.py +++ /dev/null @@ -1,17 +0,0 @@ -from opendc.models.project import Project -from opendc.util.rest import Response - - -def GET(request): - """Find all authorizations for a Project.""" - - request.check_required_parameters(path={'projectId': 'string'}) - - project = Project.from_id(request.params_path['projectId']) - - project.check_exists() - project.check_user_access(request.google_id, False) - - authorizations = project.get_all_authorizations() - - return Response(200, 'Successfully retrieved project authorizations', authorizations) diff --git a/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/authorizations/test_endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/authorizations/test_endpoint.py deleted file mode 100644 index bebd6cff..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/authorizations/test_endpoint.py +++ /dev/null @@ -1,43 +0,0 @@ -from opendc.util.database import DB - -test_id = 24 * '1' -test_id_2 = 24 * '2' - - -def test_get_authorizations_non_existing(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value=None) - mocker.patch.object(DB, 'fetch_all', return_value=None) - assert '404' in client.get(f'/v2/projects/{test_id}/authorizations').status - - -def test_get_authorizations_not_authorized(client, mocker): - mocker.patch.object(DB, - 'fetch_one', - return_value={ - '_id': test_id, - 'name': 'test trace', - 'authorizations': [{ - 'projectId': test_id_2, - 'authorizationLevel': 'OWN' - }] - }) - mocker.patch.object(DB, 'fetch_all', return_value=[]) - res = client.get(f'/v2/projects/{test_id}/authorizations') - assert '403' in res.status - - -def test_get_authorizations(client, mocker): - mocker.patch.object(DB, - 'fetch_one', - return_value={ - '_id': test_id, - 'name': 'test trace', - 'authorizations': [{ - 'projectId': test_id, - 'authorizationLevel': 'OWN' - }] - }) - mocker.patch.object(DB, 'fetch_all', return_value=[]) - res = client.get(f'/v2/projects/{test_id}/authorizations') - assert len(res.json['content']) == 0 - assert '200' in res.status diff --git a/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/endpoint.py deleted file mode 100644 index caac37ca..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/endpoint.py +++ /dev/null @@ -1,66 +0,0 @@ -from datetime import datetime - -from opendc.models.portfolio import Portfolio -from opendc.models.project import Project -from opendc.models.topology import Topology -from opendc.models.user import User -from opendc.util.database import Database -from opendc.util.rest import Response - - -def GET(request): - """Get this Project.""" - - request.check_required_parameters(path={'projectId': 'string'}) - - project = Project.from_id(request.params_path['projectId']) - - project.check_exists() - project.check_user_access(request.google_id, False) - - return Response(200, 'Successfully retrieved project', project.obj) - - -def PUT(request): - """Update a project's name.""" - - request.check_required_parameters(body={'project': {'name': 'name'}}, path={'projectId': 'string'}) - - project = Project.from_id(request.params_path['projectId']) - - project.check_exists() - project.check_user_access(request.google_id, True) - - project.set_property('name', request.params_body['project']['name']) - project.set_property('datetime_last_edited', Database.datetime_to_string(datetime.now())) - project.update() - - return Response(200, 'Successfully updated project.', project.obj) - - -def DELETE(request): - """Delete this Project.""" - - request.check_required_parameters(path={'projectId': 'string'}) - - project = Project.from_id(request.params_path['projectId']) - - project.check_exists() - project.check_user_access(request.google_id, True) - - for topology_id in project.obj['topologyIds']: - topology = Topology.from_id(topology_id) - topology.delete() - - for portfolio_id in project.obj['portfolioIds']: - portfolio = Portfolio.from_id(portfolio_id) - portfolio.delete() - - user = User.from_google_id(request.google_id) - user.obj['authorizations'] = list( - filter(lambda x: x['projectId'] != project.get_id(), user.obj['authorizations'])) - user.update() - - old_object = project.delete() - - return Response(200, 'Successfully deleted project.', old_object) diff --git a/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/portfolios/__init__.py b/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/portfolios/__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/portfolios/__init__.py +++ /dev/null diff --git a/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/portfolios/endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/portfolios/endpoint.py deleted file mode 100644 index 2cdb1194..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/portfolios/endpoint.py +++ /dev/null @@ -1,35 +0,0 @@ -from opendc.models.portfolio import Portfolio -from opendc.models.project import Project -from opendc.util.rest import Response - - -def POST(request): - """Add a new Portfolio for this Project.""" - - request.check_required_parameters(path={'projectId': 'string'}, - body={ - 'portfolio': { - 'name': 'string', - 'targets': { - 'enabledMetrics': 'list', - 'repeatsPerScenario': 'int', - }, - } - }) - - project = Project.from_id(request.params_path['projectId']) - - project.check_exists() - project.check_user_access(request.google_id, True) - - portfolio = Portfolio(request.params_body['portfolio']) - - portfolio.set_property('projectId', project.get_id()) - portfolio.set_property('scenarioIds', []) - - portfolio.insert() - - project.obj['portfolioIds'].append(portfolio.get_id()) - project.update() - - return Response(200, 'Successfully added Portfolio.', portfolio.obj) diff --git a/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/portfolios/test_endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/portfolios/test_endpoint.py deleted file mode 100644 index 04c699b5..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/portfolios/test_endpoint.py +++ /dev/null @@ -1,85 +0,0 @@ -from opendc.util.database import DB - -test_id = 24 * '1' - - -def test_add_portfolio_missing_parameter(client): - assert '400' in client.post(f'/v2/projects/{test_id}/portfolios').status - - -def test_add_portfolio_non_existing_project(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value=None) - assert '404' in client.post(f'/v2/projects/{test_id}/portfolios', - json={ - 'portfolio': { - 'name': 'test', - 'targets': { - 'enabledMetrics': ['test'], - 'repeatsPerScenario': 2 - } - } - }).status - - -def test_add_portfolio_not_authorized(client, mocker): - mocker.patch.object(DB, - 'fetch_one', - return_value={ - '_id': test_id, - 'projectId': test_id, - 'authorizations': [{ - 'projectId': test_id, - 'authorizationLevel': 'VIEW' - }] - }) - assert '403' in client.post(f'/v2/projects/{test_id}/portfolios', - json={ - 'portfolio': { - 'name': 'test', - 'targets': { - 'enabledMetrics': ['test'], - 'repeatsPerScenario': 2 - } - } - }).status - - -def test_add_portfolio(client, mocker): - mocker.patch.object(DB, - 'fetch_one', - return_value={ - '_id': test_id, - 'projectId': test_id, - 'portfolioIds': [test_id], - 'authorizations': [{ - 'projectId': test_id, - 'authorizationLevel': 'EDIT' - }] - }) - mocker.patch.object(DB, - 'insert', - return_value={ - '_id': test_id, - 'name': 'test', - 'targets': { - 'enabledMetrics': ['test'], - 'repeatsPerScenario': 2 - }, - 'projectId': test_id, - 'scenarioIds': [], - }) - mocker.patch.object(DB, 'update', return_value=None) - res = client.post( - f'/v2/projects/{test_id}/portfolios', - json={ - 'portfolio': { - 'name': 'test', - 'targets': { - 'enabledMetrics': ['test'], - 'repeatsPerScenario': 2 - } - } - }) - assert 'projectId' in res.json['content'] - assert 'scenarioIds' in res.json['content'] - assert '200' in res.status diff --git a/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/test_endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/test_endpoint.py deleted file mode 100644 index f9ffaf37..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/test_endpoint.py +++ /dev/null @@ -1,122 +0,0 @@ -from opendc.util.database import DB - -test_id = 24 * '1' -test_id_2 = 24 * '2' - - -def test_get_project_non_existing(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value=None) - assert '404' in client.get(f'/v2/projects/{test_id}').status - - -def test_get_project_no_authorizations(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value={'authorizations': []}) - res = client.get(f'/v2/projects/{test_id}') - assert '403' in res.status - - -def test_get_project_not_authorized(client, mocker): - mocker.patch.object(DB, - 'fetch_one', - return_value={ - '_id': test_id, - 'authorizations': [{ - 'projectId': test_id_2, - 'authorizationLevel': 'OWN' - }] - }) - res = client.get(f'/v2/projects/{test_id}') - assert '403' in res.status - - -def test_get_project(client, mocker): - mocker.patch.object(DB, - 'fetch_one', - return_value={ - '_id': test_id, - 'authorizations': [{ - 'projectId': test_id, - 'authorizationLevel': 'EDIT' - }] - }) - res = client.get(f'/v2/projects/{test_id}') - assert '200' in res.status - - -def test_update_project_missing_parameter(client): - assert '400' in client.put(f'/v2/projects/{test_id}').status - - -def test_update_project_non_existing(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value=None) - assert '404' in client.put(f'/v2/projects/{test_id}', json={'project': {'name': 'S'}}).status - - -def test_update_project_not_authorized(client, mocker): - mocker.patch.object(DB, - 'fetch_one', - return_value={ - '_id': test_id, - 'authorizations': [{ - 'projectId': test_id, - 'authorizationLevel': 'VIEW' - }] - }) - mocker.patch.object(DB, 'update', return_value={}) - assert '403' in client.put(f'/v2/projects/{test_id}', json={'project': {'name': 'S'}}).status - - -def test_update_project(client, mocker): - mocker.patch.object(DB, - 'fetch_one', - return_value={ - '_id': test_id, - 'authorizations': [{ - 'projectId': test_id, - 'authorizationLevel': 'OWN' - }] - }) - mocker.patch.object(DB, 'update', return_value={}) - - res = client.put(f'/v2/projects/{test_id}', json={'project': {'name': 'S'}}) - assert '200' in res.status - - -def test_delete_project_non_existing(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value=None) - assert '404' in client.delete(f'/v2/projects/{test_id}').status - - -def test_delete_project_different_user(client, mocker): - mocker.patch.object(DB, - 'fetch_one', - return_value={ - '_id': test_id, - 'googleId': 'other_test', - 'authorizations': [{ - 'projectId': test_id, - 'authorizationLevel': 'VIEW' - }], - 'topologyIds': [] - }) - mocker.patch.object(DB, 'delete_one', return_value=None) - assert '403' in client.delete(f'/v2/projects/{test_id}').status - - -def test_delete_project(client, mocker): - mocker.patch.object(DB, - 'fetch_one', - return_value={ - '_id': test_id, - 'googleId': 'test', - 'authorizations': [{ - 'projectId': test_id, - 'authorizationLevel': 'OWN' - }], - 'topologyIds': [], - 'portfolioIds': [], - }) - mocker.patch.object(DB, 'update', return_value=None) - mocker.patch.object(DB, 'delete_one', return_value={'googleId': 'test'}) - res = client.delete(f'/v2/projects/{test_id}') - assert '200' in res.status diff --git a/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/topologies/__init__.py b/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/topologies/__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/topologies/__init__.py +++ /dev/null diff --git a/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/topologies/endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/topologies/endpoint.py deleted file mode 100644 index 44a0d575..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/topologies/endpoint.py +++ /dev/null @@ -1,31 +0,0 @@ -from datetime import datetime - -from opendc.models.project import Project -from opendc.models.topology import Topology -from opendc.util.rest import Response -from opendc.util.database import Database - - -def POST(request): - """Add a new Topology to the specified project and return it""" - - request.check_required_parameters(path={'projectId': 'string'}, body={'topology': {'name': 'string'}}) - - project = Project.from_id(request.params_path['projectId']) - - project.check_exists() - project.check_user_access(request.google_id, True) - - topology = Topology({ - 'projectId': project.get_id(), - 'name': request.params_body['topology']['name'], - 'rooms': request.params_body['topology']['rooms'], - }) - - topology.insert() - - project.obj['topologyIds'].append(topology.get_id()) - project.set_property('datetimeLastEdited', Database.datetime_to_string(datetime.now())) - project.update() - - return Response(200, 'Successfully inserted topology.', topology.obj) diff --git a/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/topologies/test_endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/topologies/test_endpoint.py deleted file mode 100644 index 71e88f00..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/projects/projectId/topologies/test_endpoint.py +++ /dev/null @@ -1,52 +0,0 @@ -from opendc.util.database import DB - -test_id = 24 * '1' - - -def test_add_topology_missing_parameter(client): - assert '400' in client.post(f'/v2/projects/{test_id}/topologies').status - - -def test_add_topology(client, mocker): - mocker.patch.object(DB, - 'fetch_one', - return_value={ - '_id': test_id, - 'authorizations': [{ - 'projectId': test_id, - 'authorizationLevel': 'OWN' - }], - 'topologyIds': [] - }) - mocker.patch.object(DB, - 'insert', - return_value={ - '_id': test_id, - 'datetimeCreated': '000', - 'datetimeLastEdited': '000', - 'topologyIds': [] - }) - mocker.patch.object(DB, 'update', return_value={}) - res = client.post(f'/v2/projects/{test_id}/topologies', json={'topology': {'name': 'test project', 'rooms': []}}) - assert 'rooms' in res.json['content'] - assert '200' in res.status - - -def test_add_topology_not_authorized(client, mocker): - mocker.patch.object(DB, - 'fetch_one', - return_value={ - '_id': test_id, - 'projectId': test_id, - 'authorizations': [{ - 'projectId': test_id, - 'authorizationLevel': 'VIEW' - }] - }) - assert '403' in client.post(f'/v2/projects/{test_id}/topologies', - json={ - 'topology': { - 'name': 'test_topology', - 'rooms': {} - } - }).status diff --git a/opendc-web/opendc-web-api/opendc/api/v2/projects/test_endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/projects/test_endpoint.py deleted file mode 100644 index 9444b1e4..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/projects/test_endpoint.py +++ /dev/null @@ -1,25 +0,0 @@ -from opendc.util.database import DB - -test_id = 24 * '1' - - -def test_add_project_missing_parameter(client): - assert '400' in client.post('/v2/projects').status - - -def test_add_project(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value={'_id': test_id, 'authorizations': []}) - mocker.patch.object(DB, - 'insert', - return_value={ - '_id': test_id, - 'datetimeCreated': '000', - 'datetimeLastEdited': '000', - 'topologyIds': [] - }) - mocker.patch.object(DB, 'update', return_value={}) - res = client.post('/v2/projects', json={'project': {'name': 'test project'}}) - assert 'datetimeCreated' in res.json['content'] - assert 'datetimeLastEdited' in res.json['content'] - assert 'topologyIds' in res.json['content'] - assert '200' in res.status diff --git a/opendc-web/opendc-web-api/opendc/api/v2/scenarios/__init__.py b/opendc-web/opendc-web-api/opendc/api/v2/scenarios/__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/scenarios/__init__.py +++ /dev/null diff --git a/opendc-web/opendc-web-api/opendc/api/v2/scenarios/scenarioId/__init__.py b/opendc-web/opendc-web-api/opendc/api/v2/scenarios/scenarioId/__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/scenarios/scenarioId/__init__.py +++ /dev/null diff --git a/opendc-web/opendc-web-api/opendc/api/v2/scenarios/scenarioId/endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/scenarios/scenarioId/endpoint.py deleted file mode 100644 index 88a74e9c..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/scenarios/scenarioId/endpoint.py +++ /dev/null @@ -1,59 +0,0 @@ -from opendc.models.scenario import Scenario -from opendc.models.portfolio import Portfolio -from opendc.util.rest import Response - - -def GET(request): - """Get this Scenario.""" - - request.check_required_parameters(path={'scenarioId': 'string'}) - - scenario = Scenario.from_id(request.params_path['scenarioId']) - - scenario.check_exists() - scenario.check_user_access(request.google_id, False) - - return Response(200, 'Successfully retrieved scenario.', scenario.obj) - - -def PUT(request): - """Update this Scenarios name.""" - - request.check_required_parameters(path={'scenarioId': 'string'}, body={'scenario': { - 'name': 'string', - }}) - - scenario = Scenario.from_id(request.params_path['scenarioId']) - - scenario.check_exists() - scenario.check_user_access(request.google_id, True) - - scenario.set_property('name', - request.params_body['scenario']['name']) - - scenario.update() - - return Response(200, 'Successfully updated scenario.', scenario.obj) - - -def DELETE(request): - """Delete this Scenario.""" - - request.check_required_parameters(path={'scenarioId': 'string'}) - - scenario = Scenario.from_id(request.params_path['scenarioId']) - - scenario.check_exists() - scenario.check_user_access(request.google_id, True) - - scenario_id = scenario.get_id() - - portfolio = Portfolio.from_id(scenario.obj['portfolioId']) - portfolio.check_exists() - if scenario_id in portfolio.obj['scenarioIds']: - portfolio.obj['scenarioIds'].remove(scenario_id) - portfolio.update() - - old_object = scenario.delete() - - return Response(200, 'Successfully deleted scenario.', old_object) diff --git a/opendc-web/opendc-web-api/opendc/api/v2/scenarios/scenarioId/test_endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/scenarios/scenarioId/test_endpoint.py deleted file mode 100644 index cd4bcdf8..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/scenarios/scenarioId/test_endpoint.py +++ /dev/null @@ -1,149 +0,0 @@ -from opendc.util.database import DB - -test_id = 24 * '1' -test_id_2 = 24 * '2' - - -def test_get_scenario_non_existing(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value=None) - assert '404' in client.get(f'/v2/scenarios/{test_id}').status - - -def test_get_scenario_no_authorizations(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value={ - 'portfolioId': '1', - 'authorizations': [] - }) - res = client.get(f'/v2/scenarios/{test_id}') - assert '403' in res.status - - -def test_get_scenario_not_authorized(client, mocker): - mocker.patch.object(DB, - 'fetch_one', - return_value={ - 'projectId': test_id, - 'portfolioId': test_id, - '_id': test_id, - 'authorizations': [{ - 'projectId': test_id_2, - 'authorizationLevel': 'OWN' - }] - }) - res = client.get(f'/v2/scenarios/{test_id}') - assert '403' in res.status - - -def test_get_scenario(client, mocker): - mocker.patch.object(DB, - 'fetch_one', - return_value={ - 'projectId': test_id, - 'portfolioId': test_id, - '_id': test_id, - 'authorizations': [{ - 'projectId': test_id, - 'authorizationLevel': 'EDIT' - }] - }) - res = client.get(f'/v2/scenarios/{test_id}') - assert '200' in res.status - - -def test_update_scenario_missing_parameter(client): - assert '400' in client.put(f'/v2/scenarios/{test_id}').status - - -def test_update_scenario_non_existing(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value=None) - assert '404' in client.put(f'/v2/scenarios/{test_id}', json={ - 'scenario': { - 'name': 'test', - } - }).status - - -def test_update_scenario_not_authorized(client, mocker): - mocker.patch.object(DB, - 'fetch_one', - return_value={ - '_id': test_id, - 'projectId': test_id, - 'portfolioId': test_id, - 'authorizations': [{ - 'projectId': test_id, - 'authorizationLevel': 'VIEW' - }] - }) - mocker.patch.object(DB, 'update', return_value={}) - assert '403' in client.put(f'/v2/scenarios/{test_id}', json={ - 'scenario': { - 'name': 'test', - } - }).status - - -def test_update_scenario(client, mocker): - mocker.patch.object(DB, - 'fetch_one', - return_value={ - '_id': test_id, - 'projectId': test_id, - 'portfolioId': test_id, - 'authorizations': [{ - 'projectId': test_id, - 'authorizationLevel': 'OWN' - }], - 'targets': { - 'enabledMetrics': [], - 'repeatsPerScenario': 1 - } - }) - mocker.patch.object(DB, 'update', return_value={}) - - res = client.put(f'/v2/scenarios/{test_id}', json={'scenario': { - 'name': 'test', - }}) - assert '200' in res.status - - -def test_delete_project_non_existing(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value=None) - assert '404' in client.delete(f'/v2/scenarios/{test_id}').status - - -def test_delete_project_different_user(client, mocker): - mocker.patch.object(DB, - 'fetch_one', - return_value={ - '_id': test_id, - 'projectId': test_id, - 'portfolioId': test_id, - 'googleId': 'other_test', - 'authorizations': [{ - 'projectId': test_id, - 'authorizationLevel': 'VIEW' - }] - }) - mocker.patch.object(DB, 'delete_one', return_value=None) - assert '403' in client.delete(f'/v2/scenarios/{test_id}').status - - -def test_delete_project(client, mocker): - mocker.patch.object(DB, - 'fetch_one', - return_value={ - '_id': test_id, - 'projectId': test_id, - 'portfolioId': test_id, - 'googleId': 'test', - 'scenarioIds': [test_id], - 'authorizations': [{ - 'projectId': test_id, - 'authorizationLevel': 'OWN' - }] - }) - mocker.patch.object(DB, 'delete_one', return_value={}) - mocker.patch.object(DB, 'update', return_value=None) - res = client.delete(f'/v2/scenarios/{test_id}') - assert '200' in res.status diff --git a/opendc-web/opendc-web-api/opendc/api/v2/schedulers/__init__.py b/opendc-web/opendc-web-api/opendc/api/v2/schedulers/__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/schedulers/__init__.py +++ /dev/null diff --git a/opendc-web/opendc-web-api/opendc/api/v2/schedulers/endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/schedulers/endpoint.py deleted file mode 100644 index f33159bf..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/schedulers/endpoint.py +++ /dev/null @@ -1,19 +0,0 @@ -from opendc.util.rest import Response - -SCHEDULERS = [ - 'mem', - 'mem-inv', - 'core-mem', - 'core-mem-inv', - 'active-servers', - 'active-servers-inv', - 'provisioned-cores', - 'provisioned-cores-inv', - 'random' -] - - -def GET(_): - """Get all available Schedulers.""" - - return Response(200, 'Successfully retrieved Schedulers.', [{'name': name} for name in SCHEDULERS]) diff --git a/opendc-web/opendc-web-api/opendc/api/v2/schedulers/test_endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/schedulers/test_endpoint.py deleted file mode 100644 index 4950ca4c..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/schedulers/test_endpoint.py +++ /dev/null @@ -1,2 +0,0 @@ -def test_get_schedulers(client): - assert '200' in client.get('/v2/schedulers').status diff --git a/opendc-web/opendc-web-api/opendc/api/v2/topologies/__init__.py b/opendc-web/opendc-web-api/opendc/api/v2/topologies/__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/topologies/__init__.py +++ /dev/null diff --git a/opendc-web/opendc-web-api/opendc/api/v2/topologies/topologyId/__init__.py b/opendc-web/opendc-web-api/opendc/api/v2/topologies/topologyId/__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/topologies/topologyId/__init__.py +++ /dev/null diff --git a/opendc-web/opendc-web-api/opendc/api/v2/topologies/topologyId/endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/topologies/topologyId/endpoint.py deleted file mode 100644 index ea82b2e2..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/topologies/topologyId/endpoint.py +++ /dev/null @@ -1,58 +0,0 @@ -from datetime import datetime - -from opendc.util.database import Database -from opendc.models.project import Project -from opendc.models.topology import Topology -from opendc.util.rest import Response - - -def GET(request): - """Get this Topology.""" - - request.check_required_parameters(path={'topologyId': 'string'}) - - 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) - - -def PUT(request): - """Update this topology""" - request.check_required_parameters(path={'topologyId': 'string'}, body={'topology': {'name': 'string', 'rooms': {}}}) - topology = Topology.from_id(request.params_path['topologyId']) - - topology.check_exists() - topology.check_user_access(request.google_id, True) - - topology.set_property('name', request.params_body['topology']['name']) - topology.set_property('rooms', request.params_body['topology']['rooms']) - topology.set_property('datetimeLastEdited', Database.datetime_to_string(datetime.now())) - - topology.update() - - return Response(200, 'Successfully updated topology.', topology.obj) - - -def DELETE(request): - """Delete this topology""" - request.check_required_parameters(path={'topologyId': 'string'}) - - topology = Topology.from_id(request.params_path['topologyId']) - - topology.check_exists() - topology.check_user_access(request.google_id, True) - - topology_id = topology.get_id() - - project = Project.from_id(topology.obj['projectId']) - project.check_exists() - if topology_id in project.obj['topologyIds']: - project.obj['topologyIds'].remove(topology_id) - project.update() - - old_object = topology.delete() - - return Response(200, 'Successfully deleted topology.', old_object) diff --git a/opendc-web/opendc-web-api/opendc/api/v2/topologies/topologyId/test_endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/topologies/topologyId/test_endpoint.py deleted file mode 100644 index 4da0bc64..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/topologies/topologyId/test_endpoint.py +++ /dev/null @@ -1,119 +0,0 @@ -from opendc.util.database import DB - -test_id = 24 * '1' -test_id_2 = 24 * '2' - - -def test_get_topology(client, mocker): - mocker.patch.object(DB, - 'fetch_one', - return_value={ - '_id': test_id, - 'projectId': test_id, - 'authorizations': [{ - 'projectId': test_id, - 'authorizationLevel': 'EDIT' - }] - }) - res = client.get(f'/v2/topologies/{test_id}') - 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('/v2/topologies/1').status - - -def test_get_topology_not_authorized(client, mocker): - mocker.patch.object(DB, - 'fetch_one', - return_value={ - '_id': test_id, - 'projectId': test_id, - 'authorizations': [{ - 'projectId': test_id_2, - 'authorizationLevel': 'OWN' - }] - }) - res = client.get(f'/v2/topologies/{test_id}') - assert '403' in res.status - - -def test_get_topology_no_authorizations(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value={'projectId': test_id, 'authorizations': []}) - res = client.get(f'/v2/topologies/{test_id}') - assert '403' in res.status - - -def test_update_topology_missing_parameter(client): - assert '400' in client.put(f'/v2/topologies/{test_id}').status - - -def test_update_topology_non_existent(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value=None) - assert '404' in client.put(f'/v2/topologies/{test_id}', json={'topology': {'name': 'test_topology', 'rooms': {}}}).status - - -def test_update_topology_not_authorized(client, mocker): - mocker.patch.object(DB, - 'fetch_one', - return_value={ - '_id': test_id, - 'projectId': test_id, - 'authorizations': [{ - 'projectId': test_id, - 'authorizationLevel': 'VIEW' - }] - }) - mocker.patch.object(DB, 'update', return_value={}) - assert '403' in client.put(f'/v2/topologies/{test_id}', json={ - 'topology': { - 'name': 'updated_topology', - 'rooms': {} - } - }).status - - -def test_update_topology(client, mocker): - mocker.patch.object(DB, - 'fetch_one', - return_value={ - '_id': test_id, - 'projectId': test_id, - 'authorizations': [{ - 'projectId': test_id, - 'authorizationLevel': 'OWN' - }] - }) - mocker.patch.object(DB, 'update', return_value={}) - - assert '200' in client.put(f'/v2/topologies/{test_id}', json={ - 'topology': { - 'name': 'updated_topology', - 'rooms': {} - } - }).status - - -def test_delete_topology(client, mocker): - mocker.patch.object(DB, - 'fetch_one', - return_value={ - '_id': test_id, - 'projectId': test_id, - 'googleId': 'test', - 'topologyIds': [test_id], - 'authorizations': [{ - 'projectId': test_id, - 'authorizationLevel': 'OWN' - }] - }) - mocker.patch.object(DB, 'delete_one', return_value={}) - mocker.patch.object(DB, 'update', return_value=None) - res = client.delete(f'/v2/topologies/{test_id}') - assert '200' in res.status - - -def test_delete_nonexistent_topology(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value=None) - assert '404' in client.delete(f'/v2/topologies/{test_id}').status diff --git a/opendc-web/opendc-web-api/opendc/api/v2/traces/__init__.py b/opendc-web/opendc-web-api/opendc/api/v2/traces/__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/traces/__init__.py +++ /dev/null diff --git a/opendc-web/opendc-web-api/opendc/api/v2/traces/endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/traces/endpoint.py deleted file mode 100644 index ee699e02..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/traces/endpoint.py +++ /dev/null @@ -1,10 +0,0 @@ -from opendc.models.trace import Trace -from opendc.util.rest import Response - - -def GET(_): - """Get all available Traces.""" - - traces = Trace.get_all() - - return Response(200, 'Successfully retrieved Traces', traces.obj) diff --git a/opendc-web/opendc-web-api/opendc/api/v2/traces/test_endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/traces/test_endpoint.py deleted file mode 100644 index 36846bd9..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/traces/test_endpoint.py +++ /dev/null @@ -1,6 +0,0 @@ -from opendc.util.database import DB - - -def test_get_traces(client, mocker): - mocker.patch.object(DB, 'fetch_all', return_value=[]) - assert '200' in client.get('/v2/traces').status diff --git a/opendc-web/opendc-web-api/opendc/api/v2/traces/traceId/__init__.py b/opendc-web/opendc-web-api/opendc/api/v2/traces/traceId/__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/traces/traceId/__init__.py +++ /dev/null diff --git a/opendc-web/opendc-web-api/opendc/api/v2/traces/traceId/endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/traces/traceId/endpoint.py deleted file mode 100644 index 670f88d1..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/traces/traceId/endpoint.py +++ /dev/null @@ -1,14 +0,0 @@ -from opendc.models.trace import Trace -from opendc.util.rest import Response - - -def GET(request): - """Get this Trace.""" - - request.check_required_parameters(path={'traceId': 'string'}) - - trace = Trace.from_id(request.params_path['traceId']) - - trace.check_exists() - - return Response(200, 'Successfully retrieved trace.', trace.obj) diff --git a/opendc-web/opendc-web-api/opendc/api/v2/traces/traceId/test_endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/traces/traceId/test_endpoint.py deleted file mode 100644 index 0c51538b..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/traces/traceId/test_endpoint.py +++ /dev/null @@ -1,15 +0,0 @@ -from opendc.util.database import DB - -test_id = 24 * '1' - - -def test_get_trace_non_existing(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value=None) - assert '404' in client.get(f'/v2/traces/{test_id}').status - - -def test_get_trace(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value={'name': 'test trace'}) - res = client.get(f'/v2/traces/{test_id}') - assert 'name' in res.json['content'] - assert '200' in res.status diff --git a/opendc-web/opendc-web-api/opendc/api/v2/users/__init__.py b/opendc-web/opendc-web-api/opendc/api/v2/users/__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/users/__init__.py +++ /dev/null diff --git a/opendc-web/opendc-web-api/opendc/api/v2/users/endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/users/endpoint.py deleted file mode 100644 index 0dcf2463..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/users/endpoint.py +++ /dev/null @@ -1,30 +0,0 @@ -from opendc.models.user import User -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, '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, 'Successfully created user.', user.obj) diff --git a/opendc-web/opendc-web-api/opendc/api/v2/users/test_endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/users/test_endpoint.py deleted file mode 100644 index 13b63b20..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/users/test_endpoint.py +++ /dev/null @@ -1,34 +0,0 @@ -from opendc.util.database import DB - - -def test_get_user_by_email_missing_parameter(client): - assert '400' in client.get('/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('/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('/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('/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('/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('/v2/users', json={'user': {'email': 'test@test.com'}}) - assert 'email' in res.json['content'] - assert '200' in res.status diff --git a/opendc-web/opendc-web-api/opendc/api/v2/users/userId/__init__.py b/opendc-web/opendc-web-api/opendc/api/v2/users/userId/__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/users/userId/__init__.py +++ /dev/null diff --git a/opendc-web/opendc-web-api/opendc/api/v2/users/userId/endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/users/userId/endpoint.py deleted file mode 100644 index be3462c0..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/users/userId/endpoint.py +++ /dev/null @@ -1,59 +0,0 @@ -from opendc.models.project import Project -from opendc.models.user import User -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, '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, '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) - - for authorization in user.obj['authorizations']: - if authorization['authorizationLevel'] != 'OWN': - continue - - project = Project.from_id(authorization['projectId']) - project.delete() - - old_object = user.delete() - - return Response(200, 'Successfully deleted user.', old_object) diff --git a/opendc-web/opendc-web-api/opendc/api/v2/users/userId/test_endpoint.py b/opendc-web/opendc-web-api/opendc/api/v2/users/userId/test_endpoint.py deleted file mode 100644 index 4085642f..00000000 --- a/opendc-web/opendc-web-api/opendc/api/v2/users/userId/test_endpoint.py +++ /dev/null @@ -1,56 +0,0 @@ -from opendc.util.database import DB - -test_id = 24 * '1' - - -def test_get_user_non_existing(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value=None) - assert '404' in client.get(f'/v2/users/{test_id}').status - - -def test_get_user(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value={'email': 'test@test.com'}) - res = client.get(f'/v2/users/{test_id}') - assert 'email' in res.json['content'] - assert '200' in res.status - - -def test_update_user_missing_parameter(client): - assert '400' in client.put(f'/v2/users/{test_id}').status - - -def test_update_user_non_existing(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value=None) - assert '404' in client.put(f'/v2/users/{test_id}', json={'user': {'givenName': 'A', 'familyName': 'B'}}).status - - -def test_update_user_different_user(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value={'_id': test_id, 'googleId': 'other_test'}) - assert '403' in client.put(f'/v2/users/{test_id}', json={'user': {'givenName': 'A', 'familyName': 'B'}}).status - - -def test_update_user(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value={'_id': test_id, 'googleId': 'test'}) - mocker.patch.object(DB, 'update', return_value={'givenName': 'A', 'familyName': 'B'}) - res = client.put(f'/v2/users/{test_id}', 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(f'/v2/users/{test_id}').status - - -def test_delete_user_different_user(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value={'_id': test_id, 'googleId': 'other_test'}) - assert '403' in client.delete(f'/v2/users/{test_id}').status - - -def test_delete_user(client, mocker): - mocker.patch.object(DB, 'fetch_one', return_value={'_id': test_id, 'googleId': 'test', 'authorizations': []}) - mocker.patch.object(DB, 'delete_one', return_value={'googleId': 'test'}) - res = client.delete(f'/v2/users/{test_id}', ) - - assert 'googleId' in res.json['content'] - assert '200' in res.status diff --git a/opendc-web/opendc-web-api/opendc/auth.py b/opendc-web/opendc-web-api/opendc/auth.py new file mode 100644 index 00000000..d5da6ee5 --- /dev/null +++ b/opendc-web/opendc-web-api/opendc/auth.py @@ -0,0 +1,236 @@ +# Copyright (c) 2021 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. + +import json +import time + +import urllib3 +from flask import request +from jose import jwt, JWTError + + +def get_token(): + """ + Obtain the Access Token from the Authorization Header + """ + auth = request.headers.get("Authorization", None) + if not auth: + raise AuthError({ + "code": "authorization_header_missing", + "description": "Authorization header is expected" + }, 401) + + parts = auth.split() + + if parts[0].lower() != "bearer": + raise AuthError({"code": "invalid_header", "description": "Authorization header must start with Bearer"}, 401) + if len(parts) == 1: + raise AuthError({"code": "invalid_header", "description": "Token not found"}, 401) + if len(parts) > 2: + raise AuthError({"code": "invalid_header", "description": "Authorization header must be" " Bearer token"}, 401) + + token = parts[1] + return token + + +class AuthError(Exception): + """ + This error is thrown when the request failed to authorize. + """ + def __init__(self, error, status_code): + Exception.__init__(self, error) + self.error = error + self.status_code = status_code + + +class AuthContext: + """ + This class handles the authorization of requests. + """ + def __init__(self, alg, issuer, audience): + self._alg = alg + self._issuer = issuer + self._audience = audience + + def validate(self, token): + """ + Validate the specified JWT token. + :param token: The authorization token specified by the user. + :return: The token payload on success, otherwise `AuthError`. + """ + try: + header = jwt.get_unverified_header(token) + except JWTError as e: + raise AuthError({"code": "invalid_token", "message": str(e)}, 401) + + alg = header.get('alg', None) + if alg != self._alg.algorithm: + raise AuthError( + { + "code": + "invalid_header", + "message": + f"Signature algorithm of {alg} is not supported. Expected the ID token " + f"to be signed with {self._alg.algorithm}" + }, 401) + + kid = header.get('kid', None) + try: + secret_or_certificate = self._alg.get_key(key_id=kid) + except TokenValidationError as e: + raise AuthError({"code": "invalid_header", "message": str(e)}, 401) + try: + payload = jwt.decode(token, + key=secret_or_certificate, + algorithms=[self._alg.algorithm], + audience=self._audience, + issuer=self._issuer) + return payload + except jwt.ExpiredSignatureError: + raise AuthError({"code": "token_expired", "message": "Token is expired"}, 401) + except jwt.JWTClaimsError: + raise AuthError( + { + "code": "invalid_claims", + "message": "Incorrect claims, please check the audience and issuer" + }, 401) + except Exception as e: + print(e) + raise AuthError({"code": "invalid_header", "message": "Unable to parse authentication token."}, 401) + + +class SymmetricJwtAlgorithm: + """Verifier for HMAC signatures, which rely on shared secrets. + Args: + shared_secret (str): The shared secret used to decode the token. + algorithm (str, optional): The expected signing algorithm. Defaults to "HS256". + """ + def __init__(self, shared_secret, algorithm="HS256"): + self.algorithm = algorithm + self._shared_secret = shared_secret + + # pylint: disable=W0613 + def get_key(self, key_id=None): + """ + Obtain the key for this algorithm. + :param key_id: The identifier of the key. + :return: The JWK key. + """ + return self._shared_secret + + +class AsymmetricJwtAlgorithm: + """Verifier for RSA signatures, which rely on public key certificates. + Args: + jwks_url (str): The url where the JWK set is located. + algorithm (str, optional): The expected signing algorithm. Defaults to "RS256". + """ + def __init__(self, jwks_url, algorithm="RS256"): + self.algorithm = algorithm + self._fetcher = JwksFetcher(jwks_url) + + def get_key(self, key_id=None): + """ + Obtain the key for this algorithm. + :param key_id: The identifier of the key. + :return: The JWK key. + """ + return self._fetcher.get_key(key_id) + + +class TokenValidationError(Exception): + """ + Error thrown when the token cannot be validated + """ + + +class JwksFetcher: + """Class that fetches and holds a JSON web key set. + This class makes use of an in-memory cache. For it to work properly, define this instance once and re-use it. + Args: + jwks_url (str): The url where the JWK set is located. + cache_ttl (str, optional): The lifetime of the JWK set cache in seconds. Defaults to 600 seconds. + """ + CACHE_TTL = 600 # 10 min cache lifetime + + def __init__(self, jwks_url, cache_ttl=CACHE_TTL): + self._jwks_url = jwks_url + self._http = urllib3.PoolManager() + self._cache_value = {} + self._cache_date = 0 + self._cache_ttl = cache_ttl + self._cache_is_fresh = False + + def _fetch_jwks(self, force=False): + """Attempts to obtain the JWK set from the cache, as long as it's still valid. + When not, it will perform a network request to the jwks_url to obtain a fresh result + and update the cache value with it. + Args: + force (bool, optional): whether to ignore the cache and force a network request or not. Defaults to False. + """ + has_expired = self._cache_date + self._cache_ttl < time.time() + + if not force and not has_expired: + # Return from cache + self._cache_is_fresh = False + return self._cache_value + + # Invalidate cache and fetch fresh data + self._cache_value = {} + response = self._http.request('GET', self._jwks_url) + + if response.status == 200: + # Update cache + jwks = json.loads(response.data.decode('utf-8')) + self._cache_value = self._parse_jwks(jwks) + self._cache_is_fresh = True + self._cache_date = time.time() + return self._cache_value + + @staticmethod + def _parse_jwks(jwks): + """Converts a JWK string representation into a binary certificate in PEM format. + """ + keys = {} + + for key in jwks['keys']: + keys[key["kid"]] = key + return keys + + def get_key(self, key_id): + """Obtains the JWK associated with the given key id. + Args: + key_id (str): The id of the key to fetch. + Returns: + the JWK associated with the given key id. + + Raises: + TokenValidationError: when a key with that id cannot be found + """ + keys = self._fetch_jwks() + + if keys and key_id in keys: + return keys[key_id] + + if not self._cache_is_fresh: + keys = self._fetch_jwks(force=True) + if keys and key_id in keys: + return keys[key_id] + raise TokenValidationError(f"RSA Public Key with ID {key_id} was not found.") diff --git a/opendc-web/opendc-web-api/opendc/util/database.py b/opendc-web/opendc-web-api/opendc/database.py index dd26533d..dd6367f2 100644 --- a/opendc-web/opendc-web-api/opendc/util/database.py +++ b/opendc-web/opendc-web-api/opendc/database.py @@ -1,7 +1,26 @@ +# Copyright (c) 2021 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. + import urllib.parse -from datetime import datetime -from pymongo import MongoClient +from pymongo import MongoClient, ReturnDocument DATETIME_STRING_FORMAT = '%Y-%m-%dT%H:%M:%S' CONNECTION_POOL = None @@ -9,19 +28,27 @@ CONNECTION_POOL = None class Database: """Object holding functionality for database access.""" - def __init__(self): - self.opendc_db = None - - def initialize_database(self, user, password, database, host): + def __init__(self, db=None): """Initializes the database connection.""" + self.opendc_db = db + @classmethod + def from_credentials(cls, user, password, database, host): + """ + Construct a database instance from the specified credentials. + :param user: The username to connect with. + :param password: The password to connect with. + :param database: The database name to connect to. + :param host: The host to connect to. + :return: The database instance. + """ user = urllib.parse.quote_plus(user) password = urllib.parse.quote_plus(password) 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 + return cls(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 @@ -49,6 +76,12 @@ class Database: """Updates an existing object.""" return getattr(self.opendc_db, collection).update({'_id': _id}, obj) + def fetch_and_update(self, query, update, collection): + """Updates an existing object.""" + return getattr(self.opendc_db, collection).find_one_and_update(query, + update, + return_document=ReturnDocument.AFTER) + def delete_one(self, query, collection): """Deletes one object matching the given query. @@ -62,16 +95,3 @@ class Database: The query needs to be in json format, i.e.: `{'name': prefab_name}`. """ getattr(self.opendc_db, collection).delete_many(query) - - @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/opendc-web/opendc-web-api/opendc/exts.py b/opendc-web/opendc-web-api/opendc/exts.py new file mode 100644 index 00000000..3ee8babb --- /dev/null +++ b/opendc-web/opendc-web-api/opendc/exts.py @@ -0,0 +1,91 @@ +import os +from functools import wraps + +from flask import g, _request_ctx_stack +from jose import jwt +from werkzeug.local import LocalProxy + +from opendc.database import Database +from opendc.auth import AuthContext, AsymmetricJwtAlgorithm, get_token, AuthError + + +def get_db(): + """ + Return the configured database instance for the application. + """ + _db = getattr(g, 'db', None) + if _db is None: + _db = Database.from_credentials(user=os.environ['OPENDC_DB_USERNAME'], + password=os.environ['OPENDC_DB_PASSWORD'], + database=os.environ['OPENDC_DB'], + host=os.environ.get('OPENDC_DB_HOST', 'localhost')) + g.db = _db + return _db + + +db = LocalProxy(get_db) + + +def get_auth_context(): + """ + Return the configured auth context for the application. + """ + _auth_context = getattr(g, 'auth_context', None) + if _auth_context is None: + _auth_context = AuthContext( + alg=AsymmetricJwtAlgorithm(jwks_url=f"https://{os.environ['AUTH0_DOMAIN']}/.well-known/jwks.json"), + issuer=f"https://{os.environ['AUTH0_DOMAIN']}/", + audience=os.environ['AUTH0_AUDIENCE']) + g.auth_context = _auth_context + return _auth_context + + +auth_context = LocalProxy(get_auth_context) + + +def requires_auth(f): + """Decorator to determine if the Access Token is valid. + """ + @wraps(f) + def decorated(*args, **kwargs): + token = get_token() + payload = auth_context.validate(token) + _request_ctx_stack.top.current_user = payload + return f(*args, **kwargs) + + return decorated + + +current_user = LocalProxy(lambda: getattr(_request_ctx_stack.top, 'current_user', None)) + + +def has_scope(required_scope): + """Determines if the required scope is present in the Access Token + Args: + required_scope (str): The scope required to access the resource + """ + token = get_token() + unverified_claims = jwt.get_unverified_claims(token) + if unverified_claims.get("scope"): + token_scopes = unverified_claims["scope"].split() + for token_scope in token_scopes: + if token_scope == required_scope: + return True + return False + + +def requires_scope(required_scope): + """Determines if the required scope is present in the Access Token + Args: + required_scope (str): The scope required to access the resource + """ + def decorator(f): + @wraps(f) + def decorated(*args, **kwargs): + if not has_scope(required_scope): + raise AuthError({"code": "Unauthorized", "description": "You don't have access to this resource"}, 403) + return f(*args, **kwargs) + + return decorated + + return decorator diff --git a/opendc-web/opendc-web-api/opendc/models/model.py b/opendc-web/opendc-web-api/opendc/models/model.py index f9dfc9ad..28299453 100644 --- a/opendc-web/opendc-web-api/opendc/models/model.py +++ b/opendc-web/opendc-web-api/opendc/models/model.py @@ -1,8 +1,7 @@ from bson.objectid import ObjectId +from werkzeug.exceptions import NotFound -from opendc.util.database import DB -from opendc.util.exceptions import ClientError -from opendc.util.rest import Response +from opendc.exts import db class Model: @@ -15,15 +14,13 @@ class Model: """Fetches the document with given ID from the collection.""" if isinstance(_id, str) and len(_id) == 24: _id = ObjectId(_id) - elif not isinstance(_id, ObjectId): - return cls(None) - return cls(DB.fetch_one({'_id': _id}, cls.collection_name)) + return cls(db.fetch_one({'_id': _id}, cls.collection_name)) @classmethod def get_all(cls): """Fetches all documents from the collection.""" - return cls(DB.fetch_all({}, cls.collection_name)) + return cls(db.fetch_all({}, cls.collection_name)) def __init__(self, obj): self.obj = obj @@ -35,7 +32,7 @@ class Model: def check_exists(self): """Raises an error if the enclosed object does not exist.""" if self.obj is None: - raise ClientError(Response(404, 'Not found.')) + raise NotFound('Entity not found.') def set_property(self, key, value): """Sets the given property on the enclosed object, with support for simple nested access.""" @@ -48,11 +45,11 @@ class Model: def insert(self): """Inserts the enclosed object and generates a UUID for it.""" self.obj['_id'] = ObjectId() - DB.insert(self.obj, self.collection_name) + db.insert(self.obj, self.collection_name) def update(self): """Updates the enclosed object and updates the internal reference to the newly inserted object.""" - DB.update(self.get_id(), self.obj, self.collection_name) + db.update(self.get_id(), self.obj, self.collection_name) def delete(self): """Deletes the enclosed object in the database, if it existed.""" @@ -60,5 +57,5 @@ class Model: return None old_object = self.obj.copy() - DB.delete_one({'_id': self.get_id()}, self.collection_name) + db.delete_one({'_id': self.get_id()}, self.collection_name) return old_object diff --git a/opendc-web/opendc-web-api/opendc/models/portfolio.py b/opendc-web/opendc-web-api/opendc/models/portfolio.py index 32961b63..eb016947 100644 --- a/opendc-web/opendc-web-api/opendc/models/portfolio.py +++ b/opendc-web/opendc-web-api/opendc/models/portfolio.py @@ -1,7 +1,28 @@ +from bson import ObjectId +from marshmallow import Schema, fields + +from opendc.exts import db +from opendc.models.project import Project 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 TargetSchema(Schema): + """ + Schema representing a target. + """ + enabledMetrics = fields.List(fields.String()) + repeatsPerScenario = fields.Integer(required=True) + + +class PortfolioSchema(Schema): + """ + Schema representing a portfolio. + """ + _id = fields.String(dump_only=True) + projectId = fields.String() + name = fields.String(required=True) + scenarioIds = fields.List(fields.String()) + targets = fields.Nested(TargetSchema) class Portfolio(Model): @@ -9,16 +30,18 @@ class Portfolio(Model): collection_name = 'portfolios' - def check_user_access(self, google_id, edit_access): - """Raises an error if the user with given [google_id] has insufficient access. + def check_user_access(self, user_id, edit_access): + """Raises an error if the user with given [user_id] has insufficient access. Checks access on the parent project. - :param google_id: The Google ID of the user. + :param user_id: The User ID of the user. :param edit_access: True when edit access should be checked, otherwise view access. """ - user = User.from_google_id(google_id) - authorizations = list( - filter(lambda x: str(x['projectId']) == str(self.obj['projectId']), user.obj['authorizations'])) - if len(authorizations) == 0 or (edit_access and authorizations[0]['authorizationLevel'] == 'VIEW'): - raise ClientError(Response(403, 'Forbidden from retrieving/editing portfolio.')) + project = Project.from_id(self.obj['projectId']) + project.check_user_access(user_id, edit_access) + + @classmethod + def get_for_project(cls, project_id): + """Get all portfolios for the specified project id.""" + return db.fetch_all({'projectId': ObjectId(project_id)}, cls.collection_name) diff --git a/opendc-web/opendc-web-api/opendc/models/prefab.py b/opendc-web/opendc-web-api/opendc/models/prefab.py index edf1d4c4..5e4b81dc 100644 --- a/opendc-web/opendc-web-api/opendc/models/prefab.py +++ b/opendc-web/opendc-web-api/opendc/models/prefab.py @@ -1,28 +1,31 @@ +from marshmallow import Schema, fields +from werkzeug.exceptions import Forbidden + +from opendc.models.topology import ObjectSchema 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 PrefabSchema(Schema): + """ + Schema for a Prefab. + """ + _id = fields.String(dump_only=True) + authorId = fields.String(dump_only=True) + name = fields.String(required=True) + datetimeCreated = fields.DateTime() + datetimeLastEdited = fields.DateTime() + rack = fields.Nested(ObjectSchema) class Prefab(Model): - """Model representing a Project.""" + """Model representing a Prefab.""" collection_name = 'prefabs' - def check_user_access(self, google_id): - """Raises an error if the user with given [google_id] has insufficient access to view this prefab. + def check_user_access(self, user_id): + """Raises an error if the user with given [user_id] has insufficient access to view this prefab. - :param google_id: The Google ID of the user. + :param user_id: The user ID of the user. """ - user = User.from_google_id(google_id) - - # TODO(Jacob) add special handling for OpenDC-provided prefabs - - #try: - - print(self.obj) - if self.obj['authorId'] != user.get_id() and self.obj['visibility'] == "private": - raise ClientError(Response(403, "Forbidden from retrieving prefab.")) - #except KeyError: - # OpenDC-authored objects don't necessarily have an authorId - # return + if self.obj['authorId'] != user_id and self.obj['visibility'] == "private": + raise Forbidden("Forbidden from retrieving prefab.") diff --git a/opendc-web/opendc-web-api/opendc/models/project.py b/opendc-web/opendc-web-api/opendc/models/project.py index b57e9f77..f2b3b564 100644 --- a/opendc-web/opendc-web-api/opendc/models/project.py +++ b/opendc-web/opendc-web-api/opendc/models/project.py @@ -1,8 +1,29 @@ +from marshmallow import Schema, fields, validate +from werkzeug.exceptions import Forbidden + from opendc.models.model import Model -from opendc.models.user import User -from opendc.util.database import DB -from opendc.util.exceptions import ClientError -from opendc.util.rest import Response +from opendc.exts import db + + +class ProjectAuthorizations(Schema): + """ + Schema representing a project authorization. + """ + userId = fields.String(required=True) + level = fields.String(required=True, validate=validate.OneOf(["VIEW", "EDIT", "OWN"])) + + +class ProjectSchema(Schema): + """ + Schema representing a Project. + """ + _id = fields.String(dump_only=True) + name = fields.String(required=True) + datetimeCreated = fields.DateTime() + datetimeLastEdited = fields.DateTime() + topologyIds = fields.List(fields.String()) + portfolioIds = fields.List(fields.String()) + authorizations = fields.List(fields.Nested(ProjectAuthorizations)) class Project(Model): @@ -10,22 +31,18 @@ class Project(Model): collection_name = 'projects' - def check_user_access(self, google_id, edit_access): - """Raises an error if the user with given [google_id] has insufficient access. + def check_user_access(self, user_id, edit_access): + """Raises an error if the user with given [user_id] has insufficient access. - :param google_id: The Google ID of the user. + :param user_id: The User ID of the user. :param edit_access: True when edit access should be checked, otherwise view access. """ - user = User.from_google_id(google_id) - authorizations = list(filter(lambda x: str(x['projectId']) == str(self.get_id()), - user.obj['authorizations'])) - if len(authorizations) == 0 or (edit_access and authorizations[0]['authorizationLevel'] == 'VIEW'): - raise ClientError(Response(403, "Forbidden from retrieving project.")) - - def get_all_authorizations(self): - """Get all user IDs having access to this project.""" - return [ - str(user['_id']) for user in DB.fetch_all({'authorizations': { - 'projectId': self.obj['_id'] - }}, User.collection_name) - ] + for authorization in self.obj['authorizations']: + if user_id == authorization['userId'] and authorization['level'] != 'VIEW' or not edit_access: + return + raise Forbidden("Forbidden from retrieving project.") + + @classmethod + def get_for_user(cls, user_id): + """Get all projects for the specified user id.""" + return db.fetch_all({'authorizations.userId': user_id}, Project.collection_name) diff --git a/opendc-web/opendc-web-api/opendc/models/scenario.py b/opendc-web/opendc-web-api/opendc/models/scenario.py index 8d53e408..47771e06 100644 --- a/opendc-web/opendc-web-api/opendc/models/scenario.py +++ b/opendc-web/opendc-web-api/opendc/models/scenario.py @@ -1,8 +1,56 @@ +from datetime import datetime + +from bson import ObjectId +from marshmallow import Schema, fields + +from opendc.exts import db from opendc.models.model import Model from opendc.models.portfolio import Portfolio -from opendc.models.user import User -from opendc.util.exceptions import ClientError -from opendc.util.rest import Response + + +class SimulationSchema(Schema): + """ + Simulation details. + """ + state = fields.String() + + +class TraceSchema(Schema): + """ + Schema for specifying the trace of a scenario. + """ + traceId = fields.String() + loadSamplingFraction = fields.Float() + + +class TopologySchema(Schema): + """ + Schema for topology specification for a scenario. + """ + topologyId = fields.String() + + +class OperationalSchema(Schema): + """ + Schema for the operational phenomena for a scenario. + """ + failuresEnabled = fields.Boolean() + performanceInterferenceEnabled = fields.Boolean() + schedulerName = fields.String() + + +class ScenarioSchema(Schema): + """ + Schema representing a scenario. + """ + _id = fields.String(dump_only=True) + portfolioId = fields.String() + name = fields.String(required=True) + trace = fields.Nested(TraceSchema) + topology = fields.Nested(TopologySchema) + operational = fields.Nested(OperationalSchema) + simulation = fields.Nested(SimulationSchema, dump_only=True) + results = fields.Dict(dump_only=True) class Scenario(Model): @@ -10,17 +58,36 @@ class Scenario(Model): collection_name = 'scenarios' - def check_user_access(self, google_id, edit_access): - """Raises an error if the user with given [google_id] has insufficient access. + def check_user_access(self, user_id, edit_access): + """Raises an error if the user with given [user_id] has insufficient access. Checks access on the parent project. - :param google_id: The Google ID of the user. + :param user_id: The User ID of the user. :param edit_access: True when edit access should be checked, otherwise view access. """ portfolio = Portfolio.from_id(self.obj['portfolioId']) - user = User.from_google_id(google_id) - authorizations = list( - filter(lambda x: str(x['projectId']) == str(portfolio.obj['projectId']), user.obj['authorizations'])) - if len(authorizations) == 0 or (edit_access and authorizations[0]['authorizationLevel'] == 'VIEW'): - raise ClientError(Response(403, 'Forbidden from retrieving/editing scenario.')) + portfolio.check_user_access(user_id, edit_access) + + @classmethod + def get_jobs(cls): + """Obtain the scenarios that have been queued. + """ + return cls(db.fetch_all({'simulation.state': 'QUEUED'}, cls.collection_name)) + + @classmethod + def get_for_portfolio(cls, portfolio_id): + """Get all scenarios for the specified portfolio id.""" + return db.fetch_all({'portfolioId': ObjectId(portfolio_id)}, cls.collection_name) + + def update_state(self, new_state, results=None): + """Atomically update the state of the Scenario. + """ + update = {'$set': {'simulation.state': new_state, 'simulation.heartbeat': datetime.now()}} + if results: + update['$set']['results'] = results + return db.fetch_and_update( + query={'_id': self.obj['_id'], 'simulation.state': self.obj['simulation']['state']}, + update=update, + collection=self.collection_name + ) diff --git a/opendc-web/opendc-web-api/opendc/models/topology.py b/opendc-web/opendc-web-api/opendc/models/topology.py index cb4c4bab..44994818 100644 --- a/opendc-web/opendc-web-api/opendc/models/topology.py +++ b/opendc-web/opendc-web-api/opendc/models/topology.py @@ -1,7 +1,89 @@ +from bson import ObjectId +from marshmallow import Schema, fields + +from opendc.exts import db +from opendc.models.project import Project 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 MemorySchema(Schema): + """ + Schema representing a memory unit. + """ + _id = fields.String() + name = fields.String() + speedMbPerS = fields.Integer() + sizeMb = fields.Integer() + energyConsumptionW = fields.Integer() + + +class PuSchema(Schema): + """ + Schema representing a processing unit. + """ + _id = fields.String() + name = fields.String() + clockRateMhz = fields.Integer() + numberOfCores = fields.Integer() + energyConsumptionW = fields.Integer() + + +class MachineSchema(Schema): + """ + Schema representing a machine. + """ + _id = fields.String() + position = fields.Integer() + cpus = fields.List(fields.Nested(PuSchema)) + gpus = fields.List(fields.Nested(PuSchema)) + memories = fields.List(fields.Nested(MemorySchema)) + storages = fields.List(fields.Nested(MemorySchema)) + rackId = fields.String() + + +class ObjectSchema(Schema): + """ + Schema representing a room object. + """ + _id = fields.String() + name = fields.String() + capacity = fields.Integer() + powerCapacityW = fields.Integer() + machines = fields.List(fields.Nested(MachineSchema)) + tileId = fields.String() + + +class TileSchema(Schema): + """ + Schema representing a room tile. + """ + _id = fields.String() + topologyId = fields.String() + positionX = fields.Integer() + positionY = fields.Integer() + rack = fields.Nested(ObjectSchema) + roomId = fields.String() + + +class RoomSchema(Schema): + """ + Schema representing a room. + """ + _id = fields.String() + name = fields.String(required=True) + topologyId = fields.String() + tiles = fields.List(fields.Nested(TileSchema), required=True) + + +class TopologySchema(Schema): + """ + Schema representing a datacenter topology. + """ + _id = fields.String(dump_only=True) + projectId = fields.String() + name = fields.String(required=True) + rooms = fields.List(fields.Nested(RoomSchema), required=True) + datetimeLastEdited = fields.DateTime() class Topology(Model): @@ -9,19 +91,18 @@ class Topology(Model): collection_name = 'topologies' - def check_user_access(self, google_id, edit_access): - """Raises an error if the user with given [google_id] has insufficient access. + def check_user_access(self, user_id, edit_access): + """Raises an error if the user with given [user_id] has insufficient access. Checks access on the parent project. - :param google_id: The Google ID of the user. + :param user_id: The User ID of the user. :param edit_access: True when edit access should be checked, otherwise view access. """ - user = User.from_google_id(google_id) - if 'projectId' not in self.obj: - raise ClientError(Response(400, 'Missing projectId in topology.')) - - authorizations = list( - filter(lambda x: str(x['projectId']) == str(self.obj['projectId']), user.obj['authorizations'])) - if len(authorizations) == 0 or (edit_access and authorizations[0]['authorizationLevel'] == 'VIEW'): - raise ClientError(Response(403, 'Forbidden from retrieving topology.')) + project = Project.from_id(self.obj['projectId']) + project.check_user_access(user_id, edit_access) + + @classmethod + def get_for_project(cls, project_id): + """Get all topologies for the specified project id.""" + return db.fetch_all({'projectId': ObjectId(project_id)}, cls.collection_name) diff --git a/opendc-web/opendc-web-api/opendc/models/trace.py b/opendc-web/opendc-web-api/opendc/models/trace.py index 2f6e4926..69287f29 100644 --- a/opendc-web/opendc-web-api/opendc/models/trace.py +++ b/opendc-web/opendc-web-api/opendc/models/trace.py @@ -1,6 +1,15 @@ +from marshmallow import Schema, fields + from opendc.models.model import Model +class TraceSchema(Schema): + """Schema for a Trace.""" + _id = fields.String(dump_only=True) + name = fields.String() + type = fields.String() + + class Trace(Model): """Model representing a Trace.""" diff --git a/opendc-web/opendc-web-api/opendc/models/user.py b/opendc-web/opendc-web-api/opendc/models/user.py deleted file mode 100644 index 8e8ff945..00000000 --- a/opendc-web/opendc-web-api/opendc/models/user.py +++ /dev/null @@ -1,36 +0,0 @@ -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): - """Model representing a User.""" - - collection_name = 'users' - - @classmethod - def from_email(cls, email): - """Fetches the user with given email from the collection.""" - return User(DB.fetch_one({'email': email}, User.collection_name)) - - @classmethod - def from_google_id(cls, google_id): - """Fetches the user with given Google ID from the collection.""" - return User(DB.fetch_one({'googleId': google_id}, User.collection_name)) - - def check_correct_user(self, request_google_id): - """Raises an error if a user tries to modify another user. - - :param 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): - """Checks if the user already exists in the database.""" - - 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/opendc-web/opendc-web-api/opendc/util.py b/opendc-web/opendc-web-api/opendc/util.py new file mode 100644 index 00000000..e7dc07a4 --- /dev/null +++ b/opendc-web/opendc-web-api/opendc/util.py @@ -0,0 +1,32 @@ +# Copyright (c) 2021 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. + +import flask +from bson.objectid import ObjectId + + +class JSONEncoder(flask.json.JSONEncoder): + """ + A customized JSON encoder to handle unsupported types. + """ + def default(self, o): + if isinstance(o, ObjectId): + return str(o) + return flask.json.JSONEncoder.default(self, o) diff --git a/opendc-web/opendc-web-api/opendc/util/__init__.py b/opendc-web/opendc-web-api/opendc/util/__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/opendc-web/opendc-web-api/opendc/util/__init__.py +++ /dev/null diff --git a/opendc-web/opendc-web-api/opendc/util/exceptions.py b/opendc-web/opendc-web-api/opendc/util/exceptions.py deleted file mode 100644 index 7724a407..00000000 --- a/opendc-web/opendc-web-api/opendc/util/exceptions.py +++ /dev/null @@ -1,64 +0,0 @@ -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/opendc-web/opendc-web-api/opendc/util/json.py b/opendc-web/opendc-web-api/opendc/util/json.py deleted file mode 100644 index 2ef4f965..00000000 --- a/opendc-web/opendc-web-api/opendc/util/json.py +++ /dev/null @@ -1,12 +0,0 @@ -import flask -from bson.objectid import ObjectId - - -class JSONEncoder(flask.json.JSONEncoder): - """ - A customized JSON encoder to handle unsupported types. - """ - def default(self, o): - if isinstance(o, ObjectId): - return str(o) - return flask.json.JSONEncoder.default(self, o) diff --git a/opendc-web/opendc-web-api/opendc/util/parameter_checker.py b/opendc-web/opendc-web-api/opendc/util/parameter_checker.py deleted file mode 100644 index 14dd1dc0..00000000 --- a/opendc-web/opendc-web-api/opendc/util/parameter_checker.py +++ /dev/null @@ -1,85 +0,0 @@ -from opendc.util import exceptions -from opendc.util.database import Database - - -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) - - type_pairs = [ - ('int', (int,)), - ('float', (float, int)), - ('bool', (bool,)), - ('string', (str, int)), - ('list', (list,)), - ] - - for str_type, actual_types in type_pairs: - if param_required == str_type and all(not isinstance(param_actual, t) - for t in actual_types): - return '{}.{}'.format(parent, param_name) - - return None - - -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): - """Check 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/opendc-web/opendc-web-api/opendc/util/path_parser.py b/opendc-web/opendc-web-api/opendc/util/path_parser.py deleted file mode 100644 index c8452f20..00000000 --- a/opendc-web/opendc-web-api/opendc/util/path_parser.py +++ /dev/null @@ -1,36 +0,0 @@ -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('{'): - parameters[name.strip('{}')] = value - - return '{}/{}'.format(version, '/'.join(path)), parameters diff --git a/opendc-web/opendc-web-api/opendc/util/rest.py b/opendc-web/opendc-web-api/opendc/util/rest.py deleted file mode 100644 index c9e98295..00000000 --- a/opendc-web/opendc-web-api/opendc/util/rest.py +++ /dev/null @@ -1,141 +0,0 @@ -import importlib -import json -import os - -from oauth2client import client, crypt - -from opendc.util import exceptions, parameter_checker -from opendc.util.exceptions import ClientError - - -class Request: - """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: - """Response to websocket mapping""" - def __init__(self, status_code, status_description, content=None): - """Initialize a new Response.""" - - self.id = 0 - 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, default=str) diff --git a/opendc-web/opendc-web-api/requirements.txt b/opendc-web/opendc-web-api/requirements.txt index 146f1717..45eae12b 100644 --- a/opendc-web/opendc-web-api/requirements.txt +++ b/opendc-web/opendc-web-api/requirements.txt @@ -9,6 +9,8 @@ Flask==1.1.2 Flask-Compress==1.5.0 Flask-Cors==3.0.9 Flask-SocketIO==4.3.1 +flask-swagger-ui==3.36.0 +Flask-Restful==0.3.8 greenlet==0.4.17 httplib2==0.19.0 isort==4.3.21 @@ -16,6 +18,7 @@ itsdangerous==1.1.0 Jinja2==2.11.3 lazy-object-proxy==1.4.3 MarkupSafe==1.1.1 +marshmallow==3.12.1 mccabe==0.6.1 monotonic==1.5 more-itertools==8.6.0 @@ -33,13 +36,12 @@ pytest-cov==2.11.1 pytest-env==0.6.2 pytest-mock==3.2.0 python-dotenv==0.14.0 -python-engineio==3.13.2 -python-socketio==4.6.0 +python-jose==3.2.0 rsa==4.7 sentry-sdk==0.19.2 six==1.15.0 toml==0.10.2 -urllib3==1.26.4 +urllib3==1.26.5 wcwidth==0.2.5 Werkzeug==1.0.1 wrapt==1.12.1 diff --git a/opendc-web/opendc-web-api/static/index.html b/opendc-web/opendc-web-api/static/index.html deleted file mode 100644 index ac78cbfb..00000000 --- a/opendc-web/opendc-web-api/static/index.html +++ /dev/null @@ -1,22 +0,0 @@ -<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/opendc-web/opendc-web-api/static/schema.yml b/opendc-web/opendc-web-api/static/schema.yml new file mode 100644 index 00000000..56cf58e7 --- /dev/null +++ b/opendc-web/opendc-web-api/static/schema.yml @@ -0,0 +1,1631 @@ +openapi: 3.0.0 +info: + version: 2.1.0 + title: OpenDC REST API v2 + description: OpenDC is an open-source datacenter simulator for education, featuring + real-time online collaboration, diverse simulation models, and detailed + performance feedback statistics. + license: + name: MIT + url: https://spdx.org/licenses/MIT + contact: + name: Support + url: https://opendc.org +servers: + - url: https://api.opendc.org/v2 +externalDocs: + description: OpenDC REST API v2 + url: https://api.opendc.com/v2/docs/ +security: + - auth0: + - openid +paths: + /projects: + get: + tags: + - projects + description: List Projects of the active user + responses: + "200": + description: Successfully + content: + "application/json": + schema: + type: object + required: + - data + properties: + data: + type: array + items: + $ref: "#/components/schemas/Project" + "401": + description: Unauthorized. + content: + "application/json": + schema: + $ref: "#/components/schemas/Unauthorized" + post: + tags: + - projects + description: Add a Project. + requestBody: + content: + application/json: + schema: + properties: + name: + type: string + description: The new Project. + required: true + responses: + "200": + description: Successfully added Project. + content: + "application/json": + schema: + type: object + required: + - data + properties: + data: + $ref: "#/components/schemas/Project" + "400": + description: Missing or incorrectly typed parameter. + content: + "application/json": + schema: + $ref: "#/components/schemas/Invalid" + "401": + description: Unauthorized. + content: + "application/json": + schema: + $ref: "#/components/schemas/Unauthorized" + "/projects/{projectId}": + get: + tags: + - projects + description: Get this Project. + parameters: + - name: projectId + in: path + description: Project's ID. + required: true + schema: + type: string + responses: + "200": + description: Successfully retrieved Project. + content: + "application/json": + schema: + type: object + required: + - data + properties: + data: + $ref: "#/components/schemas/Project" + "401": + description: Unauthorized. + content: + "application/json": + schema: + $ref: "#/components/schemas/Unauthorized" + "403": + description: Forbidden from retrieving Project. + content: + "application/json": + schema: + $ref: "#/components/schemas/Forbidden" + "404": + description: Project not found + content: + "application/json": + schema: + $ref: "#/components/schemas/NotFound" + put: + tags: + - projects + description: Update this Project. + parameters: + - name: projectId + in: path + description: Project's ID. + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + properties: + project: + $ref: "#/components/schemas/Project" + description: Project's new properties. + required: true + responses: + "200": + description: Successfully updated Project. + content: + "application/json": + schema: + type: object + required: + - data + properties: + data: + $ref: "#/components/schemas/Project" + "400": + description: Missing or incorrectly typed parameter. + content: + "application/json": + schema: + $ref: "#/components/schemas/Invalid" + "401": + description: Unauthorized. + content: + "application/json": + schema: + $ref: "#/components/schemas/Unauthorized" + "403": + description: Forbidden from updating Project. + content: + "application/json": + schema: + $ref: "#/components/schemas/Forbidden" + "404": + description: Project not found. + content: + "application/json": + schema: + $ref: "#/components/schemas/NotFound" + delete: + tags: + - projects + description: Delete this project. + parameters: + - name: projectId + in: path + description: Project's ID. + required: true + schema: + type: string + responses: + "200": + description: Successfully deleted Project. + content: + "application/json": + schema: + type: object + required: + - data + properties: + data: + $ref: "#/components/schemas/Project" + "401": + description: Unauthorized. + content: + "application/json": + schema: + $ref: "#/components/schemas/Unauthorized" + "403": + description: Forbidden from deleting Project. + content: + "application/json": + schema: + $ref: "#/components/schemas/Forbidden" + "404": + description: Project not found. + content: + "application/json": + schema: + $ref: "#/components/schemas/NotFound" + "/projects/{projectId}/topologies": + get: + tags: + - projects + description: Get Project Topologies. + parameters: + - name: projectId + in: path + description: Project's ID. + required: true + schema: + type: string + responses: + "200": + description: Successfully retrieved Project Topologies. + content: + "application/json": + schema: + type: object + required: + - data + properties: + data: + type: array + items: + $ref: "#/components/schemas/Topology" + "401": + description: Unauthorized. + content: + "application/json": + schema: + $ref: "#/components/schemas/Unauthorized" + "403": + description: Forbidden from retrieving Project. + content: + "application/json": + schema: + $ref: "#/components/schemas/Forbidden" + "404": + description: Project not found. + content: + "application/json": + schema: + $ref: "#/components/schemas/NotFound" + post: + tags: + - projects + description: Add a Topology. + parameters: + - name: projectId + in: path + description: Project's ID. + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + properties: + topology: + $ref: "#/components/schemas/Topology" + description: The new Topology. + required: true + responses: + "200": + description: Successfully added Topology. + content: + "application/json": + schema: + type: object + required: + - data + properties: + data: + $ref: "#/components/schemas/Topology" + "400": + description: Missing or incorrectly typed parameter. + content: + "application/json": + schema: + $ref: "#/components/schemas/Invalid" + "401": + description: Unauthorized. + content: + "application/json": + schema: + $ref: "#/components/schemas/Unauthorized" + "404": + description: Project not found. + content: + "application/json": + schema: + $ref: "#/components/schemas/NotFound" + "/projects/{projectId}/portfolios": + get: + tags: + - projects + description: Get Project Portfolios. + parameters: + - name: projectId + in: path + description: Project's ID. + required: true + schema: + type: string + responses: + "200": + description: Successfully retrieved Project Portfolios. + content: + "application/json": + schema: + type: object + required: + - data + properties: + data: + type: array + items: + $ref: "#/components/schemas/Portfolio" + "401": + description: Unauthorized. + content: + "application/json": + schema: + $ref: "#/components/schemas/Unauthorized" + "403": + description: Forbidden from retrieving Project. + content: + "application/json": + schema: + $ref: "#/components/schemas/Forbidden" + "404": + description: Project not found. + content: + "application/json": + schema: + $ref: "#/components/schemas/NotFound" + post: + tags: + - projects + description: Add a Portfolio. + parameters: + - name: projectId + in: path + description: Project's ID. + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + properties: + topology: + $ref: "#/components/schemas/Portfolio" + description: The new Portfolio. + required: true + responses: + "200": + description: Successfully added Portfolio. + content: + "application/json": + schema: + type: object + required: + - data + properties: + data: + $ref: "#/components/schemas/Portfolio" + "400": + description: Missing or incorrectly typed parameter. + content: + "application/json": + schema: + $ref: "#/components/schemas/Invalid" + "401": + description: Unauthorized. + content: + "application/json": + schema: + $ref: "#/components/schemas/Unauthorized" + "404": + description: Project not found. + content: + "application/json": + schema: + $ref: "#/components/schemas/NotFound" + "/topologies/{topologyId}": + get: + tags: + - topologies + description: Get this Topology. + parameters: + - name: topologyId + in: path + description: Topology's ID. + required: true + schema: + type: string + responses: + "200": + description: Successfully retrieved Topology. + content: + "application/json": + schema: + type: object + required: + - data + properties: + data: + $ref: "#/components/schemas/Topology" + "400": + description: Missing or incorrectly typed parameter. + content: + "application/json": + schema: + $ref: "#/components/schemas/Invalid" + "401": + description: Unauthorized. + content: + "application/json": + schema: + $ref: "#/components/schemas/Unauthorized" + "403": + description: Forbidden from retrieving Topology. + content: + "application/json": + schema: + $ref: "#/components/schemas/Forbidden" + "404": + description: Topology not found. + content: + "application/json": + schema: + $ref: "#/components/schemas/NotFound" + put: + tags: + - topologies + description: Update this Topology's name. + parameters: + - name: topologyId + in: path + description: Topology's ID. + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + properties: + topology: + $ref: "#/components/schemas/Topology" + description: Topology's new properties. + required: true + responses: + "200": + description: Successfully updated Topology. + content: + "application/json": + schema: + type: object + required: + - data + properties: + data: + $ref: "#/components/schemas/Topology" + "400": + description: Missing or incorrectly typed parameter. + content: + "application/json": + schema: + $ref: "#/components/schemas/Invalid" + "401": + description: Unauthorized. + content: + "application/json": + schema: + $ref: "#/components/schemas/Unauthorized" + "403": + description: Forbidden from retrieving Project. + content: + "application/json": + schema: + $ref: "#/components/schemas/Forbidden" + "404": + description: Project not found. + content: + "application/json": + schema: + $ref: "#/components/schemas/NotFound" + delete: + tags: + - topologies + description: Delete this Topology. + parameters: + - name: topologyId + in: path + description: Topology's ID. + required: true + schema: + type: string + responses: + "200": + description: Successfully deleted Topology. + content: + "application/json": + schema: + type: object + required: + - data + properties: + data: + $ref: "#/components/schemas/Topology" + "401": + description: Unauthorized. + content: + "application/json": + schema: + $ref: "#/components/schemas/Unauthorized" + "403": + description: Forbidden from deleting Topology. + content: + "application/json": + schema: + $ref: "#/components/schemas/Forbidden" + "404": + description: Topology not found. + content: + "application/json": + schema: + $ref: "#/components/schemas/NotFound" + "/portfolios/{portfolioId}": + get: + tags: + - portfolios + description: Get this Portfolio. + parameters: + - name: portfolioId + in: path + description: Portfolio's ID. + required: true + schema: + type: string + responses: + "200": + description: Successfully retrieved Portfolio. + content: + "application/json": + schema: + type: object + required: + - data + properties: + data: + $ref: "#/components/schemas/Portfolio" + "400": + description: Missing or incorrectly typed parameter. + content: + "application/json": + schema: + $ref: "#/components/schemas/Invalid" + "401": + description: Unauthorized. + content: + "application/json": + schema: + $ref: "#/components/schemas/Unauthorized" + "403": + description: Forbidden from retrieving Portfolio. + content: + "application/json": + schema: + $ref: "#/components/schemas/Forbidden" + "404": + description: Portfolio not found. + content: + "application/json": + schema: + $ref: "#/components/schemas/NotFound" + put: + tags: + - portfolios + description: Update this Portfolio. + parameters: + - name: portfolioId + in: path + description: Portfolio's ID. + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/Portfolio" + description: Portfolio's new properties. + required: true + responses: + "200": + description: Successfully updated Portfolio. + content: + "application/json": + schema: + type: object + required: + - data + properties: + data: + $ref: "#/components/schemas/Portfolio" + "400": + description: Missing or incorrectly typed parameter. + content: + "application/json": + schema: + $ref: "#/components/schemas/Invalid" + "401": + description: Unauthorized. + content: + "application/json": + schema: + $ref: "#/components/schemas/Unauthorized" + "403": + description: Forbidden from retrieving Portfolio. + content: + "application/json": + schema: + $ref: "#/components/schemas/Forbidden" + "404": + description: Portfolio not found. + content: + "application/json": + schema: + $ref: "#/components/schemas/NotFound" + delete: + tags: + - portfolios + description: Delete this Portfolio. + parameters: + - name: portfolioId + in: path + description: Portfolio's ID. + required: true + schema: + type: string + responses: + "200": + description: Successfully deleted Portfolio. + content: + "application/json": + schema: + type: object + required: + - data + properties: + data: + $ref: "#/components/schemas/Portfolio" + "401": + description: Unauthorized. + content: + "application/json": + schema: + $ref: "#/components/schemas/Unauthorized" + "403": + description: Forbidden from retrieving Portfolio. + content: + "application/json": + schema: + $ref: "#/components/schemas/Forbidden" + "404": + description: Portfolio not found. + content: + "application/json": + schema: + $ref: "#/components/schemas/NotFound" + "/portfolios/{portfolioId}/scenarios": + get: + tags: + - portfolios + description: Get Portfolio Scenarios. + parameters: + - name: portfolioId + in: path + description: Portfolio's ID. + required: true + schema: + type: string + responses: + "200": + description: Successfully retrieved Portfolio Scenarios. + content: + "application/json": + schema: + type: object + required: + - data + properties: + data: + type: array + items: + $ref: "#/components/schemas/Scenario" + "401": + description: Unauthorized. + content: + "application/json": + schema: + $ref: "#/components/schemas/Unauthorized" + "403": + description: Forbidden from retrieving Portfolio. + content: + "application/json": + schema: + $ref: "#/components/schemas/Forbidden" + "404": + description: Portfolio not found. + content: + "application/json": + schema: + $ref: "#/components/schemas/NotFound" + post: + tags: + - portfolios + description: Add a Scenario. + parameters: + - name: portfolioId + in: path + description: Portfolio's ID. + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + properties: + topology: + $ref: "#/components/schemas/Scenario" + description: The new Scenario. + required: true + responses: + "200": + description: Successfully added Scenario. + content: + "application/json": + schema: + type: object + required: + - data + properties: + data: + $ref: "#/components/schemas/Scenario" + "400": + description: Missing or incorrectly typed parameter. + content: + "application/json": + schema: + $ref: "#/components/schemas/Invalid" + "401": + description: Unauthorized. + content: + "application/json": + schema: + $ref: "#/components/schemas/Unauthorized" + "404": + description: Portfolio not found. + content: + "application/json": + schema: + $ref: "#/components/schemas/NotFound" + "/scenarios/{scenarioId}": + get: + tags: + - scenarios + description: Get this Scenario. + parameters: + - name: scenarioId + in: path + description: Scenario's ID. + required: true + schema: + type: string + responses: + "200": + description: Successfully retrieved Scenario. + content: + "application/json": + schema: + type: object + required: + - data + properties: + data: + $ref: "#/components/schemas/Scenario" + "400": + description: Missing or incorrectly typed parameter. + content: + "application/json": + schema: + $ref: "#/components/schemas/Invalid" + "401": + description: Unauthorized. + content: + "application/json": + schema: + $ref: "#/components/schemas/Unauthorized" + "403": + description: Forbidden from retrieving Scenario. + content: + "application/json": + schema: + $ref: "#/components/schemas/Forbidden" + "404": + description: Scenario not found. + content: + "application/json": + schema: + $ref: "#/components/schemas/NotFound" + put: + tags: + - scenarios + description: Update this Scenario's name (other properties are read-only). + parameters: + - name: scenarioId + in: path + description: Scenario's ID. + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/Scenario" + description: Scenario with new name. + required: true + responses: + "200": + description: Successfully updated Scenario. + content: + "application/json": + schema: + type: object + required: + - data + properties: + data: + $ref: "#/components/schemas/Scenario" + "400": + description: Missing or incorrectly typed parameter. + content: + "application/json": + schema: + $ref: "#/components/schemas/Invalid" + "401": + description: Unauthorized. + content: + "application/json": + schema: + $ref: "#/components/schemas/Unauthorized" + "403": + description: Forbidden from retrieving Scenario. + content: + "application/json": + schema: + $ref: "#/components/schemas/Forbidden" + "404": + description: Scenario not found. + content: + "application/json": + schema: + $ref: "#/components/schemas/NotFound" + delete: + tags: + - scenarios + description: Delete this Scenario. + parameters: + - name: scenarioId + in: path + description: Scenario's ID. + required: true + schema: + type: string + responses: + "200": + description: Successfully deleted Scenario. + content: + "application/json": + schema: + type: object + required: + - data + properties: + data: + $ref: "#/components/schemas/Scenario" + "401": + description: Unauthorized. + content: + "application/json": + schema: + $ref: "#/components/schemas/Unauthorized" + "403": + description: Forbidden from retrieving Scenario. + content: + "application/json": + schema: + $ref: "#/components/schemas/Forbidden" + "404": + description: Scenario not found. + content: + "application/json": + schema: + $ref: "#/components/schemas/NotFound" + /schedulers: + get: + tags: + - simulation + description: Get all available Schedulers + responses: + "200": + description: Successfully retrieved Schedulers. + content: + "application/json": + schema: + type: object + required: + - data + properties: + data: + type: array + items: + $ref: "#/components/schemas/Scheduler" + "401": + description: Unauthorized. + content: + "application/json": + schema: + $ref: "#/components/schemas/Unauthorized" + /traces: + get: + tags: + - simulation + description: Get all available Traces + responses: + "200": + description: Successfully retrieved Traces. + content: + "application/json": + schema: + type: object + required: + - data + properties: + data: + type: array + items: + type: object + properties: + _id: + type: string + name: + type: string + "401": + description: Unauthorized. + content: + "application/json": + schema: + $ref: "#/components/schemas/Unauthorized" + "/traces/{traceId}": + get: + tags: + - simulation + description: Get this Trace. + parameters: + - name: traceId + in: path + description: Trace's ID. + required: true + schema: + type: string + responses: + "200": + description: Successfully retrieved Trace. + content: + "application/json": + schema: + type: object + required: + - data + properties: + data: + $ref: "#/components/schemas/Trace" + "401": + description: Unauthorized. + content: + "application/json": + schema: + $ref: "#/components/schemas/Unauthorized" + "404": + description: Trace not found + content: + "application/json": + schema: + $ref: "#/components/schemas/NotFound" + /prefabs: + get: + tags: + - prefabs + description: Get all Prefabs the user has rights to view. + responses: + "200": + description: Successfully retrieved prefabs the user is authorized on. + content: + "application/json": + schema: + type: object + required: + - data + properties: + data: + type: array + items: + $ref: "#/components/schemas/Prefab" + "401": + description: Unauthorized. + content: + "application/json": + schema: + $ref: "#/components/schemas/Unauthorized" + post: + tags: + - prefabs + description: Add a Prefab. + requestBody: + content: + application/json: + schema: + properties: + name: + type: string + description: The new Prefab. + required: true + responses: + "200": + description: Successfully added Prefab. + content: + "application/json": + schema: + type: object + required: + - data + properties: + data: + $ref: "#/components/schemas/Prefab" + "400": + description: Missing or incorrectly typed parameter. + content: + "application/json": + schema: + $ref: "#/components/schemas/Invalid" + "401": + description: Unauthorized. + content: + "application/json": + schema: + $ref: "#/components/schemas/Unauthorized" + "/prefabs/{prefabId}": + get: + tags: + - prefabs + description: Get this Prefab. + parameters: + - name: prefabId + in: path + description: Prefab's ID. + required: true + schema: + type: string + responses: + "200": + description: Successfully retrieved Prefab. + content: + "application/json": + schema: + type: object + required: + - data + properties: + data: + $ref: "#/components/schemas/Prefab" + "401": + description: Unauthorized. + content: + "application/json": + schema: + $ref: "#/components/schemas/Unauthorized" + "403": + description: Forbidden from retrieving Prefab. + content: + "application/json": + schema: + $ref: "#/components/schemas/Forbidden" + "404": + description: Prefab not found. + content: + "application/json": + schema: + $ref: "#/components/schemas/NotFound" + put: + tags: + - prefabs + description: Update this Prefab. + parameters: + - name: prefabId + in: path + description: Prefab's ID. + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + properties: + prefab: + $ref: "#/components/schemas/Prefab" + description: Prefab's new properties. + required: true + responses: + "200": + description: Successfully updated Prefab. + content: + "application/json": + schema: + type: object + required: + - data + properties: + data: + $ref: "#/components/schemas/Prefab" + "400": + description: Missing or incorrectly typed parameter. + content: + "application/json": + schema: + $ref: "#/components/schemas/Invalid" + "401": + description: Unauthorized. + content: + "application/json": + schema: + $ref: "#/components/schemas/Unauthorized" + "403": + description: Forbidden from retrieving Prefab. + content: + "application/json": + schema: + $ref: "#/components/schemas/Forbidden" + "404": + description: Prefab not found. + content: + "application/json": + schema: + $ref: "#/components/schemas/NotFound" + delete: + tags: + - prefabs + description: Delete this prefab. + parameters: + - name: prefabId + in: path + description: Prefab's ID. + required: true + schema: + type: string + responses: + "200": + description: Successfully deleted Prefab. + content: + "application/json": + schema: + type: object + required: + - data + properties: + data: + $ref: "#/components/schemas/Prefab" + "401": + description: Unauthorized. + content: + "application/json": + schema: + $ref: "#/components/schemas/Unauthorized" + "404": + description: Prefab not found. + content: + "application/json": + schema: + $ref: "#/components/schemas/NotFound" + /jobs: + get: + tags: + - jobs + description: Get all available jobs to run. + responses: + "200": + description: Successfully retrieved available jobs. + content: + "application/json": + schema: + type: object + required: + - data + properties: + data: + type: array + items: + $ref: "#/components/schemas/Job" + "401": + description: Unauthorized. + content: + "application/json": + schema: + $ref: "#/components/schemas/Unauthorized" + "/jobs/{jobId}": + get: + tags: + - jobs + description: Get this Job. + parameters: + - name: jobId + in: path + description: Job's ID. + required: true + schema: + type: string + responses: + "200": + description: Successfully retrieved Job. + content: + "application/json": + schema: + type: object + required: + - data + properties: + data: + $ref: "#/components/schemas/Job" + "401": + description: Unauthorized. + content: + "application/json": + schema: + $ref: "#/components/schemas/Unauthorized" + "403": + description: Forbidden from retrieving Job. + content: + "application/json": + schema: + $ref: "#/components/schemas/Forbidden" + "404": + description: Job not found. + content: + "application/json": + schema: + $ref: "#/components/schemas/NotFound" + post: + tags: + - jobs + description: Update this Job. + parameters: + - name: jobId + in: path + description: Job's ID. + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + properties: + job: + $ref: "#/components/schemas/Job" + description: Job's new properties. + required: true + responses: + "200": + description: Successfully updated Job. + content: + "application/json": + schema: + type: object + required: + - data + properties: + data: + $ref: "#/components/schemas/Job" + "400": + description: Missing or incorrectly typed parameter. + content: + "application/json": + schema: + $ref: "#/components/schemas/Invalid" + "401": + description: Unauthorized. + content: + "application/json": + schema: + $ref: "#/components/schemas/Unauthorized" + "403": + description: Forbidden from retrieving Job. + content: + "application/json": + schema: + $ref: "#/components/schemas/Forbidden" + "404": + description: Job not found. + content: + "application/json": + schema: + $ref: "#/components/schemas/NotFound" + "409": + description: State conflict. + content: + "application/json": + schema: + $ref: "#/components/schemas/Invalid" +components: + securitySchemes: + auth0: + type: oauth2 + x-token-validation-url: https://opendc.eu.auth0.com/userinfo + flows: + authorizationCode: + authorizationUrl: https://opendc.eu.auth0.com/authorize + tokenUrl: https://opendc.eu.auth0.com/oauth/token + scopes: + openid: Grants access to user_id + runner: Grants access to runner jobs + schemas: + Unauthorized: + type: object + required: + - message + properties: + message: + type: string + Invalid: + type: object + required: + - message + - errors + properties: + message: + type: string + errors: + type: array + items: + type: string + Forbidden: + type: object + required: + - message + properties: + message: + type: string + NotFound: + type: object + required: + - message + properties: + message: + type: string + Scheduler: + type: object + properties: + name: + type: string + Project: + 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 + portfolioIds: + type: array + items: + type: string + authorizations: + type: array + items: + type: object + properties: + userId: + type: string + level: + type: string + enum: ['OWN', 'EDIT', 'VIEW'] + Topology: + type: object + properties: + _id: + type: string + projectId: + 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: + _id: + type: string + name: + type: string + capacity: + type: integer + powerCapacityW: + type: integer + machines: + type: array + items: + type: object + properties: + _id: + type: string + position: + type: integer + cpus: + type: array + items: + type: object + properties: + _id: + type: string + name: + type: string + clockRateMhz: + type: integer + numberOfCores: + type: integer + energyConsumptionW: + type: integer + gpus: + type: array + items: + type: object + properties: + _id: + type: string + name: + type: string + clockRateMhz: + type: integer + numberOfCores: + type: integer + energyConsumptionW: + type: integer + memories: + type: array + items: + type: object + properties: + _id: + type: string + name: + type: string + speedMbPerS: + type: integer + sizeMb: + type: integer + energyConsumptionW: + type: integer + storages: + type: array + items: + type: object + properties: + _id: + type: string + name: + type: string + speedMbPerS: + type: integer + sizeMb: + type: integer + energyConsumptionW: + type: integer + Portfolio: + type: object + properties: + _id: + type: string + projectId: + type: string + name: + type: string + scenarioIds: + type: array + items: + type: string + targets: + type: object + properties: + enabledMetrics: + type: array + items: + type: string + repeatsPerScenario: + type: integer + Scenario: + type: object + properties: + _id: + type: string + portfolioId: + type: string + name: + type: string + trace: + type: object + properties: + traceId: + type: string + loadSamplingFraction: + type: number + topology: + type: object + properties: + topologyId: + type: string + operational: + type: object + properties: + failuresEnabled: + type: boolean + performanceInterferenceEnabled: + type: boolean + schedulerName: + type: string + Job: + type: object + properties: + _id: + type: string + scenarioId: + type: string + state: + type: string + heartbeat: + type: string + results: + type: object + Trace: + type: object + properties: + _id: + type: string + name: + type: string + path: + type: string + type: + type: string + Prefab: + type: object + properties: + _id: + type: string + name: + type: string + datetimeCreated: + type: string + format: dateTime + datetimeLastEdited: + type: string + format: dateTime diff --git a/opendc-web/opendc-web-api/tests/api/test_jobs.py b/opendc-web/opendc-web-api/tests/api/test_jobs.py new file mode 100644 index 00000000..2efe6933 --- /dev/null +++ b/opendc-web/opendc-web-api/tests/api/test_jobs.py @@ -0,0 +1,139 @@ +# Copyright (c) 2021 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. + +# +# 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: +# +# +from datetime import datetime + +from opendc.exts import db + +test_id = 24 * '1' +test_id_2 = 24 * '2' + + +def test_get_jobs(client, mocker): + mocker.patch.object(db, 'fetch_all', return_value=[ + {'_id': 'a', 'scenarioId': 'x', 'portfolioId': 'y', 'simulation': {'state': 'QUEUED'}} + ]) + res = client.get('/jobs/') + assert '200' in res.status + + +def test_get_job_non_existing(client, mocker): + mocker.patch.object(db, 'fetch_one', return_value=None) + assert '404' in client.get(f'/jobs/{test_id}').status + + +def test_get_job(client, mocker): + mocker.patch.object(db, 'fetch_one', return_value={ + '_id': 'a', 'scenarioId': 'x', 'portfolioId': 'y', 'simulation': {'state': 'QUEUED'} + }) + res = client.get(f'/jobs/{test_id}') + assert '200' in res.status + + +def test_update_job_nop(client, mocker): + mocker.patch.object(db, 'fetch_one', return_value={ + '_id': test_id, 'scenarioId': 'x', 'portfolioId': 'y', 'simulation': {'state': 'QUEUED'} + }) + update_mock = mocker.patch.object(db, 'fetch_and_update', return_value={ + '_id': test_id, 'scenarioId': 'x', 'portfolioId': 'y', + 'simulation': {'state': 'QUEUED', 'heartbeat': datetime.now()} + }) + res = client.post(f'/jobs/{test_id}', json={'state': 'QUEUED'}) + assert '200' in res.status + update_mock.assert_called_once() + + +def test_update_job_invalid_state(client, mocker): + mocker.patch.object(db, 'fetch_one', return_value={ + '_id': test_id, 'scenarioId': 'x', 'portfolioId': 'y', 'simulation': {'state': 'QUEUED'} + }) + res = client.post(f'/jobs/{test_id}', json={'state': 'FINISHED'}) + assert '400' in res.status + + +def test_update_job_claim(client, mocker): + mocker.patch.object(db, 'fetch_one', return_value={ + '_id': test_id, 'scenarioId': 'x', 'portfolioId': 'y', 'simulation': {'state': 'QUEUED'} + }) + update_mock = mocker.patch.object(db, 'fetch_and_update', return_value={ + '_id': test_id, 'scenarioId': 'x', 'portfolioId': 'y', + 'simulation': {'state': 'CLAIMED', 'heartbeat': datetime.now()} + }) + res = client.post(f'/jobs/{test_id}', json={'state': 'CLAIMED'}) + assert '200' in res.status + update_mock.assert_called_once() + + +def test_update_job_conflict(client, mocker): + mocker.patch.object(db, 'fetch_one', return_value={ + '_id': test_id, 'scenarioId': 'x', 'portfolioId': 'y', 'simulation': {'state': 'QUEUED'} + }) + update_mock = mocker.patch.object(db, 'fetch_and_update', return_value=None) + res = client.post(f'/jobs/{test_id}', json={'state': 'CLAIMED'}) + assert '409' in res.status + update_mock.assert_called_once() + + +def test_update_job_run(client, mocker): + mocker.patch.object(db, 'fetch_one', return_value={ + '_id': test_id, 'scenarioId': 'x', 'portfolioId': 'y', 'simulation': {'state': 'CLAIMED'} + }) + update_mock = mocker.patch.object(db, 'fetch_and_update', return_value={ + '_id': test_id, 'scenarioId': 'x', 'portfolioId': 'y', + 'simulation': {'state': 'RUNNING', 'heartbeat': datetime.now()} + }) + res = client.post(f'/jobs/{test_id}', json={'state': 'RUNNING'}) + assert '200' in res.status + update_mock.assert_called_once() + + +def test_update_job_finished(client, mocker): + mocker.patch.object(db, 'fetch_one', return_value={ + '_id': test_id, 'scenarioId': 'x', 'portfolioId': 'y', 'simulation': {'state': 'RUNNING'} + }) + update_mock = mocker.patch.object(db, 'fetch_and_update', return_value={ + '_id': test_id, 'scenarioId': 'x', 'portfolioId': 'y', + 'simulation': {'state': 'FINISHED', 'heartbeat': datetime.now()} + }) + res = client.post(f'/jobs/{test_id}', json={'state': 'FINISHED'}) + assert '200' in res.status + update_mock.assert_called_once() + + +def test_update_job_failed(client, mocker): + mocker.patch.object(db, 'fetch_one', return_value={ + '_id': test_id, 'scenarioId': 'x', 'portfolioId': 'y', 'simulation': {'state': 'RUNNING'} + }) + update_mock = mocker.patch.object(db, 'fetch_and_update', return_value={ + '_id': test_id, 'scenarioId': 'x', 'portfolioId': 'y', + 'simulation': {'state': 'FAILED', 'heartbeat': datetime.now()} + }) + res = client.post(f'/jobs/{test_id}', json={'state': 'FAILED'}) + assert '200' in res.status + update_mock.assert_called_once() diff --git a/opendc-web/opendc-web-api/tests/api/test_portfolios.py b/opendc-web/opendc-web-api/tests/api/test_portfolios.py new file mode 100644 index 00000000..196fcb1c --- /dev/null +++ b/opendc-web/opendc-web-api/tests/api/test_portfolios.py @@ -0,0 +1,340 @@ +# Copyright (c) 2021 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. + +from opendc.exts import db + +test_id = 24 * '1' +test_id_2 = 24 * '2' + + +def test_get_portfolio_non_existing(client, mocker): + mocker.patch.object(db, 'fetch_one', return_value=None) + assert '404' in client.get(f'/portfolios/{test_id}').status + + +def test_get_portfolio_no_authorizations(client, mocker): + mocker.patch.object(db, 'fetch_one', return_value={'projectId': test_id, 'authorizations': []}) + res = client.get(f'/portfolios/{test_id}') + assert '403' in res.status + + +def test_get_portfolio_not_authorized(client, mocker): + mocker.patch.object(db, + 'fetch_one', + return_value={ + 'projectId': test_id, + '_id': test_id, + 'authorizations': [] + }) + res = client.get(f'/portfolios/{test_id}') + assert '403' in res.status + + +def test_get_portfolio(client, mocker): + mocker.patch.object(db, + 'fetch_one', + return_value={ + 'projectId': test_id, + '_id': test_id, + 'authorizations': [{ + 'userId': 'test', + 'level': 'EDIT' + }] + }) + res = client.get(f'/portfolios/{test_id}') + assert '200' in res.status + + +def test_update_portfolio_missing_parameter(client): + assert '400' in client.put(f'/portfolios/{test_id}').status + + +def test_update_portfolio_non_existing(client, mocker): + mocker.patch.object(db, 'fetch_one', return_value=None) + assert '404' in client.put(f'/portfolios/{test_id}', json={ + 'portfolio': { + 'name': 'test', + 'targets': { + 'enabledMetrics': ['test'], + 'repeatsPerScenario': 2 + } + } + }).status + + +def test_update_portfolio_not_authorized(client, mocker): + mocker.patch.object(db, + 'fetch_one', + return_value={ + '_id': test_id, + 'projectId': test_id, + 'authorizations': [{ + 'userId': 'test', + 'level': 'VIEW' + }] + }) + mocker.patch.object(db, 'update', return_value={}) + assert '403' in client.put(f'/portfolios/{test_id}', json={ + 'portfolio': { + 'name': 'test', + 'targets': { + 'enabledMetrics': ['test'], + 'repeatsPerScenario': 2 + } + } + }).status + + +def test_update_portfolio(client, mocker): + mocker.patch.object(db, + 'fetch_one', + return_value={ + '_id': test_id, + 'projectId': test_id, + 'authorizations': [{ + 'userId': 'test', + 'level': 'OWN' + }], + 'targets': { + 'enabledMetrics': [], + 'repeatsPerScenario': 1 + } + }) + mocker.patch.object(db, 'update', return_value={}) + + res = client.put(f'/portfolios/{test_id}', json={'portfolio': { + 'name': 'test', + 'targets': { + 'enabledMetrics': ['test'], + 'repeatsPerScenario': 2 + } + }}) + assert '200' in res.status + + +def test_delete_project_non_existing(client, mocker): + mocker.patch.object(db, 'fetch_one', return_value=None) + assert '404' in client.delete(f'/portfolios/{test_id}').status + + +def test_delete_project_different_user(client, mocker): + mocker.patch.object(db, + 'fetch_one', + return_value={ + '_id': test_id, + 'projectId': test_id, + 'googleId': 'other_test', + 'authorizations': [{ + 'userId': 'test', + 'level': 'VIEW' + }] + }) + mocker.patch.object(db, 'delete_one', return_value=None) + assert '403' in client.delete(f'/portfolios/{test_id}').status + + +def test_delete_project(client, mocker): + mocker.patch.object(db, + 'fetch_one', + return_value={ + '_id': test_id, + 'projectId': test_id, + 'googleId': 'test', + 'portfolioIds': [test_id], + 'authorizations': [{ + 'userId': 'test', + 'level': 'OWN' + }] + }) + mocker.patch.object(db, 'delete_one', return_value={}) + mocker.patch.object(db, 'update', return_value=None) + res = client.delete(f'/portfolios/{test_id}') + assert '200' in res.status + + +def test_add_topology_missing_parameter(client, mocker): + mocker.patch.object(db, + 'fetch_one', + return_value={ + '_id': test_id, + 'projectId': test_id, + 'googleId': 'test', + 'portfolioIds': [test_id], + 'authorizations': [{ + 'userId': 'test', + 'level': 'OWN' + }] + }) + assert '400' in client.post(f'/projects/{test_id}/topologies').status + + +def test_add_topology(client, mocker): + mocker.patch.object(db, + 'fetch_one', + return_value={ + '_id': test_id, + 'authorizations': [{ + 'userId': 'test', + 'level': 'OWN' + }], + 'topologyIds': [] + }) + mocker.patch.object(db, + 'insert', + return_value={ + '_id': test_id, + 'datetimeCreated': '000', + 'datetimeLastEdited': '000', + 'topologyIds': [] + }) + mocker.patch.object(db, 'update', return_value={}) + res = client.post(f'/projects/{test_id}/topologies', json={'topology': {'name': 'test project', 'rooms': []}}) + assert 'rooms' in res.json['data'] + assert '200' in res.status + + +def test_add_topology_not_authorized(client, mocker): + mocker.patch.object(db, + 'fetch_one', + return_value={ + '_id': test_id, + 'projectId': test_id, + 'authorizations': [{ + 'userId': 'test', + 'level': 'VIEW' + }] + }) + assert '403' in client.post(f'/projects/{test_id}/topologies', + json={ + 'topology': { + 'name': 'test_topology', + 'rooms': [] + } + }).status + + +def test_add_portfolio_missing_parameter(client, mocker): + mocker.patch.object(db, + 'fetch_one', + return_value={ + '_id': test_id, + 'projectId': test_id, + 'googleId': 'test', + 'portfolioIds': [test_id], + 'authorizations': [{ + 'userId': 'test', + 'level': 'OWN' + }] + }) + assert '400' in client.post(f'/projects/{test_id}/portfolios').status + + +def test_add_portfolio_non_existing_project(client, mocker): + mocker.patch.object(db, 'fetch_one', return_value=None) + assert '404' in client.post(f'/projects/{test_id}/portfolios', + json={ + 'portfolio': { + 'name': 'test', + 'targets': { + 'enabledMetrics': ['test'], + 'repeatsPerScenario': 2 + } + } + }).status + + +def test_add_portfolio_not_authorized(client, mocker): + mocker.patch.object(db, + 'fetch_one', + return_value={ + '_id': test_id, + 'projectId': test_id, + 'authorizations': [{ + 'userId': 'test', + 'level': 'VIEW' + }] + }) + assert '403' in client.post(f'/projects/{test_id}/portfolios', + json={ + 'portfolio': { + 'name': 'test', + 'targets': { + 'enabledMetrics': ['test'], + 'repeatsPerScenario': 2 + } + } + }).status + + +def test_add_portfolio(client, mocker): + mocker.patch.object(db, + 'fetch_one', + return_value={ + '_id': test_id, + 'projectId': test_id, + 'portfolioIds': [test_id], + 'authorizations': [{ + 'userId': 'test', + 'level': 'EDIT' + }] + }) + mocker.patch.object(db, + 'insert', + return_value={ + '_id': test_id, + 'name': 'test', + 'targets': { + 'enabledMetrics': ['test'], + 'repeatsPerScenario': 2 + }, + 'projectId': test_id, + 'scenarioIds': [], + }) + mocker.patch.object(db, 'update', return_value=None) + res = client.post( + f'/projects/{test_id}/portfolios', + json={ + 'portfolio': { + 'name': 'test', + 'targets': { + 'enabledMetrics': ['test'], + 'repeatsPerScenario': 2 + } + } + }) + assert 'projectId' in res.json['data'] + assert 'scenarioIds' in res.json['data'] + assert '200' in res.status + + +def test_get_portfolio_scenarios(client, mocker): + mocker.patch.object(db, + 'fetch_one', + return_value={ + 'projectId': test_id, + '_id': test_id, + 'authorizations': [{ + 'userId': 'test', + 'level': 'EDIT' + }] + }) + mocker.patch.object(db, 'fetch_all', return_value=[{'_id': test_id}]) + res = client.get(f'/portfolios/{test_id}/scenarios') + assert '200' in res.status diff --git a/opendc-web/opendc-web-api/tests/api/test_prefabs.py b/opendc-web/opendc-web-api/tests/api/test_prefabs.py new file mode 100644 index 00000000..ea3d92d6 --- /dev/null +++ b/opendc-web/opendc-web-api/tests/api/test_prefabs.py @@ -0,0 +1,252 @@ +# Copyright (c) 2021 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. + +from unittest.mock import Mock +from opendc.exts import db + +test_id = 24 * '1' +test_id_2 = 24 * '2' + + +def test_add_prefab_missing_parameter(client): + assert '400' in client.post('/prefabs/').status + + +def test_add_prefab(client, mocker): + mocker.patch.object(db, 'fetch_one', return_value={'_id': test_id, 'authorizations': []}) + mocker.patch.object(db, + 'insert', + return_value={ + '_id': test_id, + 'datetimeCreated': '000', + 'datetimeLastEdited': '000', + 'authorId': test_id + }) + res = client.post('/prefabs/', json={'prefab': {'name': 'test prefab'}}) + assert 'datetimeCreated' in res.json['data'] + assert 'datetimeLastEdited' in res.json['data'] + assert 'authorId' in res.json['data'] + assert '200' in res.status + + +def test_get_prefabs(client, mocker): + db.fetch_all = Mock() + mocker.patch.object(db, 'fetch_one', return_value={'_id': test_id}) + db.fetch_all.side_effect = [ + [{ + '_id': test_id, + 'datetimeCreated': '000', + 'datetimeLastEdited': '000', + 'authorId': test_id, + 'visibility' : 'private' + }, + { + '_id': '2' * 24, + 'datetimeCreated': '000', + 'datetimeLastEdited': '000', + 'authorId': test_id, + 'visibility' : 'private' + }, + { + '_id': '3' * 24, + 'datetimeCreated': '000', + 'datetimeLastEdited': '000', + 'authorId': test_id, + 'visibility' : 'public' + }, + { + '_id': '4' * 24, + 'datetimeCreated': '000', + 'datetimeLastEdited': '000', + 'authorId': test_id, + 'visibility' : 'public' + }], + [{ + '_id': '5' * 24, + 'datetimeCreated': '000', + 'datetimeLastEdited': '000', + 'authorId': '2' * 24, + 'visibility' : 'public' + }, + { + '_id': '6' * 24, + 'datetimeCreated': '000', + 'datetimeLastEdited': '000', + 'authorId': '2' * 24, + 'visibility' : 'public' + }, + { + '_id': '7' * 24, + 'datetimeCreated': '000', + 'datetimeLastEdited': '000', + 'authorId': '2' * 24, + 'visibility' : 'public' + }, + { + '_id': '8' * 24, + 'datetimeCreated': '000', + 'datetimeLastEdited': '000', + 'authorId': '2' * 24, + 'visibility' : 'public' + }] + ] + mocker.patch.object(db, 'fetch_one', return_value={'_id': test_id}) + res = client.get('/prefabs/') + assert '200' in res.status + + +def test_get_prefab_non_existing(client, mocker): + mocker.patch.object(db, 'fetch_one', return_value=None) + assert '404' in client.get(f'/prefabs/{test_id}').status + + +def test_get_private_prefab_not_authorized(client, mocker): + db.fetch_one = Mock() + db.fetch_one.side_effect = [{ + '_id': test_id, + 'name': 'test prefab', + 'authorId': test_id_2, + 'visibility': 'private', + 'rack': {} + }, + { + '_id': test_id + } + ] + res = client.get(f'/prefabs/{test_id}') + assert '403' in res.status + + +def test_get_private_prefab(client, mocker): + db.fetch_one = Mock() + db.fetch_one.side_effect = [{ + '_id': test_id, + 'name': 'test prefab', + 'authorId': 'test', + 'visibility': 'private', + 'rack': {} + }, + { + '_id': test_id + } + ] + res = client.get(f'/prefabs/{test_id}') + assert '200' in res.status + + +def test_get_public_prefab(client, mocker): + db.fetch_one = Mock() + db.fetch_one.side_effect = [{ + '_id': test_id, + 'name': 'test prefab', + 'authorId': test_id_2, + 'visibility': 'public', + 'rack': {} + }, + { + '_id': test_id + } + ] + res = client.get(f'/prefabs/{test_id}') + assert '200' in res.status + + +def test_update_prefab_missing_parameter(client): + assert '400' in client.put(f'/prefabs/{test_id}').status + + +def test_update_prefab_non_existing(client, mocker): + mocker.patch.object(db, 'fetch_one', return_value=None) + assert '404' in client.put(f'/prefabs/{test_id}', json={'prefab': {'name': 'S'}}).status + + +def test_update_prefab_not_authorized(client, mocker): + db.fetch_one = Mock() + db.fetch_one.side_effect = [{ + '_id': test_id, + 'name': 'test prefab', + 'authorId': test_id_2, + 'visibility': 'private', + 'rack': {} + }, + { + '_id': test_id + } + ] + mocker.patch.object(db, 'update', return_value={}) + assert '403' in client.put(f'/prefabs/{test_id}', json={'prefab': {'name': 'test prefab', 'rack': {}}}).status + + +def test_update_prefab(client, mocker): + db.fetch_one = Mock() + db.fetch_one.side_effect = [{ + '_id': test_id, + 'name': 'test prefab', + 'authorId': 'test', + 'visibility': 'private', + 'rack': {} + }, + { + '_id': test_id + } + ] + mocker.patch.object(db, 'update', return_value={}) + res = client.put(f'/prefabs/{test_id}', json={'prefab': {'name': 'test prefab', 'rack': {}}}) + assert '200' in res.status + + +def test_delete_prefab_non_existing(client, mocker): + mocker.patch.object(db, 'fetch_one', return_value=None) + assert '404' in client.delete(f'/prefabs/{test_id}').status + + +def test_delete_prefab_different_user(client, mocker): + db.fetch_one = Mock() + db.fetch_one.side_effect = [{ + '_id': test_id, + 'name': 'test prefab', + 'authorId': test_id_2, + 'visibility': 'private', + 'rack': {} + }, + { + '_id': test_id + } + ] + mocker.patch.object(db, 'delete_one', return_value=None) + assert '403' in client.delete(f'/prefabs/{test_id}').status + + +def test_delete_prefab(client, mocker): + db.fetch_one = Mock() + db.fetch_one.side_effect = [{ + '_id': test_id, + 'name': 'test prefab', + 'authorId': 'test', + 'visibility': 'private', + 'rack': {} + }, + { + '_id': test_id + } + ] + mocker.patch.object(db, 'delete_one', return_value={'prefab': {'name': 'name'}}) + res = client.delete(f'/prefabs/{test_id}') + assert '200' in res.status diff --git a/opendc-web/opendc-web-api/tests/api/test_projects.py b/opendc-web/opendc-web-api/tests/api/test_projects.py new file mode 100644 index 00000000..1cfe4c52 --- /dev/null +++ b/opendc-web/opendc-web-api/tests/api/test_projects.py @@ -0,0 +1,197 @@ +# Copyright (c) 2021 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. + +from opendc.exts import db + +test_id = 24 * '1' + + +def test_get_user_projects(client, mocker): + mocker.patch.object(db, 'fetch_all', return_value={'_id': test_id, 'authorizations': [{'userId': 'test', + 'level': 'OWN'}]}) + res = client.get('/projects/') + assert '200' in res.status + + +def test_get_user_topologies(client, mocker): + mocker.patch.object(db, + 'fetch_one', + return_value={ + '_id': test_id, + 'authorizations': [{ + 'userId': 'test', + 'level': 'EDIT' + }] + }) + mocker.patch.object(db, 'fetch_all', return_value=[{'_id': test_id}]) + res = client.get(f'/projects/{test_id}/topologies') + assert '200' in res.status + + +def test_get_user_portfolios(client, mocker): + mocker.patch.object(db, + 'fetch_one', + return_value={ + '_id': test_id, + 'authorizations': [{ + 'userId': 'test', + 'level': 'EDIT' + }] + }) + mocker.patch.object(db, 'fetch_all', return_value=[{'_id': test_id}]) + res = client.get(f'/projects/{test_id}/portfolios') + assert '200' in res.status + + +def test_add_project_missing_parameter(client): + assert '400' in client.post('/projects/').status + + +def test_add_project(client, mocker): + mocker.patch.object(db, 'fetch_one', return_value={'_id': test_id, 'authorizations': []}) + mocker.patch.object(db, + 'insert', + return_value={ + '_id': test_id, + 'datetimeCreated': '000', + 'datetimeLastEdited': '000', + 'topologyIds': [] + }) + mocker.patch.object(db, 'update', return_value={}) + res = client.post('/projects/', json={'project': {'name': 'test project'}}) + assert 'datetimeCreated' in res.json['data'] + assert 'datetimeLastEdited' in res.json['data'] + assert 'topologyIds' in res.json['data'] + assert '200' in res.status + + +def test_get_project_non_existing(client, mocker): + mocker.patch.object(db, 'fetch_one', return_value=None) + assert '404' in client.get(f'/projects/{test_id}').status + + +def test_get_project_no_authorizations(client, mocker): + mocker.patch.object(db, 'fetch_one', return_value={'authorizations': []}) + res = client.get(f'/projects/{test_id}') + assert '403' in res.status + + +def test_get_project_not_authorized(client, mocker): + mocker.patch.object(db, + 'fetch_one', + return_value={ + '_id': test_id, + 'authorizations': [] + }) + res = client.get(f'/projects/{test_id}') + assert '403' in res.status + + +def test_get_project(client, mocker): + mocker.patch.object(db, + 'fetch_one', + return_value={ + '_id': test_id, + 'authorizations': [{ + 'userId': 'test', + 'level': 'EDIT' + }] + }) + res = client.get(f'/projects/{test_id}') + assert '200' in res.status + + +def test_update_project_missing_parameter(client): + assert '400' in client.put(f'/projects/{test_id}').status + + +def test_update_project_non_existing(client, mocker): + mocker.patch.object(db, 'fetch_one', return_value=None) + assert '404' in client.put(f'/projects/{test_id}', json={'project': {'name': 'S'}}).status + + +def test_update_project_not_authorized(client, mocker): + mocker.patch.object(db, + 'fetch_one', + return_value={ + '_id': test_id, + 'authorizations': [{ + 'userId': 'test', + 'level': 'VIEW' + }] + }) + mocker.patch.object(db, 'update', return_value={}) + assert '403' in client.put(f'/projects/{test_id}', json={'project': {'name': 'S'}}).status + + +def test_update_project(client, mocker): + mocker.patch.object(db, + 'fetch_one', + return_value={ + '_id': test_id, + 'authorizations': [{ + 'userId': 'test', + 'level': 'OWN' + }] + }) + mocker.patch.object(db, 'update', return_value={}) + + res = client.put(f'/projects/{test_id}', json={'project': {'name': 'S'}}) + assert '200' in res.status + + +def test_delete_project_non_existing(client, mocker): + mocker.patch.object(db, 'fetch_one', return_value=None) + assert '404' in client.delete(f'/projects/{test_id}').status + + +def test_delete_project_different_user(client, mocker): + mocker.patch.object(db, + 'fetch_one', + return_value={ + '_id': test_id, + 'googleId': 'other_test', + 'authorizations': [{ + 'userId': 'test', + 'level': 'VIEW' + }], + 'topologyIds': [] + }) + mocker.patch.object(db, 'delete_one', return_value=None) + assert '403' in client.delete(f'/projects/{test_id}').status + + +def test_delete_project(client, mocker): + mocker.patch.object(db, + 'fetch_one', + return_value={ + '_id': test_id, + 'googleId': 'test', + 'authorizations': [{ + 'userId': 'test', + 'level': 'OWN' + }], + 'topologyIds': [], + 'portfolioIds': [], + }) + mocker.patch.object(db, 'update', return_value=None) + mocker.patch.object(db, 'delete_one', return_value={'googleId': 'test'}) + res = client.delete(f'/projects/{test_id}') + assert '200' in res.status diff --git a/opendc-web/opendc-web-api/tests/api/test_scenarios.py b/opendc-web/opendc-web-api/tests/api/test_scenarios.py new file mode 100644 index 00000000..bdd5c4a3 --- /dev/null +++ b/opendc-web/opendc-web-api/tests/api/test_scenarios.py @@ -0,0 +1,135 @@ +# Copyright (c) 2021 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. + +from opendc.exts import db + +test_id = 24 * '1' +test_id_2 = 24 * '2' + + +def test_get_scenario_non_existing(client, mocker): + mocker.patch.object(db, 'fetch_one', return_value=None) + assert '404' in client.get(f'/scenarios/{test_id}').status + + +def test_get_scenario_no_authorizations(client, mocker): + m = mocker.MagicMock() + m.side_effect = ({'portfolioId': test_id}, {'projectId': test_id}, {'authorizations': []}) + mocker.patch.object(db, 'fetch_one', m) + res = client.get(f'/scenarios/{test_id}') + assert '403' in res.status + + +def test_get_scenario(client, mocker): + mocker.patch.object(db, + 'fetch_one', + side_effect=[ + {'portfolioId': test_id}, + {'projectId': test_id}, + {'authorizations': + [{'userId': 'test', 'level': 'OWN'}] + }]) + res = client.get(f'/scenarios/{test_id}') + assert '200' in res.status + + +def test_update_scenario_missing_parameter(client): + assert '400' in client.put(f'/scenarios/{test_id}').status + + +def test_update_scenario_non_existing(client, mocker): + mocker.patch.object(db, 'fetch_one', return_value=None) + assert '404' in client.put(f'/scenarios/{test_id}', json={ + 'scenario': { + 'name': 'test', + } + }).status + + +def test_update_scenario_not_authorized(client, mocker): + mocker.patch.object(db, + 'fetch_one', + side_effect=[ + {'portfolioId': test_id}, + {'projectId': test_id}, + {'authorizations': + [{'userId': 'test', 'level': 'VIEW'}] + }]) + mocker.patch.object(db, 'update', return_value={}) + assert '403' in client.put(f'/scenarios/{test_id}', json={ + 'scenario': { + 'name': 'test', + } + }).status + + +def test_update_scenario(client, mocker): + mocker.patch.object(db, + 'fetch_one', + side_effect=[ + {'_id': test_id, 'portfolioId': test_id}, + {'projectId': test_id}, + {'authorizations': + [{'userId': 'test', 'level': 'OWN'}] + }]) + mocker.patch.object(db, 'update', return_value={}) + + res = client.put(f'/scenarios/{test_id}', json={'scenario': { + 'name': 'test', + }}) + assert '200' in res.status + + +def test_delete_project_non_existing(client, mocker): + mocker.patch.object(db, 'fetch_one', return_value=None) + assert '404' in client.delete(f'/scenarios/{test_id}').status + + +def test_delete_project_different_user(client, mocker): + mocker.patch.object(db, + 'fetch_one', + side_effect=[ + {'_id': test_id, 'portfolioId': test_id}, + {'projectId': test_id}, + {'authorizations': + [{'userId': 'test', 'level': 'VIEW'}] + }]) + mocker.patch.object(db, 'delete_one', return_value=None) + assert '403' in client.delete(f'/scenarios/{test_id}').status + + +def test_delete_project(client, mocker): + mocker.patch.object(db, + 'fetch_one', + return_value={ + '_id': test_id, + 'projectId': test_id, + 'portfolioId': test_id, + 'googleId': 'test', + 'scenarioIds': [test_id], + 'authorizations': [{ + 'userId': 'test', + 'level': 'OWN' + }] + }) + mocker.patch.object(db, 'delete_one', return_value={}) + mocker.patch.object(db, 'update', return_value=None) + res = client.delete(f'/scenarios/{test_id}') + assert '200' in res.status diff --git a/opendc-web/opendc-web-api/tests/api/test_schedulers.py b/opendc-web/opendc-web-api/tests/api/test_schedulers.py new file mode 100644 index 00000000..5d9e6995 --- /dev/null +++ b/opendc-web/opendc-web-api/tests/api/test_schedulers.py @@ -0,0 +1,22 @@ +# Copyright (c) 2021 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. + +def test_get_schedulers(client): + assert '200' in client.get('/schedulers/').status diff --git a/opendc-web/opendc-web-api/tests/api/test_topologies.py b/opendc-web/opendc-web-api/tests/api/test_topologies.py new file mode 100644 index 00000000..6e7c54ef --- /dev/null +++ b/opendc-web/opendc-web-api/tests/api/test_topologies.py @@ -0,0 +1,140 @@ +# Copyright (c) 2021 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. + +from opendc.exts import db + +test_id = 24 * '1' +test_id_2 = 24 * '2' + + +def test_get_topology(client, mocker): + mocker.patch.object(db, + 'fetch_one', + return_value={ + '_id': test_id, + 'projectId': test_id, + 'authorizations': [{ + 'userId': 'test', + 'level': 'EDIT' + }] + }) + res = client.get(f'/topologies/{test_id}') + 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('/topologies/1').status + + +def test_get_topology_not_authorized(client, mocker): + mocker.patch.object(db, + 'fetch_one', + return_value={ + '_id': test_id, + 'projectId': test_id, + 'authorizations': [] + }) + res = client.get(f'/topologies/{test_id}') + assert '403' in res.status + + +def test_get_topology_no_authorizations(client, mocker): + mocker.patch.object(db, 'fetch_one', return_value={'projectId': test_id, 'authorizations': []}) + res = client.get(f'/topologies/{test_id}') + assert '403' in res.status + + +def test_update_topology_missing_parameter(client, mocker): + mocker.patch.object(db, + 'fetch_one', + return_value={ + '_id': test_id, + 'projectId': test_id, + 'authorizations': [] + }) + assert '400' in client.put(f'/topologies/{test_id}').status + + +def test_update_topology_non_existent(client, mocker): + mocker.patch.object(db, 'fetch_one', return_value=None) + assert '404' in client.put(f'/topologies/{test_id}', json={'topology': {'name': 'test_topology', 'rooms': []}}).status + + +def test_update_topology_not_authorized(client, mocker): + mocker.patch.object(db, + 'fetch_one', + return_value={ + '_id': test_id, + 'projectId': test_id, + 'authorizations': [] + }) + mocker.patch.object(db, 'update', return_value={}) + assert '403' in client.put(f'/topologies/{test_id}', json={ + 'topology': { + 'name': 'updated_topology', + 'rooms': [] + } + }).status + + +def test_update_topology(client, mocker): + mocker.patch.object(db, + 'fetch_one', + return_value={ + '_id': test_id, + 'projectId': test_id, + 'authorizations': [{ + 'userId': 'test', + 'level': 'OWN' + }] + }) + mocker.patch.object(db, 'update', return_value={}) + + assert '200' in client.put(f'/topologies/{test_id}', json={ + 'topology': { + 'name': 'updated_topology', + 'rooms': [] + } + }).status + + +def test_delete_topology(client, mocker): + mocker.patch.object(db, + 'fetch_one', + return_value={ + '_id': test_id, + 'projectId': test_id, + 'googleId': 'test', + 'topologyIds': [test_id], + 'authorizations': [{ + 'userId': 'test', + 'level': 'OWN' + }] + }) + mocker.patch.object(db, 'delete_one', return_value={}) + mocker.patch.object(db, 'update', return_value=None) + res = client.delete(f'/topologies/{test_id}') + assert '200' in res.status + + +def test_delete_nonexistent_topology(client, mocker): + mocker.patch.object(db, 'fetch_one', return_value=None) + assert '404' in client.delete(f'/topologies/{test_id}').status diff --git a/opendc-web/opendc-web-api/tests/api/test_traces.py b/opendc-web/opendc-web-api/tests/api/test_traces.py new file mode 100644 index 00000000..0b252c2f --- /dev/null +++ b/opendc-web/opendc-web-api/tests/api/test_traces.py @@ -0,0 +1,40 @@ +# Copyright (c) 2021 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. + +from opendc.exts import db + +test_id = 24 * '1' + + +def test_get_traces(client, mocker): + mocker.patch.object(db, 'fetch_all', return_value=[]) + assert '200' in client.get('/traces/').status + + +def test_get_trace_non_existing(client, mocker): + mocker.patch.object(db, 'fetch_one', return_value=None) + assert '404' in client.get(f'/traces/{test_id}').status + + +def test_get_trace(client, mocker): + mocker.patch.object(db, 'fetch_one', return_value={'name': 'test trace'}) + res = client.get(f'/traces/{test_id}') + assert 'name' in res.json['data'] + assert '200' in res.status diff --git a/opendc-web/opendc-web-runner/build.gradle.kts b/opendc-web/opendc-web-runner/build.gradle.kts index b7eb223c..810f512f 100644 --- a/opendc-web/opendc-web-runner/build.gradle.kts +++ b/opendc-web/opendc-web-runner/build.gradle.kts @@ -25,26 +25,34 @@ description = "Experiment runner for OpenDC" /* Build configuration */ plugins { `kotlin-conventions` + `testing-conventions` application } application { - mainClass.set("org.opendc.runner.web.MainKt") + mainClass.set("org.opendc.web.runner.MainKt") } dependencies { api(platform(projects.opendcPlatform)) implementation(projects.opendcCompute.opendcComputeSimulator) - implementation(projects.opendcFormat) - implementation(projects.opendcExperiments.opendcExperimentsCapelin) + implementation(projects.opendcCompute.opendcComputeWorkload) implementation(projects.opendcSimulator.opendcSimulatorCore) implementation(projects.opendcTelemetry.opendcTelemetrySdk) + implementation(projects.opendcTelemetry.opendcTelemetryCompute) + implementation(projects.opendcTrace.opendcTraceApi) implementation(libs.kotlin.logging) implementation(libs.clikt) - implementation(libs.jackson.module.kotlin) implementation(libs.sentry.log4j2) - implementation(libs.mongodb) + implementation(libs.ktor.client.cio) + implementation(libs.ktor.client.auth) + implementation(libs.ktor.client.jackson) + implementation(libs.jackson.datatype.jsr310) + implementation(kotlin("reflect")) + runtimeOnly(projects.opendcTrace.opendcTraceOpendc) runtimeOnly(libs.log4j.slf4j) + + testImplementation(libs.ktor.client.mock) } diff --git a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/runner/web/Main.kt b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/runner/web/Main.kt deleted file mode 100644 index 09f7de35..00000000 --- a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/runner/web/Main.kt +++ /dev/null @@ -1,380 +0,0 @@ -/* - * Copyright (c) 2020 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. - */ - -package org.opendc.runner.web - -import com.github.ajalt.clikt.core.CliktCommand -import com.github.ajalt.clikt.parameters.options.* -import com.github.ajalt.clikt.parameters.types.file -import com.github.ajalt.clikt.parameters.types.int -import com.github.ajalt.clikt.parameters.types.long -import com.mongodb.MongoClientSettings -import com.mongodb.MongoCredential -import com.mongodb.ServerAddress -import com.mongodb.client.MongoClients -import com.mongodb.client.MongoDatabase -import com.mongodb.client.model.Filters -import io.opentelemetry.api.metrics.MeterProvider -import io.opentelemetry.sdk.metrics.SdkMeterProvider -import io.opentelemetry.sdk.metrics.export.MetricProducer -import kotlinx.coroutines.* -import kotlinx.coroutines.channels.Channel -import mu.KotlinLogging -import org.bson.Document -import org.bson.types.ObjectId -import org.opendc.compute.service.scheduler.FilterScheduler -import org.opendc.compute.service.scheduler.filters.ComputeCapabilitiesFilter -import org.opendc.compute.service.scheduler.filters.ComputeFilter -import org.opendc.compute.service.scheduler.weights.* -import org.opendc.experiments.capelin.* -import org.opendc.experiments.capelin.model.Workload -import org.opendc.experiments.capelin.trace.Sc20ParquetTraceReader -import org.opendc.experiments.capelin.trace.Sc20RawParquetTraceReader -import org.opendc.format.environment.EnvironmentReader -import org.opendc.format.trace.sc20.Sc20PerformanceInterferenceReader -import org.opendc.simulator.core.runBlockingSimulation -import org.opendc.telemetry.sdk.toOtelClock -import java.io.File -import kotlin.random.Random - -private val logger = KotlinLogging.logger {} - -/** - * Represents the CLI command for starting the OpenDC web runner. - */ -@OptIn(ExperimentalCoroutinesApi::class) -public class RunnerCli : CliktCommand(name = "runner") { - /** - * The name of the database to use. - */ - private val mongoDb by option( - "--mongo-db", - help = "name of the database to use", - envvar = "OPENDC_DB" - ) - .default("opendc") - - /** - * The database host to connect to. - */ - private val mongoHost by option( - "--mongo-host", - help = "database host to connect to", - envvar = "OPENDC_DB_HOST" - ) - .default("localhost") - - /** - * The database port to connect to. - */ - private val mongoPort by option( - "--mongo-port", - help = "database port to connect to", - envvar = "OPENDC_DB_PORT" - ) - .int() - .default(27017) - - /** - * The database user to connect with. - */ - private val mongoUser by option( - "--mongo-user", - help = "database user to connect with", - envvar = "OPENDC_DB_USER" - ) - .default("opendc") - - /** - * The database password to connect with. - */ - private val mongoPassword by option( - "--mongo-password", - help = "database password to connect with", - envvar = "OPENDC_DB_PASSWORD" - ) - .convert { it.toCharArray() } - .required() - - /** - * The path to the traces directory. - */ - private val tracePath by option( - "--traces", - help = "path to the directory containing the traces", - envvar = "OPENDC_TRACES" - ) - .file(canBeFile = false) - .defaultLazy { File("traces/") } - - /** - * The maximum duration of a single experiment run. - */ - private val runTimeout by option( - "--run-timeout", - help = "maximum duration of experiment in seconds", - envvar = "OPENDC_RUN_TIMEOUT" - ) - .long() - .default(60 * 3) // Experiment may run for a maximum of three minutes - - /** - * Connect to the user-specified database. - */ - private fun createDatabase(): MongoDatabase { - val credential = MongoCredential.createScramSha1Credential( - mongoUser, - mongoDb, - mongoPassword - ) - - val settings = MongoClientSettings.builder() - .credential(credential) - .applyToClusterSettings { it.hosts(listOf(ServerAddress(mongoHost, mongoPort))) } - .build() - val client = MongoClients.create(settings) - return client.getDatabase(mongoDb) - } - - /** - * Run a single scenario. - */ - private suspend fun runScenario(portfolio: Document, scenario: Document, topologyParser: TopologyParser): List<WebExperimentMonitor.Result> { - val id = scenario.getObjectId("_id") - - logger.info { "Constructing performance interference model" } - - val traceDir = File( - tracePath, - scenario.getEmbedded(listOf("trace", "traceId"), String::class.java) - ) - val traceReader = Sc20RawParquetTraceReader(traceDir) - val performanceInterferenceReader = let { - val path = File(traceDir, "performance-interference-model.json") - val operational = scenario.get("operational", Document::class.java) - val enabled = operational.getBoolean("performanceInterferenceEnabled") - - if (!enabled || !path.exists()) { - return@let null - } - - path.inputStream().use { Sc20PerformanceInterferenceReader(it) } - } - - val targets = portfolio.get("targets", Document::class.java) - val topologyId = scenario.getEmbedded(listOf("topology", "topologyId"), ObjectId::class.java) - val environment = topologyParser.read(topologyId) - - val results = (0 until targets.getInteger("repeatsPerScenario")).map { - logger.info { "Starting repeat $it" } - withTimeout(runTimeout * 1000) { - runRepeat(scenario, it, environment, traceReader, performanceInterferenceReader) - } - } - - logger.info { "Finished simulation for scenario $id" } - - return results - } - - /** - * Run a single repeat. - */ - private suspend fun runRepeat( - scenario: Document, - repeat: Int, - environment: EnvironmentReader, - traceReader: Sc20RawParquetTraceReader, - performanceInterferenceReader: Sc20PerformanceInterferenceReader? - ): WebExperimentMonitor.Result { - val monitor = WebExperimentMonitor() - - try { - runBlockingSimulation { - val seed = repeat - val traceDocument = scenario.get("trace", Document::class.java) - val workloadName = traceDocument.getString("traceId") - val workloadFraction = traceDocument.get("loadSamplingFraction", Number::class.java).toDouble() - - val seeder = Random(seed) - - val chan = Channel<Unit>(Channel.CONFLATED) - - val meterProvider: MeterProvider = SdkMeterProvider - .builder() - .setClock(clock.toOtelClock()) - .build() - val metricProducer = meterProvider as MetricProducer - - val operational = scenario.get("operational", Document::class.java) - val allocationPolicy = - when (val policyName = operational.getString("schedulerName")) { - "mem" -> FilterScheduler( - filters = listOf(ComputeFilter(), ComputeCapabilitiesFilter()), - weighers = listOf(MemoryWeigher() to -1.0) - ) - "mem-inv" -> FilterScheduler( - filters = listOf(ComputeFilter(), ComputeCapabilitiesFilter()), - weighers = listOf(MemoryWeigher() to 1.0) - ) - "core-mem" -> FilterScheduler( - filters = listOf(ComputeFilter(), ComputeCapabilitiesFilter()), - weighers = listOf(CoreMemoryWeigher() to -1.0) - ) - "core-mem-inv" -> FilterScheduler( - filters = listOf(ComputeFilter(), ComputeCapabilitiesFilter()), - weighers = listOf(CoreMemoryWeigher() to 1.0) - ) - "active-servers" -> FilterScheduler( - filters = listOf(ComputeFilter(), ComputeCapabilitiesFilter()), - weighers = listOf(ProvisionedCoresWeigher() to -1.0) - ) - "active-servers-inv" -> FilterScheduler( - filters = listOf(ComputeFilter(), ComputeCapabilitiesFilter()), - weighers = listOf(InstanceCountWeigher() to 1.0) - ) - "provisioned-cores" -> FilterScheduler( - filters = listOf(ComputeFilter(), ComputeCapabilitiesFilter()), - weighers = listOf(ProvisionedCoresWeigher() to -1.0) - ) - "provisioned-cores-inv" -> FilterScheduler( - filters = listOf(ComputeFilter(), ComputeCapabilitiesFilter()), - weighers = listOf(ProvisionedCoresWeigher() to 1.0) - ) - "random" -> FilterScheduler( - filters = listOf(ComputeFilter(), ComputeCapabilitiesFilter()), - weighers = listOf(RandomWeigher(java.util.Random(seeder.nextLong())) to 1.0) - ) - else -> throw IllegalArgumentException("Unknown policy $policyName") - } - - val performanceInterferenceModel = performanceInterferenceReader?.construct(seeder) ?: emptyMap() - val trace = Sc20ParquetTraceReader( - listOf(traceReader), - performanceInterferenceModel, - Workload(workloadName, workloadFraction), - seed - ) - val failureFrequency = if (operational.getBoolean("failuresEnabled", false)) 24.0 * 7 else 0.0 - - withComputeService(clock, meterProvider, environment, allocationPolicy) { scheduler -> - val failureDomain = if (failureFrequency > 0) { - logger.debug { "ENABLING failures" } - createFailureDomain( - this, - clock, - seeder.nextInt(), - failureFrequency, - scheduler, - chan - ) - } else { - null - } - - withMonitor(monitor, clock, meterProvider as MetricProducer, scheduler) { - processTrace( - clock, - trace, - scheduler, - chan, - monitor - ) - } - - failureDomain?.cancel() - } - - val monitorResults = collectMetrics(metricProducer) - logger.debug { "Finish SUBMIT=${monitorResults.submittedVms} FAIL=${monitorResults.unscheduledVms} QUEUE=${monitorResults.queuedVms} RUNNING=${monitorResults.runningVms}" } - } - } catch (cause: Throwable) { - logger.warn(cause) { "Experiment failed" } - } - - return monitor.getResult() - } - - private val POLL_INTERVAL = 5000L // ms = 5 s - private val HEARTBEAT_INTERVAL = 60000L // ms = 1 min - - override fun run(): Unit = runBlocking(Dispatchers.Default) { - logger.info { "Starting OpenDC web runner" } - logger.info { "Connecting to MongoDB instance" } - val database = createDatabase() - val manager = ScenarioManager(database.getCollection("scenarios")) - val portfolios = database.getCollection("portfolios") - val topologies = database.getCollection("topologies") - val topologyParser = TopologyParser(topologies) - - logger.info { "Watching for queued scenarios" } - - while (true) { - val scenario = manager.findNext() - - if (scenario == null) { - delay(POLL_INTERVAL) - continue - } - - val id = scenario.getObjectId("_id") - - logger.info { "Found queued scenario $id: attempting to claim" } - - if (!manager.claim(id)) { - logger.info { "Failed to claim scenario" } - continue - } - - coroutineScope { - // Launch heartbeat process - val heartbeat = launch { - while (true) { - delay(HEARTBEAT_INTERVAL) - manager.heartbeat(id) - } - } - - try { - val portfolio = portfolios.find(Filters.eq("_id", scenario.getObjectId("portfolioId"))).first()!! - val results = runScenario(portfolio, scenario, topologyParser) - - logger.info { "Writing results to database" } - - manager.finish(id, results) - - logger.info { "Successfully finished scenario $id" } - } catch (e: Exception) { - logger.error(e) { "Scenario failed to finish" } - manager.fail(id) - } finally { - heartbeat.cancel() - } - } - } - } -} - -/** - * Main entry point of the runner. - */ -public fun main(args: Array<String>): Unit = RunnerCli().main(args) diff --git a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/runner/web/ScenarioManager.kt b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/runner/web/ScenarioManager.kt deleted file mode 100644 index a3907051..00000000 --- a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/runner/web/ScenarioManager.kt +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (c) 2020 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. - */ - -package org.opendc.runner.web - -import com.mongodb.client.MongoCollection -import com.mongodb.client.model.Filters -import com.mongodb.client.model.Updates -import org.bson.Document -import org.bson.types.ObjectId -import java.time.Instant - -/** - * Manages the queue of scenarios that need to be processed. - */ -public class ScenarioManager(private val collection: MongoCollection<Document>) { - /** - * Find the next scenario that the simulator needs to process. - */ - public fun findNext(): Document? { - return collection - .find(Filters.eq("simulation.state", "QUEUED")) - .first() - } - - /** - * Claim the scenario in the database with the specified id. - */ - public fun claim(id: ObjectId): Boolean { - val res = collection.findOneAndUpdate( - Filters.and( - Filters.eq("_id", id), - Filters.eq("simulation.state", "QUEUED") - ), - Updates.combine( - Updates.set("simulation.state", "RUNNING"), - Updates.set("simulation.heartbeat", Instant.now()) - ) - ) - return res != null - } - - /** - * Update the heartbeat of the specified scenario. - */ - public fun heartbeat(id: ObjectId) { - collection.findOneAndUpdate( - Filters.and( - Filters.eq("_id", id), - Filters.eq("simulation.state", "RUNNING") - ), - Updates.set("simulation.heartbeat", Instant.now()) - ) - } - - /** - * Mark the scenario as failed. - */ - public fun fail(id: ObjectId) { - collection.findOneAndUpdate( - Filters.eq("_id", id), - Updates.combine( - Updates.set("simulation.state", "FAILED"), - Updates.set("simulation.heartbeat", Instant.now()) - ) - ) - } - - /** - * Persist the specified results. - */ - public fun finish(id: ObjectId, results: List<WebExperimentMonitor.Result>) { - collection.findOneAndUpdate( - Filters.eq("_id", id), - Updates.combine( - Updates.set("simulation.state", "FINISHED"), - Updates.unset("simulation.time"), - Updates.set("results.total_requested_burst", results.map { it.totalRequestedBurst }), - Updates.set("results.total_granted_burst", results.map { it.totalGrantedBurst }), - Updates.set("results.total_overcommitted_burst", results.map { it.totalOvercommittedBurst }), - Updates.set("results.total_interfered_burst", results.map { it.totalInterferedBurst }), - Updates.set("results.mean_cpu_usage", results.map { it.meanCpuUsage }), - Updates.set("results.mean_cpu_demand", results.map { it.meanCpuDemand }), - Updates.set("results.mean_num_deployed_images", results.map { it.meanNumDeployedImages }), - Updates.set("results.max_num_deployed_images", results.map { it.maxNumDeployedImages }), - Updates.set("results.total_power_draw", results.map { it.totalPowerDraw }), - Updates.set("results.total_failure_slices", results.map { it.totalFailureSlices }), - Updates.set("results.total_failure_vm_slices", results.map { it.totalFailureVmSlices }), - Updates.set("results.total_vms_submitted", results.map { it.totalVmsSubmitted }), - Updates.set("results.total_vms_queued", results.map { it.totalVmsQueued }), - Updates.set("results.total_vms_finished", results.map { it.totalVmsFinished }), - Updates.set("results.total_vms_failed", results.map { it.totalVmsFailed }) - ) - ) - } -} diff --git a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/runner/web/TopologyParser.kt b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/runner/web/TopologyParser.kt deleted file mode 100644 index 2dd63340..00000000 --- a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/runner/web/TopologyParser.kt +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (c) 2020 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. - */ - -package org.opendc.runner.web - -import com.mongodb.client.AggregateIterable -import com.mongodb.client.MongoCollection -import com.mongodb.client.model.Aggregates -import com.mongodb.client.model.Field -import com.mongodb.client.model.Filters -import com.mongodb.client.model.Projections -import org.bson.Document -import org.bson.types.ObjectId -import org.opendc.format.environment.EnvironmentReader -import org.opendc.format.environment.MachineDef -import org.opendc.simulator.compute.SimMachineModel -import org.opendc.simulator.compute.model.MemoryUnit -import org.opendc.simulator.compute.model.ProcessingNode -import org.opendc.simulator.compute.model.ProcessingUnit -import org.opendc.simulator.compute.power.LinearPowerModel -import java.util.* - -/** - * A helper class that converts the MongoDB topology into an OpenDC environment. - */ -public class TopologyParser(private val collection: MongoCollection<Document>) { - - /** - * Parse the topology from the specified [id]. - */ - public fun read(id: ObjectId): EnvironmentReader { - val nodes = mutableListOf<MachineDef>() - val random = Random(0) - - for (machine in fetchMachines(id)) { - val clusterId = machine.get("rack_id").toString() - val position = machine.getInteger("position") - - val processors = machine.getList("cpus", Document::class.java).flatMap { cpu -> - val cores = cpu.getInteger("numberOfCores") - val speed = cpu.get("clockRateMhz", Number::class.java).toDouble() - // TODO Remove hardcoding of vendor - val node = ProcessingNode("Intel", "amd64", cpu.getString("name"), cores) - List(cores) { coreId -> - ProcessingUnit(node, coreId, speed) - } - } - val memoryUnits = machine.getList("memories", Document::class.java).map { memory -> - MemoryUnit( - "Samsung", - memory.getString("name"), - memory.get("speedMbPerS", Number::class.java).toDouble(), - memory.get("sizeMb", Number::class.java).toLong() - ) - } - - val energyConsumptionW = machine.getList("cpus", Document::class.java).sumBy { it.getInteger("energyConsumptionW") }.toDouble() - - nodes.add( - MachineDef( - UUID(random.nextLong(), random.nextLong()), - "node-$clusterId-$position", - mapOf("cluster" to clusterId), - SimMachineModel(processors, memoryUnits), - LinearPowerModel(2 * energyConsumptionW, energyConsumptionW * 0.5) - ) - ) - } - - return object : EnvironmentReader { - override fun read(): List<MachineDef> = nodes - override fun close() {} - } - } - - /** - * Fetch the metadata of the topology. - */ - private fun fetchName(id: ObjectId): String { - return collection.aggregate( - listOf( - Aggregates.match(Filters.eq("_id", id)), - Aggregates.project(Projections.include("name")) - ) - ) - .first()!! - .getString("name") - } - - /** - * Fetch a topology from the database with the specified [id]. - */ - private fun fetchMachines(id: ObjectId): AggregateIterable<Document> { - return collection.aggregate( - listOf( - Aggregates.match(Filters.eq("_id", id)), - Aggregates.project(Projections.fields(Document("racks", "\$rooms.tiles.rack"))), - Aggregates.unwind("\$racks"), - Aggregates.unwind("\$racks"), - Aggregates.replaceRoot("\$racks"), - Aggregates.addFields(Field("machines.rack_id", "\$_id")), - Aggregates.unwind("\$machines"), - Aggregates.replaceRoot("\$machines") - ) - ) - } -} diff --git a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/runner/web/WebExperimentMonitor.kt b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/runner/web/WebExperimentMonitor.kt deleted file mode 100644 index c913f82f..00000000 --- a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/runner/web/WebExperimentMonitor.kt +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Copyright (c) 2020 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. - */ - -package org.opendc.runner.web - -import mu.KotlinLogging -import org.opendc.compute.api.Server -import org.opendc.compute.api.ServerState -import org.opendc.compute.service.driver.Host -import org.opendc.compute.service.driver.HostState -import org.opendc.experiments.capelin.monitor.ExperimentMonitor -import org.opendc.experiments.capelin.telemetry.HostEvent -import kotlin.math.max - -/** - * An [ExperimentMonitor] that tracks the aggregate metrics for each repeat. - */ -public class WebExperimentMonitor : ExperimentMonitor { - private val logger = KotlinLogging.logger {} - - override fun reportVmStateChange(time: Long, server: Server, newState: ServerState) {} - - override fun reportHostStateChange(time: Long, host: Host, newState: HostState) { - logger.debug { "Host ${host.uid} changed state $newState [$time]" } - } - - override fun reportHostSlice( - time: Long, - requestedBurst: Long, - grantedBurst: Long, - overcommissionedBurst: Long, - interferedBurst: Long, - cpuUsage: Double, - cpuDemand: Double, - powerDraw: Double, - numberOfDeployedImages: Int, - host: Host, - ) { - processHostEvent( - HostEvent( - time, - 5 * 60 * 1000L, - host, - numberOfDeployedImages, - requestedBurst, - grantedBurst, - overcommissionedBurst, - interferedBurst, - cpuUsage, - cpuDemand, - powerDraw, - host.model.cpuCount - ) - ) - } - - private var hostAggregateMetrics: AggregateHostMetrics = AggregateHostMetrics() - private val hostMetrics: MutableMap<Host, HostMetrics> = mutableMapOf() - - private fun processHostEvent(event: HostEvent) { - val slices = event.duration / SLICE_LENGTH - - hostAggregateMetrics = AggregateHostMetrics( - hostAggregateMetrics.totalRequestedBurst + event.requestedBurst, - hostAggregateMetrics.totalGrantedBurst + event.grantedBurst, - hostAggregateMetrics.totalOvercommittedBurst + event.overcommissionedBurst, - hostAggregateMetrics.totalInterferedBurst + event.interferedBurst, - hostAggregateMetrics.totalPowerDraw + (event.duration * event.powerDraw) / 3600, - hostAggregateMetrics.totalFailureSlices + if (event.host.state != HostState.UP) slices else 0, - hostAggregateMetrics.totalFailureVmSlices + if (event.host.state != HostState.UP) event.vmCount * slices else 0 - ) - - hostMetrics.compute(event.host) { _, prev -> - HostMetrics( - (event.cpuUsage.takeIf { event.host.state == HostState.UP } ?: 0.0) + (prev?.cpuUsage ?: 0.0), - (event.cpuDemand.takeIf { event.host.state == HostState.UP } ?: 0.0) + (prev?.cpuDemand ?: 0.0), - event.vmCount + (prev?.vmCount ?: 0), - 1 + (prev?.count ?: 0) - ) - } - } - - private val SLICE_LENGTH: Long = 5 * 60 * 1000 - - public data class AggregateHostMetrics( - val totalRequestedBurst: Long = 0, - val totalGrantedBurst: Long = 0, - val totalOvercommittedBurst: Long = 0, - val totalInterferedBurst: Long = 0, - val totalPowerDraw: Double = 0.0, - val totalFailureSlices: Long = 0, - val totalFailureVmSlices: Long = 0, - ) - - public data class HostMetrics( - val cpuUsage: Double, - val cpuDemand: Double, - val vmCount: Long, - val count: Long - ) - - private var provisionerMetrics: AggregateProvisionerMetrics = AggregateProvisionerMetrics() - - override fun reportProvisionerMetrics( - time: Long, - totalHostCount: Int, - availableHostCount: Int, - totalVmCount: Int, - activeVmCount: Int, - inactiveVmCount: Int, - waitingVmCount: Int, - failedVmCount: Int - ) { - provisionerMetrics = AggregateProvisionerMetrics( - max(totalVmCount, provisionerMetrics.vmTotalCount), - max(waitingVmCount, provisionerMetrics.vmWaitingCount), - max(activeVmCount, provisionerMetrics.vmActiveCount), - max(inactiveVmCount, provisionerMetrics.vmInactiveCount), - max(failedVmCount, provisionerMetrics.vmFailedCount), - ) - } - - public data class AggregateProvisionerMetrics( - val vmTotalCount: Int = 0, - val vmWaitingCount: Int = 0, - val vmActiveCount: Int = 0, - val vmInactiveCount: Int = 0, - val vmFailedCount: Int = 0 - ) - - override fun close() {} - - public fun getResult(): Result { - return Result( - hostAggregateMetrics.totalRequestedBurst, - hostAggregateMetrics.totalGrantedBurst, - hostAggregateMetrics.totalOvercommittedBurst, - hostAggregateMetrics.totalInterferedBurst, - hostMetrics.map { it.value.cpuUsage / it.value.count }.average(), - hostMetrics.map { it.value.cpuDemand / it.value.count }.average(), - hostMetrics.map { it.value.vmCount.toDouble() / it.value.count }.average(), - hostMetrics.map { it.value.vmCount.toDouble() / it.value.count }.maxOrNull() ?: 0.0, - hostAggregateMetrics.totalPowerDraw, - hostAggregateMetrics.totalFailureSlices, - hostAggregateMetrics.totalFailureVmSlices, - provisionerMetrics.vmTotalCount, - provisionerMetrics.vmWaitingCount, - provisionerMetrics.vmInactiveCount, - provisionerMetrics.vmFailedCount, - ) - } - - public data class Result( - public val totalRequestedBurst: Long, - public val totalGrantedBurst: Long, - public val totalOvercommittedBurst: Long, - public val totalInterferedBurst: Long, - public val meanCpuUsage: Double, - public val meanCpuDemand: Double, - public val meanNumDeployedImages: Double, - public val maxNumDeployedImages: Double, - public val totalPowerDraw: Double, - public val totalFailureSlices: Long, - public val totalFailureVmSlices: Long, - public val totalVmsSubmitted: Int, - public val totalVmsQueued: Int, - public val totalVmsFinished: Int, - public val totalVmsFailed: Int - ) -} diff --git a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/ApiClient.kt b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/ApiClient.kt new file mode 100644 index 00000000..9f2656c4 --- /dev/null +++ b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/ApiClient.kt @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2021 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. + */ + +package org.opendc.web.client + +import com.fasterxml.jackson.annotation.JsonProperty +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule +import io.ktor.client.* +import io.ktor.client.features.auth.* +import io.ktor.client.features.auth.providers.* +import io.ktor.client.features.json.* +import io.ktor.client.request.* +import io.ktor.client.statement.* +import io.ktor.http.* +import org.opendc.web.client.model.* +import java.net.URI + +/** + * Client implementation for the OpenDC REST API (version 2). + * + * @param baseUrl The base url of the API. + * @param auth The authentication configuration for the client. + * @param client The HTTP client to use. + */ +public class ApiClient( + private val baseUrl: URI, + private val auth: AuthConfiguration, + private val audience: String = "https://api.opendc.org/v2/", + client: HttpClient = HttpClient {} +) : AutoCloseable { + /** + * The Ktor [HttpClient] that is used to communicate with the REST API. + */ + private val client = client.config { + install(JsonFeature) { + serializer = JacksonSerializer { + registerModule(JavaTimeModule()) + } + } + install(Auth) { + bearer { + loadTokens { requestToken() } + refreshTokens { requestToken() } + } + } + expectSuccess = false + } + + /** + * Retrieve the topology with the specified [id]. + */ + public suspend fun getPortfolio(id: String): Portfolio? { + val url = URLBuilder(Url(baseUrl)) + .path("portfolios", id) + .build() + return when (val result = client.get<ApiResult<Portfolio>>(url)) { + is ApiResult.Success -> result.data + else -> null + } + } + + /** + * Retrieve the scenario with the specified [id]. + */ + public suspend fun getScenario(id: String): Scenario? { + val url = URLBuilder(Url(baseUrl)) + .path("scenarios", id) + .build() + return when (val result = client.get<ApiResult<Scenario>>(url)) { + is ApiResult.Success -> result.data + else -> null + } + } + + /** + * Retrieve the topology with the specified [id]. + */ + public suspend fun getTopology(id: String): Topology? { + val url = URLBuilder(Url(baseUrl)) + .path("topologies", id) + .build() + return when (val result = client.get<ApiResult<Topology>>(url)) { + is ApiResult.Success -> result.data + else -> null + } + } + + /** + * Retrieve the available jobs. + */ + public suspend fun getJobs(): List<Job> { + val url = URLBuilder(Url(baseUrl)) + .path("jobs") + .build() + return when (val result = client.get<ApiResult<List<Job>>>(url)) { + is ApiResult.Success -> result.data + else -> emptyList() + } + } + + /** + * Update the specified job. + * + * @param id The identifier of the job. + * @param state The new state of the job. + * @param results The results of the job. + */ + public suspend fun updateJob(id: String, state: SimulationState, results: Map<String, Any> = emptyMap()): Boolean { + val url = URLBuilder(Url(baseUrl)) + .path("jobs", id) + .build() + + data class Request( + val state: SimulationState, + val results: Map<String, Any> + ) + + val res = client.post<HttpResponse> { + url(url) + contentType(ContentType.Application.Json) + body = Request(state, results) + } + return res.status.isSuccess() + } + + /** + * Request the auth token for the API. + */ + private suspend fun requestToken(): BearerTokens { + data class Request( + val audience: String, + @JsonProperty("grant_type") + val grantType: String, + @JsonProperty("client_id") + val clientId: String, + @JsonProperty("client_secret") + val clientSecret: String + ) + + data class Response( + @JsonProperty("access_token") + val accessToken: String, + @JsonProperty("token_type") + val tokenType: String, + val scope: String = "", + @JsonProperty("expires_in") + val expiresIn: Long + ) + + val result = client.post<Response> { + url(Url("https://${auth.domain}/oauth/token")) + contentType(ContentType.Application.Json) + body = Request(audience, "client_credentials", auth.clientId, auth.clientSecret) + } + + return BearerTokens(result.accessToken, "") + } + + override fun close() = client.close() +} diff --git a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/ApiResult.kt b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/ApiResult.kt new file mode 100644 index 00000000..a3df01c5 --- /dev/null +++ b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/ApiResult.kt @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2021 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. + */ + +package org.opendc.web.client + +import com.fasterxml.jackson.annotation.JsonSubTypes +import com.fasterxml.jackson.annotation.JsonTypeInfo + +/** + * Generic response model for the OpenDC API. + */ +@JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION) +@JsonSubTypes(JsonSubTypes.Type(ApiResult.Success::class), JsonSubTypes.Type(ApiResult.Failure::class)) +public sealed class ApiResult<out T> { + /** + * A response indicating everything is okay. + */ + public data class Success<out T>(val data: T) : ApiResult<T>() + + /** + * A response indicating a failure. + */ + public data class Failure<out T>(val message: String, val errors: List<String> = emptyList()) : ApiResult<T>() +} diff --git a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/AuthConfiguration.kt b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/AuthConfiguration.kt new file mode 100644 index 00000000..5dbf2f59 --- /dev/null +++ b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/AuthConfiguration.kt @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2021 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. + */ + +package org.opendc.web.client + +/** + * The authentication configuration for the API client. + */ +public data class AuthConfiguration( + val domain: String, + val clientId: String, + val clientSecret: String +) diff --git a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/Job.kt b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/Job.kt new file mode 100644 index 00000000..eeb65e49 --- /dev/null +++ b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/Job.kt @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2021 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. + */ + +package org.opendc.web.client.model + +import com.fasterxml.jackson.annotation.JsonProperty +import java.time.LocalDateTime + +/** + * A description of a simulation job. + */ +public data class Job( + @JsonProperty("_id") + val id: String, + val scenarioId: String, + val state: SimulationState, + val heartbeat: LocalDateTime, + val results: Map<String, Any> +) diff --git a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/Machine.kt b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/Machine.kt new file mode 100644 index 00000000..86d2d46f --- /dev/null +++ b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/Machine.kt @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2021 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. + */ + +package org.opendc.web.client.model + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties +import com.fasterxml.jackson.annotation.JsonProperty + +/** + * A machine in a rack. + */ +@JsonIgnoreProperties("id_legacy") +public data class Machine( + @JsonProperty("_id") + val id: String, + val position: Int, + val cpus: List<ProcessingUnit> = emptyList(), + val gpus: List<ProcessingUnit> = emptyList(), + @JsonProperty("memories") + val memory: List<MemoryUnit> = emptyList(), + @JsonProperty("storages") + val storage: List<MemoryUnit> = emptyList(), + val rackId: String? = null +) diff --git a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/MemoryUnit.kt b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/MemoryUnit.kt new file mode 100644 index 00000000..11e794e8 --- /dev/null +++ b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/MemoryUnit.kt @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2021 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. + */ + +package org.opendc.web.client.model + +import com.fasterxml.jackson.annotation.JsonProperty + +/** + * A memory unit in a system. + */ +public data class MemoryUnit( + @JsonProperty("_id") + val id: String, + val name: String, + val speedMbPerS: Double, + val sizeMb: Double, + val energyConsumptionW: Double +) diff --git a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/OperationalPhenomena.kt b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/OperationalPhenomena.kt new file mode 100644 index 00000000..ef5b4902 --- /dev/null +++ b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/OperationalPhenomena.kt @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2021 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. + */ + +package org.opendc.web.client.model + +/** + * Object describing the enabled operational phenomena for a scenario. + */ +public data class OperationalPhenomena( + val failuresEnabled: Boolean, + val performanceInterferenceEnabled: Boolean, + val schedulerName: String +) diff --git a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/Portfolio.kt b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/Portfolio.kt new file mode 100644 index 00000000..6904920b --- /dev/null +++ b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/Portfolio.kt @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2021 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. + */ + +package org.opendc.web.client.model + +import com.fasterxml.jackson.annotation.JsonProperty + +/** + * A portfolio in OpenDC. + */ +public data class Portfolio( + @JsonProperty("_id") + val id: String, + val projectId: String, + val name: String, + @JsonProperty("scenarioIds") + val scenarios: Set<String>, + val targets: PortfolioTargets +) diff --git a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/PortfolioTargets.kt b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/PortfolioTargets.kt new file mode 100644 index 00000000..07c11c19 --- /dev/null +++ b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/PortfolioTargets.kt @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2021 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. + */ + +package org.opendc.web.client.model + +/** + * The targets of a portfolio. + */ +public data class PortfolioTargets(val enabledMetrics: Set<String>, val repeatsPerScenario: Int) diff --git a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/ProcessingUnit.kt b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/ProcessingUnit.kt new file mode 100644 index 00000000..449b5c43 --- /dev/null +++ b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/ProcessingUnit.kt @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2021 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. + */ + +package org.opendc.web.client.model + +import com.fasterxml.jackson.annotation.JsonProperty + +/** + * A CPU model. + */ +public data class ProcessingUnit( + @JsonProperty("_id") + val id: String, + val name: String, + val clockRateMhz: Double, + val numberOfCores: Int, + val energyConsumptionW: Double +) diff --git a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/Rack.kt b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/Rack.kt new file mode 100644 index 00000000..a0464388 --- /dev/null +++ b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/Rack.kt @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2021 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. + */ + +package org.opendc.web.client.model + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties +import com.fasterxml.jackson.annotation.JsonProperty + +/** + * A rack in a datacenter. + */ +@JsonIgnoreProperties("id_legacy") +public class Rack( + @JsonProperty("_id") + val id: String, + val name: String, + val capacity: Int, + val powerCapacityW: Double, + val machines: List<Machine> +) diff --git a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/Room.kt b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/Room.kt new file mode 100644 index 00000000..f1b8f946 --- /dev/null +++ b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/Room.kt @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2021 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. + */ + +package org.opendc.web.client.model + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties +import com.fasterxml.jackson.annotation.JsonProperty + +/** + * A room in a datacenter. + */ +@JsonIgnoreProperties("id_legacy") +public data class Room( + @JsonProperty("_id") + val id: String, + val name: String, + val tiles: Set<RoomTile>, + val topologyId: String? = null, +) diff --git a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/RoomTile.kt b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/RoomTile.kt new file mode 100644 index 00000000..0b956262 --- /dev/null +++ b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/RoomTile.kt @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2021 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. + */ + +package org.opendc.web.client.model + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties +import com.fasterxml.jackson.annotation.JsonProperty + +/** + * A room tile. + */ +@JsonIgnoreProperties("id_legacy") +public data class RoomTile( + @JsonProperty("_id") + val id: String, + val positionX: Double, + val positionY: Double, + val rack: Rack? = null, + val roomId: String? = null, +) diff --git a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/Scenario.kt b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/Scenario.kt new file mode 100644 index 00000000..851ff980 --- /dev/null +++ b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/Scenario.kt @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2021 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. + */ + +package org.opendc.web.client.model + +import com.fasterxml.jackson.annotation.JsonProperty + +/** + * A simulation scenario. + */ +public data class Scenario( + @JsonProperty("_id") + val id: String, + val portfolioId: String, + val name: String, + val trace: ScenarioTrace, + val topology: ScenarioTopology, + @JsonProperty("operational") + val operationalPhenomena: OperationalPhenomena +) diff --git a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/ScenarioTopology.kt b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/ScenarioTopology.kt new file mode 100644 index 00000000..2b90f7ef --- /dev/null +++ b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/ScenarioTopology.kt @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2021 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. + */ + +package org.opendc.web.client.model + +/** + * The topology details for a scenario. + */ +public data class ScenarioTopology(val topologyId: String) diff --git a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/ScenarioTrace.kt b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/ScenarioTrace.kt new file mode 100644 index 00000000..adff6d97 --- /dev/null +++ b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/ScenarioTrace.kt @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2021 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. + */ + +package org.opendc.web.client.model + +/** + * The trace details of a scenario. + */ +public data class ScenarioTrace(val traceId: String, val loadSamplingFraction: Double) diff --git a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/SimulationState.kt b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/SimulationState.kt new file mode 100644 index 00000000..2eadd747 --- /dev/null +++ b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/SimulationState.kt @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2021 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. + */ + +package org.opendc.web.client.model + +/** + * The state of a simulation job. + */ +public enum class SimulationState { + QUEUED, CLAIMED, RUNNING, FINISHED, FAILED +} diff --git a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/Topology.kt b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/Topology.kt new file mode 100644 index 00000000..b59aba42 --- /dev/null +++ b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/client/model/Topology.kt @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2021 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. + */ + +package org.opendc.web.client.model + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties +import com.fasterxml.jackson.annotation.JsonProperty + +/** + * Model for an OpenDC topology. + */ +@JsonIgnoreProperties("id_legacy", "datacenter_id_legacy", "datetimeLastUpdated", "datetimeLastEdited") +public data class Topology( + @JsonProperty("_id") + val id: String, + val projectId: String, + val name: String, + val rooms: Set<Room>, +) diff --git a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/Main.kt b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/Main.kt new file mode 100644 index 00000000..59308e11 --- /dev/null +++ b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/Main.kt @@ -0,0 +1,348 @@ +/* + * Copyright (c) 2021 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. + */ + +package org.opendc.web.runner + +import com.github.ajalt.clikt.core.CliktCommand +import com.github.ajalt.clikt.parameters.options.* +import com.github.ajalt.clikt.parameters.types.file +import com.github.ajalt.clikt.parameters.types.long +import kotlinx.coroutines.* +import mu.KotlinLogging +import org.opendc.compute.workload.* +import org.opendc.compute.workload.topology.HostSpec +import org.opendc.compute.workload.topology.Topology +import org.opendc.compute.workload.topology.apply +import org.opendc.compute.workload.util.PerformanceInterferenceReader +import org.opendc.simulator.compute.kernel.interference.VmInterferenceModel +import org.opendc.simulator.compute.model.MachineModel +import org.opendc.simulator.compute.model.MemoryUnit +import org.opendc.simulator.compute.model.ProcessingNode +import org.opendc.simulator.compute.model.ProcessingUnit +import org.opendc.simulator.compute.power.LinearPowerModel +import org.opendc.simulator.compute.power.SimplePowerDriver +import org.opendc.simulator.core.runBlockingSimulation +import org.opendc.telemetry.compute.collectServiceMetrics +import org.opendc.telemetry.sdk.metrics.export.CoroutineMetricReader +import org.opendc.web.client.ApiClient +import org.opendc.web.client.AuthConfiguration +import org.opendc.web.client.model.Scenario +import java.io.File +import java.net.URI +import java.time.Duration +import java.util.* +import org.opendc.web.client.model.Portfolio as ClientPortfolio +import org.opendc.web.client.model.Topology as ClientTopology + +private val logger = KotlinLogging.logger {} + +/** + * Represents the CLI command for starting the OpenDC web runner. + */ +class RunnerCli : CliktCommand(name = "runner") { + /** + * The URL to the OpenDC API. + */ + private val apiUrl by option( + "--api-url", + help = "url to the OpenDC API", + envvar = "OPENDC_API_URL" + ) + .convert { URI(it) } + .default(URI("https://api.opendc.org/v2")) + + /** + * The auth domain to use. + */ + private val authDomain by option( + "--auth-domain", + help = "auth domain of the OpenDC API", + envvar = "AUTH0_DOMAIN" + ) + .required() + + /** + * The auth client ID to use. + */ + private val authClientId by option( + "--auth-id", + help = "auth client id of the OpenDC API", + envvar = "AUTH0_CLIENT_ID" + ) + .required() + + /** + * The auth client secret to use. + */ + private val authClientSecret by option( + "--auth-secret", + help = "auth client secret of the OpenDC API", + envvar = "AUTH0_CLIENT_SECRET" + ) + .required() + + /** + * The path to the traces directory. + */ + private val tracePath by option( + "--traces", + help = "path to the directory containing the traces", + envvar = "OPENDC_TRACES" + ) + .file(canBeFile = false) + .defaultLazy { File("traces/") } + + /** + * The maximum duration of a single experiment run. + */ + private val runTimeout by option( + "--run-timeout", + help = "maximum duration of experiment in seconds", + envvar = "OPENDC_RUN_TIMEOUT" + ) + .long() + .default(60L * 3) // Experiment may run for a maximum of three minutes + + /** + * Converge a single scenario. + */ + private suspend fun runScenario(portfolio: ClientPortfolio, scenario: Scenario, topology: Topology): List<WebComputeMetricExporter.Result> { + val id = scenario.id + + logger.info { "Constructing performance interference model" } + + val workloadLoader = ComputeWorkloadLoader(tracePath) + val interferenceGroups = let { + val path = tracePath.resolve(scenario.trace.traceId).resolve("performance-interference-model.json") + val operational = scenario.operationalPhenomena + val enabled = operational.performanceInterferenceEnabled + + if (!enabled || !path.exists()) { + return@let null + } + + PerformanceInterferenceReader().read(path.inputStream()) + } + + val targets = portfolio.targets + val results = (0 until targets.repeatsPerScenario).map { repeat -> + logger.info { "Starting repeat $repeat" } + withTimeout(runTimeout * 1000) { + val interferenceModel = interferenceGroups?.let { VmInterferenceModel(it, Random(repeat.toLong())) } + runRepeat(scenario, repeat, topology, workloadLoader, interferenceModel) + } + } + + logger.info { "Finished simulation for scenario $id" } + + return results + } + + /** + * Converge a single repeat. + */ + private suspend fun runRepeat( + scenario: Scenario, + repeat: Int, + topology: Topology, + workloadLoader: ComputeWorkloadLoader, + interferenceModel: VmInterferenceModel? + ): WebComputeMetricExporter.Result { + val exporter = WebComputeMetricExporter() + + try { + runBlockingSimulation { + val workloadName = scenario.trace.traceId + val workloadFraction = scenario.trace.loadSamplingFraction + + val seeder = Random(repeat.toLong()) + + val operational = scenario.operationalPhenomena + val computeScheduler = createComputeScheduler(operational.schedulerName, seeder) + val workload = trace(workloadName).sampleByLoad(workloadFraction) + + val failureModel = + if (operational.failuresEnabled) + grid5000(Duration.ofDays(7)) + else + null + + val simulator = ComputeWorkloadRunner( + coroutineContext, + clock, + computeScheduler, + failureModel, + interferenceModel.takeIf { operational.performanceInterferenceEnabled } + ) + + val metricReader = CoroutineMetricReader(this, simulator.producers, exporter, exportInterval = Duration.ofHours(1)) + + try { + // Instantiate the topology onto the simulator + simulator.apply(topology) + // Converge workload trace + simulator.run(workload.resolve(workloadLoader, seeder), seeder.nextLong()) + } finally { + simulator.close() + metricReader.close() + } + + val serviceMetrics = collectServiceMetrics(simulator.producers[0]) + logger.debug { + "Scheduler " + + "Success=${serviceMetrics.attemptsSuccess} " + + "Failure=${serviceMetrics.attemptsFailure} " + + "Error=${serviceMetrics.attemptsError} " + + "Pending=${serviceMetrics.serversPending} " + + "Active=${serviceMetrics.serversActive}" + } + } + } catch (cause: Throwable) { + logger.warn(cause) { "Experiment failed" } + } + + return exporter.getResult() + } + + private val POLL_INTERVAL = 30000L // ms = 30 s + private val HEARTBEAT_INTERVAL = 60000L // ms = 1 min + + override fun run(): Unit = runBlocking(Dispatchers.Default) { + logger.info { "Starting OpenDC web runner" } + + val client = ApiClient(baseUrl = apiUrl, AuthConfiguration(authDomain, authClientId, authClientSecret)) + val manager = ScenarioManager(client) + + logger.info { "Watching for queued scenarios" } + + while (true) { + val scenario = manager.findNext() + + if (scenario == null) { + delay(POLL_INTERVAL) + continue + } + + val id = scenario.id + + logger.info { "Found queued scenario $id: attempting to claim" } + + if (!manager.claim(id)) { + logger.info { "Failed to claim scenario" } + continue + } + + coroutineScope { + // Launch heartbeat process + val heartbeat = launch { + while (true) { + manager.heartbeat(id) + delay(HEARTBEAT_INTERVAL) + } + } + + try { + val scenarioModel = client.getScenario(id)!! + val portfolio = client.getPortfolio(scenarioModel.portfolioId)!! + val environment = convert(client.getTopology(scenarioModel.topology.topologyId)!!) + val results = runScenario(portfolio, scenarioModel, environment) + + logger.info { "Writing results to database" } + + manager.finish(id, results) + + logger.info { "Successfully finished scenario $id" } + } catch (e: Exception) { + logger.error(e) { "Scenario failed to finish" } + manager.fail(id) + } finally { + heartbeat.cancel() + } + } + } + } + + /** + * Convert the specified [topology] into an [Topology] understood by OpenDC. + */ + private fun convert(topology: ClientTopology): Topology { + return object : Topology { + + override fun resolve(): List<HostSpec> { + val res = mutableListOf<HostSpec>() + val random = Random(0) + + val machines = topology.rooms.asSequence() + .flatMap { room -> + room.tiles.flatMap { tile -> + tile.rack?.machines?.map { machine -> tile.rack to machine } ?: emptyList() + } + } + for ((rack, machine) in machines) { + val clusterId = rack.id + val position = machine.position + + val processors = machine.cpus.flatMap { cpu -> + val cores = cpu.numberOfCores + val speed = cpu.clockRateMhz + // TODO Remove hard coding of vendor + val node = ProcessingNode("Intel", "amd64", cpu.name, cores) + List(cores) { coreId -> + ProcessingUnit(node, coreId, speed) + } + } + val memoryUnits = machine.memory.map { memory -> + MemoryUnit( + "Samsung", + memory.name, + memory.speedMbPerS, + memory.sizeMb.toLong() + ) + } + + val energyConsumptionW = machine.cpus.sumOf { it.energyConsumptionW } + val powerModel = LinearPowerModel(2 * energyConsumptionW, energyConsumptionW * 0.5) + val powerDriver = SimplePowerDriver(powerModel) + + val spec = HostSpec( + UUID(random.nextLong(), random.nextLong()), + "node-$clusterId-$position", + mapOf("cluster" to clusterId), + MachineModel(processors, memoryUnits), + powerDriver + ) + + res += spec + } + + return res + } + + override fun toString(): String = "WebRunnerTopologyFactory" + } + } +} + +/** + * Main entry point of the runner. + */ +fun main(args: Array<String>): Unit = RunnerCli().main(args) diff --git a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/ScenarioManager.kt b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/ScenarioManager.kt new file mode 100644 index 00000000..1ee835a6 --- /dev/null +++ b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/ScenarioManager.kt @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2021 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. + */ + +package org.opendc.web.runner + +import org.opendc.web.client.ApiClient +import org.opendc.web.client.model.Job +import org.opendc.web.client.model.SimulationState + +/** + * Manages the queue of scenarios that need to be processed. + */ +public class ScenarioManager(private val client: ApiClient) { + /** + * Find the next job that the simulator needs to process. + */ + public suspend fun findNext(): Job? { + return client.getJobs().firstOrNull() + } + + /** + * Claim the simulation job with the specified id. + */ + public suspend fun claim(id: String): Boolean { + return client.updateJob(id, SimulationState.CLAIMED) + } + + /** + * Update the heartbeat of the specified scenario. + */ + public suspend fun heartbeat(id: String) { + client.updateJob(id, SimulationState.RUNNING) + } + + /** + * Mark the scenario as failed. + */ + public suspend fun fail(id: String) { + client.updateJob(id, SimulationState.FAILED) + } + + /** + * Persist the specified results. + */ + public suspend fun finish(id: String, results: List<WebComputeMetricExporter.Result>) { + client.updateJob( + id, SimulationState.FINISHED, + mapOf( + "total_requested_burst" to results.map { it.totalActiveTime + it.totalIdleTime }, + "total_granted_burst" to results.map { it.totalActiveTime }, + "total_overcommitted_burst" to results.map { it.totalStealTime }, + "total_interfered_burst" to results.map { it.totalLostTime }, + "mean_cpu_usage" to results.map { it.meanCpuUsage }, + "mean_cpu_demand" to results.map { it.meanCpuDemand }, + "mean_num_deployed_images" to results.map { it.meanNumDeployedImages }, + "max_num_deployed_images" to results.map { it.maxNumDeployedImages }, + "total_power_draw" to results.map { it.totalPowerDraw }, + "total_failure_slices" to results.map { it.totalFailureSlices }, + "total_failure_vm_slices" to results.map { it.totalFailureVmSlices }, + "total_vms_submitted" to results.map { it.totalVmsSubmitted }, + "total_vms_queued" to results.map { it.totalVmsQueued }, + "total_vms_finished" to results.map { it.totalVmsFinished }, + "total_vms_failed" to results.map { it.totalVmsFailed } + ) + ) + } +} diff --git a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/WebComputeMetricExporter.kt b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/WebComputeMetricExporter.kt new file mode 100644 index 00000000..7913660d --- /dev/null +++ b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/WebComputeMetricExporter.kt @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2021 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. + */ + +package org.opendc.web.runner + +import org.opendc.telemetry.compute.ComputeMetricExporter +import org.opendc.telemetry.compute.ComputeMonitor +import org.opendc.telemetry.compute.table.HostData +import org.opendc.telemetry.compute.table.ServiceData +import kotlin.math.max +import kotlin.math.roundToLong + +/** + * A [ComputeMonitor] that tracks the aggregate metrics for each repeat. + */ +class WebComputeMetricExporter : ComputeMetricExporter() { + override fun record(data: HostData) { + val slices = data.downtime / SLICE_LENGTH + + hostAggregateMetrics = AggregateHostMetrics( + hostAggregateMetrics.totalActiveTime + data.cpuActiveTime, + hostAggregateMetrics.totalIdleTime + data.cpuIdleTime, + hostAggregateMetrics.totalStealTime + data.cpuStealTime, + hostAggregateMetrics.totalLostTime + data.cpuLostTime, + hostAggregateMetrics.totalPowerDraw + data.powerTotal, + hostAggregateMetrics.totalFailureSlices + slices, + hostAggregateMetrics.totalFailureVmSlices + data.guestsRunning * slices + ) + + hostMetrics.compute(data.host.id) { _, prev -> + HostMetrics( + data.cpuUsage + (prev?.cpuUsage ?: 0.0), + data.cpuDemand + (prev?.cpuDemand ?: 0.0), + data.guestsRunning + (prev?.instanceCount ?: 0), + 1 + (prev?.count ?: 0) + ) + } + } + + private var hostAggregateMetrics: AggregateHostMetrics = AggregateHostMetrics() + private val hostMetrics: MutableMap<String, HostMetrics> = mutableMapOf() + private val SLICE_LENGTH: Long = 5 * 60L + + data class AggregateHostMetrics( + val totalActiveTime: Long = 0L, + val totalIdleTime: Long = 0L, + val totalStealTime: Long = 0L, + val totalLostTime: Long = 0L, + val totalPowerDraw: Double = 0.0, + val totalFailureSlices: Double = 0.0, + val totalFailureVmSlices: Double = 0.0, + ) + + data class HostMetrics( + val cpuUsage: Double, + val cpuDemand: Double, + val instanceCount: Long, + val count: Long + ) + + private var serviceMetrics: AggregateServiceMetrics = AggregateServiceMetrics() + + override fun record(data: ServiceData) { + serviceMetrics = AggregateServiceMetrics( + max(data.attemptsSuccess, serviceMetrics.vmTotalCount), + max(data.serversPending, serviceMetrics.vmWaitingCount), + max(data.serversActive, serviceMetrics.vmActiveCount), + max(0, serviceMetrics.vmInactiveCount), + max(data.attemptsFailure, serviceMetrics.vmFailedCount), + ) + } + + data class AggregateServiceMetrics( + val vmTotalCount: Int = 0, + val vmWaitingCount: Int = 0, + val vmActiveCount: Int = 0, + val vmInactiveCount: Int = 0, + val vmFailedCount: Int = 0 + ) + + fun getResult(): Result { + return Result( + hostAggregateMetrics.totalActiveTime, + hostAggregateMetrics.totalIdleTime, + hostAggregateMetrics.totalStealTime, + hostAggregateMetrics.totalLostTime, + hostMetrics.map { it.value.cpuUsage / it.value.count }.average(), + hostMetrics.map { it.value.cpuDemand / it.value.count }.average(), + hostMetrics.map { it.value.instanceCount.toDouble() / it.value.count }.average(), + hostMetrics.map { it.value.instanceCount.toDouble() / it.value.count }.maxOrNull() ?: 0.0, + hostAggregateMetrics.totalPowerDraw, + hostAggregateMetrics.totalFailureSlices.roundToLong(), + hostAggregateMetrics.totalFailureVmSlices.roundToLong(), + serviceMetrics.vmTotalCount, + serviceMetrics.vmWaitingCount, + serviceMetrics.vmInactiveCount, + serviceMetrics.vmFailedCount, + ) + } + + data class Result( + val totalActiveTime: Long, + val totalIdleTime: Long, + val totalStealTime: Long, + val totalLostTime: Long, + val meanCpuUsage: Double, + val meanCpuDemand: Double, + val meanNumDeployedImages: Double, + val maxNumDeployedImages: Double, + val totalPowerDraw: Double, + val totalFailureSlices: Long, + val totalFailureVmSlices: Long, + val totalVmsSubmitted: Int, + val totalVmsQueued: Int, + val totalVmsFinished: Int, + val totalVmsFailed: Int + ) +} diff --git a/opendc-web/opendc-web-runner/src/main/resources/log4j2.xml b/opendc-web/opendc-web-runner/src/main/resources/log4j2.xml index 503bc5dc..ad99cc00 100644 --- a/opendc-web/opendc-web-runner/src/main/resources/log4j2.xml +++ b/opendc-web/opendc-web-runner/src/main/resources/log4j2.xml @@ -36,7 +36,7 @@ <AppenderRef ref="Console"/> <AppenderRef ref="Sentry"/> </Logger> - <Logger name="org.opendc.runner" level="info" additivity="false"> + <Logger name="org.opendc.web.runner" level="info" additivity="false"> <AppenderRef ref="Console"/> <AppenderRef ref="Sentry"/> </Logger> diff --git a/opendc-web/opendc-web-runner/src/test/kotlin/org/opendc/web/client/ApiClientTest.kt b/opendc-web/opendc-web-runner/src/test/kotlin/org/opendc/web/client/ApiClientTest.kt new file mode 100644 index 00000000..3a0730a6 --- /dev/null +++ b/opendc-web/opendc-web-runner/src/test/kotlin/org/opendc/web/client/ApiClientTest.kt @@ -0,0 +1,264 @@ +/* + * Copyright (c) 2021 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. + */ + +package org.opendc.web.client + +import io.ktor.client.* +import io.ktor.client.engine.mock.* +import io.ktor.http.* +import kotlinx.coroutines.runBlocking +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.Assertions.assertNull +import org.junit.jupiter.api.Test +import java.net.URI + +/** + * Test suite for the [ApiClient] class. + */ +class ApiClientTest { + /** + * The Ktor [HttpClient] instance. + */ + private val ktor = HttpClient(MockEngine) { + engine { + addHandler { request -> + when (request.url.fullPath) { + "/oauth/token" -> { + val responseHeaders = headersOf("Content-Type" to listOf(ContentType.Application.Json.toString())) + respond( + """ + { + "access_token": "eyJz93a...k4laUWw", + "token_type": "Bearer", + "expires_in": 86400 + } + """.trimIndent(), + headers = responseHeaders + ) + } + "/portfolios/5fda5daa97dca438e7cb0a4c" -> { + val responseHeaders = headersOf("Content-Type" to listOf(ContentType.Application.Json.toString())) + respond( + """ + { + "data": { + "_id": "string", + "projectId": "string", + "name": "string", + "scenarioIds": [ + "string" + ], + "targets": { + "enabledMetrics": [ + "string" + ], + "repeatsPerScenario": 0 + } + } + } + """.trimIndent(), + headers = responseHeaders + ) + } + "/portfolios/x" -> { + val responseHeaders = headersOf("Content-Type" to listOf(ContentType.Application.Json.toString())) + respond( + """ + { + "message": "Not Found" + } + """.trimIndent(), + headers = responseHeaders, status = HttpStatusCode.NotFound + ) + } + "/scenarios/5fda5db297dca438e7cb0a4d" -> { + val responseHeaders = headersOf("Content-Type" to listOf(ContentType.Application.Json.toString())) + respond( + """ + { + "data": { + "_id": "string", + "portfolioId": "string", + "name": "string", + "trace": { + "traceId": "string", + "loadSamplingFraction": 0 + }, + "topology": { + "topologyId": "string" + }, + "operational": { + "failuresEnabled": true, + "performanceInterferenceEnabled": true, + "schedulerName": "string" + } + } + } + """.trimIndent(), + headers = responseHeaders + ) + } + "/scenarios/x" -> { + val responseHeaders = headersOf("Content-Type" to listOf(ContentType.Application.Json.toString())) + respond( + """ + { + "message": "Not Found" + } + """.trimIndent(), + headers = responseHeaders, status = HttpStatusCode.NotFound + ) + } + "/topologies/5f9825a6cf6e4c24e380b86f" -> { + val responseHeaders = headersOf("Content-Type" to listOf(ContentType.Application.Json.toString())) + respond( + """ + { + "data": { + "_id": "string", + "projectId": "string", + "name": "string", + "rooms": [ + { + "_id": "string", + "name": "string", + "tiles": [ + { + "_id": "string", + "positionX": 0, + "positionY": 0, + "rack": { + "_id": "string", + "name": "string", + "capacity": 0, + "powerCapacityW": 0, + "machines": [ + { + "_id": "string", + "position": 0, + "cpus": [ + { + "_id": "string", + "name": "string", + "clockRateMhz": 0, + "numberOfCores": 0 + } + ], + "gpus": [ + { + "_id": "string", + "name": "string", + "clockRateMhz": 0, + "numberOfCores": 0 + } + ], + "memories": [ + { + "_id": "string", + "name": "string", + "speedMbPerS": 0, + "sizeMb": 0 + } + ], + "storages": [ + { + "_id": "string", + "name": "string", + "speedMbPerS": 0, + "sizeMb": 0 + } + ] + } + ] + } + } + ] + } + ] + } + } + """.trimIndent(), + headers = responseHeaders + ) + } + "/topologies/x" -> { + val responseHeaders = + headersOf("Content-Type" to listOf(ContentType.Application.Json.toString())) + respond( + """ + { + "message": "Not Found" + } + """.trimIndent(), + headers = responseHeaders, status = HttpStatusCode.NotFound + ) + } + else -> error("Unhandled ${request.url}") + } + } + } + } + + private val auth = AuthConfiguration("auth.opendc.org", "a", "b") + + @Test + fun testPortfolioExists(): Unit = runBlocking { + val client = ApiClient(URI("http://localhost:8081"), auth, client = ktor) + val portfolio = client.getPortfolio("5fda5daa97dca438e7cb0a4c") + assertNotNull(portfolio) + } + + @Test + fun testPortfolioDoesNotExists(): Unit = runBlocking { + val client = ApiClient(URI("http://localhost:8081"), auth, client = ktor) + val portfolio = client.getPortfolio("x") + assertNull(portfolio) + } + + @Test + fun testScenarioExists(): Unit = runBlocking { + val client = ApiClient(URI("http://localhost:8081"), auth, client = ktor) + val scenario = client.getScenario("5fda5db297dca438e7cb0a4d") + assertNotNull(scenario) + } + + @Test + fun testScenarioDoesNotExists(): Unit = runBlocking { + val client = ApiClient(URI("http://localhost:8081"), auth, client = ktor) + val scenario = client.getScenario("x") + assertNull(scenario) + } + + @Test + fun testTopologyExists(): Unit = runBlocking { + val client = ApiClient(URI("http://localhost:8081"), auth, client = ktor) + val topology = client.getTopology("5f9825a6cf6e4c24e380b86f") + assertNotNull(topology) + } + + @Test + fun testTopologyDoesNotExists(): Unit = runBlocking { + val client = ApiClient(URI("http://localhost:8081"), auth, client = ktor) + val topology = client.getTopology("x") + assertNull(topology) + } +} diff --git a/opendc-web/opendc-web-ui/.eslintrc b/opendc-web/opendc-web-ui/.eslintrc new file mode 100644 index 00000000..1446fa02 --- /dev/null +++ b/opendc-web/opendc-web-ui/.eslintrc @@ -0,0 +1,16 @@ +{ + "extends": ["next", "eslint:recommended"], + "env": { + "browser": true, + "node": true, + "es6": true + }, + "overrides": [ + { + "files": ["src/**/*.test.js"], + "env": { + "jest": true + } + } + ] +} diff --git a/opendc-web/opendc-web-ui/.gitignore b/opendc-web/opendc-web-ui/.gitignore index 4fa931fe..3340b9ee 100644 --- a/opendc-web/opendc-web-ui/.gitignore +++ b/opendc-web/opendc-web-ui/.gitignore @@ -22,7 +22,9 @@ yarn-error.log* /.idea # Environment variables -.env +.env.local # Sass output *.css + +/.next diff --git a/opendc-web/opendc-web-ui/Dockerfile b/opendc-web/opendc-web-ui/Dockerfile index 86a18a03..15a92068 100644 --- a/opendc-web/opendc-web-ui/Dockerfile +++ b/opendc-web/opendc-web-ui/Dockerfile @@ -1,22 +1,28 @@ -FROM node:15 AS staging +FROM node:16 AS staging MAINTAINER OpenDC Maintainers <opendc@atlarge-research.com> # Copy package details COPY ./package.json ./yarn.lock /opendc/ -RUN cd /opendc && yarn +RUN cd /opendc && yarn install --frozen-lockfile # Build frontend -FROM node:15 AS build +FROM node:16 AS build COPY ./ /opendc COPY --from=staging /opendc/node_modules /opendc/node_modules RUN cd /opendc/ \ - && export REACT_APP_OAUTH_CLIENT_ID="\\\$REACT_APP_OAUTH_CLIENT_ID" \ + # Environmental variables that will be substituted during image runtime + && export NEXT_PUBLIC_API_BASE_URL="%%NEXT_PUBLIC_API_BASE_URL%%" \ + NEXT_PUBLIC_SENTRY_DSN="%%NEXT_PUBLIC_SENTRY_DSN%%" \ + NEXT_PUBLIC_AUTH0_DOMAIN="%%NEXT_PUBLIC_AUTH0_DOMAIN%%" \ + NEXT_PUBLIC_AUTH0_CLIENT_ID="%%NEXT_PUBLIC_AUTH0_CLIENT_ID%%" \ + NEXT_PUBLIC_AUTH0_AUDIENCE="%%NEXT_PUBLIC_AUTH0_AUDIENCE%%" \ && yarn build \ - && mv build/index.html build/index.html.template + && yarn cache clean --all \ + && mv .next .next.template -# Setup nginx to serve the frontend -FROM nginx:1.20 -COPY --from=build /opendc/scripts/envsubst-html.sh /docker-entrypoint.d/00-envsubst-html.sh -COPY --from=build /opendc/build /usr/share/nginx/html -COPY nginx.conf /etc/nginx/conf.d/default.conf + +FROM node:16-slim +COPY --from=build /opendc /opendc +WORKDIR /opendc +CMD ./scripts/envsubst.sh; yarn start diff --git a/opendc-web/opendc-web-ui/README.md b/opendc-web/opendc-web-ui/README.md index f3a58e7a..d562f2a4 100644 --- a/opendc-web/opendc-web-ui/README.md +++ b/opendc-web/opendc-web-ui/README.md @@ -7,16 +7,19 @@ Collaborative Datacenter Simulation and Exploration for Everybody </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`. - +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 [Next.js](https://nextjs.org/). ## 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. +Looking for the full OpenDC stack? Check out the [deployment guide](../../docs/deploy.md) 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: +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 @@ -24,17 +27,22 @@ 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. +First, you need to set up an [Auth0](https://auth0.com) application. Check +the [documentation in the deployment guide](../../docs/deploy.md) if you're not sure how to do this. Once you have such +an ID, you need to set it as environment variable `NEXT_PUBLIC_AUTH0_CLIENT_ID` and `NEXT_PUBLIC_AUTH0_DOMAIN` +One way of doing this is to create an `.env.local` file with content `NEXT_PUBLIC_AUTH0_CLIENT_ID=YOUR_ID` and +`NEXT_PUBLIC_AUTH0_DOMAIN=YOUR_AUTH0_DOMAIN` in the root directory of this repo. Once you've set this variable, start the OpenDC `docker-compose` setup. See the root README for instructions on this. - + Now, you're ready to start the development server: ```bash -yarn start +yarn dev ``` -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. +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: @@ -42,47 +50,68 @@ To compile everything for camera-ready deployment, use the following command: yarn build ``` +You can run the production server using Next.js as follows: + +```bash +yarn start +``` ## 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 needing to be served is a `index.html` file. +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`. ### Pages -All pages are represented by a component in the `src/pages` directory. There are components for the following pages: +All pages are represented by a component in the `src/pages` directory, following +the [Next.js conventions](https://nextjs.org/docs/routing/introduction) for routing. There are components for the +following pages: -**Home.js** - Entry page (`/`) +**index.js** - Entry page (`/`) -**Projects.js** - Overview of projects of the user (`/projects`) +**projects/index.js** - Overview of projects of the user (`/projects`) -**App.js** - Main application, with datacenter construction and simulation UI (`/projects/:projectId` and `/projects/:projectId/portfolios/:portfolioId`) +**projects/[project]/index.js** - Main application, with datacenter construction and simulation UI (`/projects/:projectId` +and `/projects/:projectId/portfolios/:portfolioId`) -**Profile.js** - Profile of the current user (`/profile`) +**profile.js** - Profile of the current user (`/profile`) -**NotFound.js** - 404 page to appear when the route is invalid (`/*`) +**404.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. +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. +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](https://redux.js.org/). +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](https://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. - +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. +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. +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 diff --git a/opendc-web/opendc-web-ui/next.config.js b/opendc-web/opendc-web-ui/next.config.js new file mode 100644 index 00000000..1dfe4156 --- /dev/null +++ b/opendc-web/opendc-web-ui/next.config.js @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2021 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. + */ + +// PatternFly 4 uses global CSS imports in its distribution files. Therefore, +// we need to transpile the modules before we can use them. +const withTM = require('next-transpile-modules')([ + '@patternfly/react-core', + '@patternfly/react-styles', + '@patternfly/react-table', + '@patternfly/react-tokens', +]) + +module.exports = withTM({ + reactStrictMode: true, + experimental: { + eslint: true, + }, + async redirects() { + return [ + { + source: '/', + destination: '/projects', + permanent: true, + }, + ] + }, + webpack: (config, options) => { + if (options.dev) { + config.optimization.splitChunks = { + cacheGroups: { + vendor: { + test: /[\\/]node_modules[\\/]/, + name: 'transpiled-modules', + chunks: 'all', + }, + }, + } + } + return config + } +}) diff --git a/opendc-web/opendc-web-ui/nginx.conf b/opendc-web/opendc-web-ui/nginx.conf deleted file mode 100644 index 1b4e3a73..00000000 --- a/opendc-web/opendc-web-ui/nginx.conf +++ /dev/null @@ -1,15 +0,0 @@ -server { - listen 80; - server_name opendc.org; - - location / { - root /usr/share/nginx/html; - index index.html index.htm; - try_files $uri $uri/ /index.html; - } - - error_page 500 502 503 504 /50x.html; - location = /50x.html { - root /usr/share/nginx/html; - } -} diff --git a/opendc-web/opendc-web-ui/package.json b/opendc-web/opendc-web-ui/package.json index c64a3c11..427e3cbd 100644 --- a/opendc-web/opendc-web-ui/package.json +++ b/opendc-web/opendc-web-ui/package.json @@ -10,46 +10,54 @@ ], "homepage": "http://opendc.org", "bugs": { - "url": "https://github.com/atlarge-research/opendc-frontend/issues", + "url": "https://github.com/atlarge-research/opendc/issues", "email": "opendc@atlarge-research.com" }, - "author": "Georgios Andreadis <g.andreadis@atlarge-research.com> (https://gandreadis.com/)", + "author": "OpenDC Maintainers <opendc@atlarge-research.com>", "license": "MIT", "private": true, - "proxy": "http://localhost:8081", "dependencies": { - "@sentry/react": "^5.27.3", - "@sentry/tracing": "^5.27.3", - "approximate-number": "~2.0.0", - "bootstrap": "4.5.3", - "classnames": "~2.2.5", - "husky": "~4.2.5", - "konva": "~6.0.0", - "lint-staged": "~10.2.2", - "mathjs": "~7.1.0", - "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", - "reactstrap": "^8.6.0", - "recharts": "~1.8.5", - "redux": "~4.0.5", - "redux-localstorage": "~0.4.1", + "@auth0/auth0-react": "^1.7.0", + "@fortawesome/fontawesome-svg-core": "^1.2.35", + "@fortawesome/free-brands-svg-icons": "^5.15.3", + "@fortawesome/free-solid-svg-icons": "^5.15.3", + "@fortawesome/react-fontawesome": "^0.1.14", + "@patternfly/react-core": "^4.152.4", + "@patternfly/react-icons": "^4.11.14", + "@patternfly/react-table": "^4.29.58", + "@sentry/react": "^5.30.0", + "@sentry/tracing": "^5.30.0", + "approximate-number": "^2.1.0", + "classnames": "^2.3.1", + "husky": "^4.3.8", + "immer": "^9.0.6", + "konva": "^7.2.5", + "lint-staged": "^10.5.4", + "mathjs": "^7.6.0", + "next": "^11.1.2", + "next-transpile-modules": "^8.0.0", + "normalizr": "^3.6.1", + "prettier": "^2.3.2", + "prop-types": "^15.7.2", + "react": "^17.0.2", + "react-dom": "^17.0.2", + "react-hotkeys": "^2.0.0", + "react-konva": "^17.0.2-5", + "react-query": "^3.22.0", + "react-redux": "^7.2.5", + "recharts": "^2.1.2", + "redux": "^4.1.1", "redux-logger": "~3.0.6", "redux-saga": "~1.1.3", "redux-thunk": "~2.3.0", - "sass": "^1.32.12", - "socket.io-client": "~2.3.0", - "svgsaver": "~0.9.0", - "uuidv4": "~6.1.1" + "sass": "^1.39.0", + "svgsaver": "^0.9.0", + "use-resize-observer": "^8.0.0", + "uuidv4": "^6.2.12" + }, + "devDependencies": { + "eslint": "^7.32.0", + "eslint-config-next": "^11.1.2" }, "lint-staged": { "src/**/*.{js,jsx,json}": [ @@ -60,10 +68,10 @@ "scripts": { "format": "prettier --write src", "precommit": "lint-staged", - "start": "react-scripts start", - "build": "react-scripts build", - "test": "react-scripts test --env=jsdom", - "eject": "react-scripts eject" + "dev": "next dev", + "build": "next build", + "start": "next start", + "export": "next export -o build" }, "browserslist": { "production": [ diff --git a/opendc-web/opendc-web-ui/public/index.html b/opendc-web/opendc-web-ui/public/index.html deleted file mode 100644 index 19b80e29..00000000 --- a/opendc-web/opendc-web-ui/public/index.html +++ /dev/null @@ -1,70 +0,0 @@ -<!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 href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap" 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> - <script> - /* Dynamic app variables */ - window.config_overrides = { - REACT_APP_API_BASE_URL: "$REACT_APP_API_BASE_URL", - REACT_APP_OAUTH_CLIENT_ID: "$REACT_APP_OAUTH_CLIENT_ID", - REACT_APP_SENTRY_DSN: "$REACT_APP_SENTRY_DSN", - }; - </script> -</head> -<body> -<noscript> - You need to enable JavaScript to run this app. -</noscript> -<div id="root"></div> -</body> -</html> diff --git a/opendc-web/opendc-web-ui/scripts/envsubst-html.sh b/opendc-web/opendc-web-ui/scripts/envsubst-html.sh deleted file mode 100755 index 8ca12e8a..00000000 --- a/opendc-web/opendc-web-ui/scripts/envsubst-html.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/sh - -set -e - -# We can use simple version of envsubst execution as -# envsubst < /usr/share/nginx/html/index.html.template > /usr/share/nginx/html/index.html -# but it replaces everything that looks like environment variable substitution -# so it affects `default values` approach. -# we need to replace only provided environment variables. - -auto_envsubst() { - template_path="/usr/share/nginx/html/index.html.template" - output_path="/usr/share/nginx/html/index.html" - defined_envs=$(printf '${%s} ' $(env | cut -d= -f1)) - envsubst "$defined_envs" < "$template_path" > "$output_path" -} - -auto_envsubst -exit 0 diff --git a/opendc-web/opendc-web-ui/scripts/envsubst.sh b/opendc-web/opendc-web-ui/scripts/envsubst.sh new file mode 100755 index 00000000..d7ae9ecb --- /dev/null +++ b/opendc-web/opendc-web-ui/scripts/envsubst.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +set -e + +auto_envsubst() { + input_path="/opendc/.next.template" + output_path="/opendc/.next" + + cp -r "$input_path" "$output_path" + find "$output_path" -type f -name '*.js' -exec perl -pi -e 's/%%(NEXT_PUBLIC_[_A-Z0-9]+)%%/$ENV{$1}/g' {} \; +} + +auto_envsubst +exit 0 diff --git a/opendc-web/opendc-web-ui/src/actions/auth.js b/opendc-web/opendc-web-ui/src/actions/auth.js deleted file mode 100644 index 38c1a782..00000000 --- a/opendc-web/opendc-web-ui/src/actions/auth.js +++ /dev/null @@ -1,23 +0,0 @@ -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/opendc-web/opendc-web-ui/src/actions/map.js b/opendc-web/opendc-web-ui/src/actions/map.js deleted file mode 100644 index 0d49d849..00000000 --- a/opendc-web/opendc-web-ui/src/actions/map.js +++ /dev/null @@ -1,82 +0,0 @@ -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/opendc-web/opendc-web-ui/src/actions/modals/portfolios.js b/opendc-web/opendc-web-ui/src/actions/modals/portfolios.js deleted file mode 100644 index f6dce2e3..00000000 --- a/opendc-web/opendc-web-ui/src/actions/modals/portfolios.js +++ /dev/null @@ -1,14 +0,0 @@ -export const OPEN_NEW_PORTFOLIO_MODAL = 'OPEN_NEW_PORTFOLIO_MODAL' -export const CLOSE_NEW_PORTFOLIO_MODAL = 'CLOSE_PORTFOLIO_MODAL' - -export function openNewPortfolioModal() { - return { - type: OPEN_NEW_PORTFOLIO_MODAL, - } -} - -export function closeNewPortfolioModal() { - return { - type: CLOSE_NEW_PORTFOLIO_MODAL, - } -} diff --git a/opendc-web/opendc-web-ui/src/actions/modals/prefabs.js b/opendc-web/opendc-web-ui/src/actions/modals/prefabs.js deleted file mode 100644 index 826565d2..00000000 --- a/opendc-web/opendc-web-ui/src/actions/modals/prefabs.js +++ /dev/null @@ -1,14 +0,0 @@ -export const OPEN_NEW_PREFAB_MODAL = 'OPEN_NEW_PREFAB_MODAL' -export const CLOSE_NEW_PREFAB_MODAL = 'CLOSE_PREFAB_MODAL' - -export function openNewPrefabModal() { - return { - type: OPEN_NEW_PREFAB_MODAL, - } -} - -export function closeNewPrefabModal() { - return { - type: CLOSE_NEW_PREFAB_MODAL, - } -} diff --git a/opendc-web/opendc-web-ui/src/actions/modals/profile.js b/opendc-web/opendc-web-ui/src/actions/modals/profile.js deleted file mode 100644 index 39c72c03..00000000 --- a/opendc-web/opendc-web-ui/src/actions/modals/profile.js +++ /dev/null @@ -1,14 +0,0 @@ -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/opendc-web/opendc-web-ui/src/actions/modals/projects.js b/opendc-web/opendc-web-ui/src/actions/modals/projects.js deleted file mode 100644 index d1043cbb..00000000 --- a/opendc-web/opendc-web-ui/src/actions/modals/projects.js +++ /dev/null @@ -1,14 +0,0 @@ -export const OPEN_NEW_PROJECT_MODAL = 'OPEN_NEW_PROJECT_MODAL' -export const CLOSE_NEW_PROJECT_MODAL = 'CLOSE_PROJECT_MODAL' - -export function openNewProjectModal() { - return { - type: OPEN_NEW_PROJECT_MODAL, - } -} - -export function closeNewProjectModal() { - return { - type: CLOSE_NEW_PROJECT_MODAL, - } -} diff --git a/opendc-web/opendc-web-ui/src/actions/modals/scenarios.js b/opendc-web/opendc-web-ui/src/actions/modals/scenarios.js deleted file mode 100644 index b71cb27b..00000000 --- a/opendc-web/opendc-web-ui/src/actions/modals/scenarios.js +++ /dev/null @@ -1,14 +0,0 @@ -export const OPEN_NEW_SCENARIO_MODAL = 'OPEN_NEW_SCENARIO_MODAL' -export const CLOSE_NEW_SCENARIO_MODAL = 'CLOSE_SCENARIO_MODAL' - -export function openNewScenarioModal() { - return { - type: OPEN_NEW_SCENARIO_MODAL, - } -} - -export function closeNewScenarioModal() { - return { - type: CLOSE_NEW_SCENARIO_MODAL, - } -} diff --git a/opendc-web/opendc-web-ui/src/actions/modals/topology.js b/opendc-web/opendc-web-ui/src/actions/modals/topology.js deleted file mode 100644 index b5fecac1..00000000 --- a/opendc-web/opendc-web-ui/src/actions/modals/topology.js +++ /dev/null @@ -1,84 +0,0 @@ -export const OPEN_NEW_TOPOLOGY_MODAL = 'OPEN_NEW_TOPOLOGY_MODAL' -export const CLOSE_NEW_TOPOLOGY_MODAL = 'CLOSE_NEW_TOPOLOGY_MODAL' -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 openNewTopologyModal() { - return { - type: OPEN_NEW_TOPOLOGY_MODAL, - } -} - -export function closeNewTopologyModal() { - return { - type: CLOSE_NEW_TOPOLOGY_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/opendc-web/opendc-web-ui/src/actions/objects.js b/opendc-web/opendc-web-ui/src/actions/objects.js deleted file mode 100644 index 7b648b18..00000000 --- a/opendc-web/opendc-web-ui/src/actions/objects.js +++ /dev/null @@ -1,41 +0,0 @@ -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/opendc-web/opendc-web-ui/src/actions/portfolios.js b/opendc-web/opendc-web-ui/src/actions/portfolios.js deleted file mode 100644 index d37886d8..00000000 --- a/opendc-web/opendc-web-ui/src/actions/portfolios.js +++ /dev/null @@ -1,41 +0,0 @@ -export const ADD_PORTFOLIO = 'ADD_PORTFOLIO' -export const UPDATE_PORTFOLIO = 'UPDATE_PORTFOLIO' -export const DELETE_PORTFOLIO = 'DELETE_PORTFOLIO' -export const OPEN_PORTFOLIO_SUCCEEDED = 'OPEN_PORTFOLIO_SUCCEEDED' -export const SET_CURRENT_PORTFOLIO = 'SET_CURRENT_PORTFOLIO' - -export function addPortfolio(portfolio) { - return { - type: ADD_PORTFOLIO, - portfolio, - } -} - -export function updatePortfolio(portfolio) { - return { - type: UPDATE_PORTFOLIO, - portfolio, - } -} - -export function deletePortfolio(id) { - return { - type: DELETE_PORTFOLIO, - id, - } -} - -export function openPortfolioSucceeded(projectId, portfolioId) { - return { - type: OPEN_PORTFOLIO_SUCCEEDED, - projectId, - portfolioId, - } -} - -export function setCurrentPortfolio(portfolioId) { - return { - type: SET_CURRENT_PORTFOLIO, - portfolioId, - } -} diff --git a/opendc-web/opendc-web-ui/src/actions/prefabs.js b/opendc-web/opendc-web-ui/src/actions/prefabs.js deleted file mode 100644 index c112feed..00000000 --- a/opendc-web/opendc-web-ui/src/actions/prefabs.js +++ /dev/null @@ -1,32 +0,0 @@ -export const ADD_PREFAB = 'ADD_PREFAB' -export const DELETE_PREFAB = 'DELETE_PREFAB' -export const DELETE_PREFAB_SUCCEEDED = 'DELETE_PREFAB_SUCCEEDED' -export const OPEN_PREFAB_SUCCEEDED = 'OPEN_PREFAB_SUCCEEDED' - -export function addPrefab(name) { - return { - type: ADD_PREFAB, - name, - } -} - -export function deletePrefab(id) { - return { - type: DELETE_PREFAB, - id, - } -} - -export function deletePrefabSucceeded(id) { - return { - type: DELETE_PREFAB_SUCCEEDED, - id, - } -} - -export function openPrefabSucceeded(id) { - return { - type: OPEN_PREFAB_SUCCEEDED, - id, - } -} diff --git a/opendc-web/opendc-web-ui/src/actions/projects.js b/opendc-web/opendc-web-ui/src/actions/projects.js deleted file mode 100644 index add0f242..00000000 --- a/opendc-web/opendc-web-ui/src/actions/projects.js +++ /dev/null @@ -1,52 +0,0 @@ -export const SET_AUTH_VISIBILITY_FILTER = 'SET_AUTH_VISIBILITY_FILTER' -export const ADD_PROJECT = 'ADD_PROJECT' -export const ADD_PROJECT_SUCCEEDED = 'ADD_PROJECT_SUCCEEDED' -export const DELETE_PROJECT = 'DELETE_PROJECT' -export const DELETE_PROJECT_SUCCEEDED = 'DELETE_PROJECT_SUCCEEDED' -export const OPEN_PROJECT_SUCCEEDED = 'OPEN_PROJECT_SUCCEEDED' - -export function setAuthVisibilityFilter(filter) { - return { - type: SET_AUTH_VISIBILITY_FILTER, - filter, - } -} - -export function addProject(name) { - return (dispatch, getState) => { - const { auth } = getState() - dispatch({ - type: ADD_PROJECT, - name, - userId: auth.userId, - }) - } -} - -export function addProjectSucceeded(authorization) { - return { - type: ADD_PROJECT_SUCCEEDED, - authorization, - } -} - -export function deleteProject(id) { - return { - type: DELETE_PROJECT, - id, - } -} - -export function deleteProjectSucceeded(id) { - return { - type: DELETE_PROJECT_SUCCEEDED, - id, - } -} - -export function openProjectSucceeded(id) { - return { - type: OPEN_PROJECT_SUCCEEDED, - id, - } -} diff --git a/opendc-web/opendc-web-ui/src/actions/scenarios.js b/opendc-web/opendc-web-ui/src/actions/scenarios.js deleted file mode 100644 index c8a90762..00000000 --- a/opendc-web/opendc-web-ui/src/actions/scenarios.js +++ /dev/null @@ -1,43 +0,0 @@ -export const ADD_SCENARIO = 'ADD_SCENARIO' -export const UPDATE_SCENARIO = 'UPDATE_SCENARIO' -export const DELETE_SCENARIO = 'DELETE_SCENARIO' -export const OPEN_SCENARIO_SUCCEEDED = 'OPEN_SCENARIO_SUCCEEDED' -export const SET_CURRENT_SCENARIO = 'SET_CURRENT_SCENARIO' - -export function addScenario(scenario) { - return { - type: ADD_SCENARIO, - scenario, - } -} - -export function updateScenario(scenario) { - return { - type: UPDATE_SCENARIO, - scenario, - } -} - -export function deleteScenario(id) { - return { - type: DELETE_SCENARIO, - id, - } -} - -export function openScenarioSucceeded(projectId, portfolioId, scenarioId) { - return { - type: OPEN_SCENARIO_SUCCEEDED, - projectId, - portfolioId, - scenarioId, - } -} - -export function setCurrentScenario(portfolioId, scenarioId) { - return { - type: SET_CURRENT_SCENARIO, - portfolioId, - scenarioId, - } -} diff --git a/opendc-web/opendc-web-ui/src/actions/topologies.js b/opendc-web/opendc-web-ui/src/actions/topologies.js deleted file mode 100644 index dcce3b7d..00000000 --- a/opendc-web/opendc-web-ui/src/actions/topologies.js +++ /dev/null @@ -1,17 +0,0 @@ -export const ADD_TOPOLOGY = 'ADD_TOPOLOGY' -export const DELETE_TOPOLOGY = 'DELETE_TOPOLOGY' - -export function addTopology(name, duplicateId) { - return { - type: ADD_TOPOLOGY, - name, - duplicateId, - } -} - -export function deleteTopology(id) { - return { - type: DELETE_TOPOLOGY, - id, - } -} diff --git a/opendc-web/opendc-web-ui/src/actions/topology/rack.js b/opendc-web/opendc-web-ui/src/actions/topology/rack.js deleted file mode 100644 index b117402e..00000000 --- a/opendc-web/opendc-web-ui/src/actions/topology/rack.js +++ /dev/null @@ -1,23 +0,0 @@ -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/opendc-web/opendc-web-ui/src/actions/topology/room.js b/opendc-web/opendc-web-ui/src/actions/topology/room.js deleted file mode 100644 index 52cba680..00000000 --- a/opendc-web/opendc-web-ui/src/actions/topology/room.js +++ /dev/null @@ -1,48 +0,0 @@ -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/opendc-web/opendc-web-ui/src/actions/users.js b/opendc-web/opendc-web-ui/src/actions/users.js deleted file mode 100644 index 4868ac34..00000000 --- a/opendc-web/opendc-web-ui/src/actions/users.js +++ /dev/null @@ -1,37 +0,0 @@ -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/opendc-web/opendc-web-ui/src/api/index.js b/opendc-web/opendc-web-ui/src/api/index.js index cefcb2c5..680d49ce 100644 --- a/opendc-web/opendc-web-ui/src/api/index.js +++ b/opendc-web/opendc-web-ui/src/api/index.js @@ -1,13 +1,51 @@ -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) - } - }) +/* + * Copyright (c) 2021 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. + */ + +const apiUrl = process.env.NEXT_PUBLIC_API_BASE_URL + +/** + * Send the specified request to the OpenDC API. + * + * @param auth The authentication context. + * @param path Relative path for the API. + * @param method The method to use for the request. + * @param body The body of the request. + */ +export async function request(auth, path, method = 'GET', body) { + const { getAccessTokenSilently } = auth + const token = await getAccessTokenSilently() + const response = await fetch(`${apiUrl}/${path}`, { + method: method, + headers: { + Authorization: `Bearer ${token}`, + 'Content-Type': 'application/json', + }, + body: body && JSON.stringify(body), }) + const json = await response.json() + + if (!response.ok) { + throw response.message + } + + return json.data } diff --git a/opendc-web/opendc-web-ui/src/api/portfolios.js b/opendc-web/opendc-web-ui/src/api/portfolios.js new file mode 100644 index 00000000..82ac0ced --- /dev/null +++ b/opendc-web/opendc-web-ui/src/api/portfolios.js @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2021 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. + */ + +import { request } from './index' + +export function fetchPortfolio(auth, portfolioId) { + return request(auth, `portfolios/${portfolioId}`) +} + +export function fetchPortfoliosOfProject(auth, projectId) { + return request(auth, `projects/${projectId}/portfolios`) +} + +export function addPortfolio(auth, portfolio) { + return request(auth, `projects/${portfolio.projectId}/portfolios`, 'POST', { portfolio }) +} + +export function updatePortfolio(auth, portfolioId, portfolio) { + return request(auth, `portfolios/${portfolioId}`, 'PUT', { portfolio }) +} + +export function deletePortfolio(auth, portfolioId) { + return request(auth, `portfolios/${portfolioId}`, 'DELETE') +} diff --git a/opendc-web/opendc-web-ui/src/api/prefabs.js b/opendc-web/opendc-web-ui/src/api/prefabs.js new file mode 100644 index 00000000..eb9aa23c --- /dev/null +++ b/opendc-web/opendc-web-ui/src/api/prefabs.js @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2021 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. + */ + +import { request } from './index' + +export function getPrefab(auth, prefabId) { + return request(auth, `prefabs/${prefabId}`) +} + +export function addPrefab(auth, prefab) { + return request(auth, 'prefabs/', 'POST', { prefab }) +} + +export function updatePrefab(auth, prefab) { + return request(auth, `prefabs/${prefab._id}`, 'PUT', { prefab }) +} + +export function deletePrefab(auth, prefabId) { + return request(auth, `prefabs/${prefabId}`, 'DELETE') +} diff --git a/opendc-web/opendc-web-ui/src/api/projects.js b/opendc-web/opendc-web-ui/src/api/projects.js new file mode 100644 index 00000000..4123b371 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/api/projects.js @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2021 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. + */ + +import { request } from './index' + +export function fetchProjects(auth) { + return request(auth, `projects/`) +} + +export function fetchProject(auth, projectId) { + return request(auth, `projects/${projectId}`) +} + +export function addProject(auth, project) { + return request(auth, 'projects/', 'POST', { project }) +} + +export function updateProject(auth, project) { + return request(auth, `projects/${project._id}`, 'PUT', { project }) +} + +export function deleteProject(auth, projectId) { + return request(auth, `projects/${projectId}`, 'DELETE') +} diff --git a/opendc-web/opendc-web-ui/src/api/routes/portfolios.js b/opendc-web/opendc-web-ui/src/api/routes/portfolios.js deleted file mode 100644 index 7c9ea02a..00000000 --- a/opendc-web/opendc-web-ui/src/api/routes/portfolios.js +++ /dev/null @@ -1,42 +0,0 @@ -import { deleteById, getById } from './util' -import { sendRequest } from '../index' - -export function addPortfolio(projectId, portfolio) { - return sendRequest({ - path: '/projects/{projectId}/portfolios', - method: 'POST', - parameters: { - body: { - portfolio, - }, - path: { - projectId, - }, - query: {}, - }, - }) -} - -export function getPortfolio(portfolioId) { - return getById('/portfolios/{portfolioId}', { portfolioId }) -} - -export function updatePortfolio(portfolioId, portfolio) { - return sendRequest({ - path: '/portfolios/{projectId}', - method: 'POST', - parameters: { - body: { - portfolio, - }, - path: { - portfolioId, - }, - query: {}, - }, - }) -} - -export function deletePortfolio(portfolioId) { - return deleteById('/portfolios/{portfolioId}', { portfolioId }) -} diff --git a/opendc-web/opendc-web-ui/src/api/routes/prefabs.js b/opendc-web/opendc-web-ui/src/api/routes/prefabs.js deleted file mode 100644 index 8a1debfa..00000000 --- a/opendc-web/opendc-web-ui/src/api/routes/prefabs.js +++ /dev/null @@ -1,40 +0,0 @@ -import { sendRequest } from '../index' -import { deleteById, getById } from './util' - -export function getPrefab(prefabId) { - return getById('/prefabs/{prefabId}', { prefabId }) -} - -export function addPrefab(prefab) { - return sendRequest({ - path: '/prefabs', - method: 'POST', - parameters: { - body: { - prefab, - }, - path: {}, - query: {}, - }, - }) -} - -export function updatePrefab(prefab) { - return sendRequest({ - path: '/prefabs/{prefabId}', - method: 'PUT', - parameters: { - body: { - prefab, - }, - path: { - prefabId: prefab._id, - }, - query: {}, - }, - }) -} - -export function deletePrefab(prefabId) { - return deleteById('/prefabs/{prefabId}', { prefabId }) -} diff --git a/opendc-web/opendc-web-ui/src/api/routes/projects.js b/opendc-web/opendc-web-ui/src/api/routes/projects.js deleted file mode 100644 index 4109079c..00000000 --- a/opendc-web/opendc-web-ui/src/api/routes/projects.js +++ /dev/null @@ -1,40 +0,0 @@ -import { sendRequest } from '../index' -import { deleteById, getById } from './util' - -export function getProject(projectId) { - return getById('/projects/{projectId}', { projectId }) -} - -export function addProject(project) { - return sendRequest({ - path: '/projects', - method: 'POST', - parameters: { - body: { - project, - }, - path: {}, - query: {}, - }, - }) -} - -export function updateProject(project) { - return sendRequest({ - path: '/projects/{projectId}', - method: 'PUT', - parameters: { - body: { - project, - }, - path: { - projectId: project._id, - }, - query: {}, - }, - }) -} - -export function deleteProject(projectId) { - return deleteById('/projects/{projectId}', { projectId }) -} diff --git a/opendc-web/opendc-web-ui/src/api/routes/scenarios.js b/opendc-web/opendc-web-ui/src/api/routes/scenarios.js deleted file mode 100644 index ab2e8b86..00000000 --- a/opendc-web/opendc-web-ui/src/api/routes/scenarios.js +++ /dev/null @@ -1,42 +0,0 @@ -import { deleteById, getById } from './util' -import { sendRequest } from '../index' - -export function addScenario(portfolioId, scenario) { - return sendRequest({ - path: '/portfolios/{portfolioId}/scenarios', - method: 'POST', - parameters: { - body: { - scenario, - }, - path: { - portfolioId, - }, - query: {}, - }, - }) -} - -export function getScenario(scenarioId) { - return getById('/scenarios/{scenarioId}', { scenarioId }) -} - -export function updateScenario(scenarioId, scenario) { - return sendRequest({ - path: '/scenarios/{projectId}', - method: 'POST', - parameters: { - body: { - scenario, - }, - path: { - scenarioId, - }, - query: {}, - }, - }) -} - -export function deleteScenario(scenarioId) { - return deleteById('/scenarios/{scenarioId}', { scenarioId }) -} diff --git a/opendc-web/opendc-web-ui/src/api/routes/schedulers.js b/opendc-web/opendc-web-ui/src/api/routes/schedulers.js deleted file mode 100644 index 4481fb2a..00000000 --- a/opendc-web/opendc-web-ui/src/api/routes/schedulers.js +++ /dev/null @@ -1,5 +0,0 @@ -import { getAll } from './util' - -export function getAllSchedulers() { - return getAll('/schedulers') -} diff --git a/opendc-web/opendc-web-ui/src/api/routes/token-signin.js b/opendc-web/opendc-web-ui/src/api/routes/token-signin.js deleted file mode 100644 index ced5d2e0..00000000 --- a/opendc-web/opendc-web-ui/src/api/routes/token-signin.js +++ /dev/null @@ -1,12 +0,0 @@ -import config from '../../config' - -export function performTokenSignIn(token) { - const apiUrl = config['API_BASE_URL'] - - return fetch(`${apiUrl}/tokensignin`, { - method: 'POST', - body: new URLSearchParams({ - idtoken: token, - }), - }).then((res) => res.json()) -} diff --git a/opendc-web/opendc-web-ui/src/api/routes/topologies.js b/opendc-web/opendc-web-ui/src/api/routes/topologies.js deleted file mode 100644 index a8f0d6b1..00000000 --- a/opendc-web/opendc-web-ui/src/api/routes/topologies.js +++ /dev/null @@ -1,42 +0,0 @@ -import { deleteById, getById } from './util' -import { sendRequest } from '../index' - -export function addTopology(topology) { - return sendRequest({ - path: '/projects/{projectId}/topologies', - method: 'POST', - parameters: { - body: { - topology, - }, - path: { - projectId: topology.projectId, - }, - query: {}, - }, - }) -} - -export function getTopology(topologyId) { - return getById('/topologies/{topologyId}', { topologyId }) -} - -export function updateTopology(topology) { - return sendRequest({ - path: '/topologies/{topologyId}', - method: 'PUT', - parameters: { - body: { - topology, - }, - path: { - topologyId: topology._id, - }, - query: {}, - }, - }) -} - -export function deleteTopology(topologyId) { - return deleteById('/topologies/{topologyId}', { topologyId }) -} diff --git a/opendc-web/opendc-web-ui/src/api/routes/traces.js b/opendc-web/opendc-web-ui/src/api/routes/traces.js deleted file mode 100644 index 67895a87..00000000 --- a/opendc-web/opendc-web-ui/src/api/routes/traces.js +++ /dev/null @@ -1,5 +0,0 @@ -import { getAll } from './util' - -export function getAllTraces() { - return getAll('/traces') -} diff --git a/opendc-web/opendc-web-ui/src/api/routes/users.js b/opendc-web/opendc-web-ui/src/api/routes/users.js deleted file mode 100644 index 3028f3f7..00000000 --- a/opendc-web/opendc-web-ui/src/api/routes/users.js +++ /dev/null @@ -1,48 +0,0 @@ -import { sendRequest } from '../index' -import { deleteById } 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, - }, - path: {}, - query: {}, - }, - }) -} - -export function getUser(userId) { - return sendRequest({ - path: '/users/{userId}', - method: 'GET', - parameters: { - body: {}, - path: { - userId, - }, - query: {}, - }, - }) -} - -export function deleteUser(userId) { - return deleteById('/users/{userId}', { userId }) -} diff --git a/opendc-web/opendc-web-ui/src/api/routes/util.js b/opendc-web/opendc-web-ui/src/api/routes/util.js deleted file mode 100644 index 67e7173b..00000000 --- a/opendc-web/opendc-web-ui/src/api/routes/util.js +++ /dev/null @@ -1,37 +0,0 @@ -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/opendc-web/opendc-web-ui/src/api/scenarios.js b/opendc-web/opendc-web-ui/src/api/scenarios.js new file mode 100644 index 00000000..88516caa --- /dev/null +++ b/opendc-web/opendc-web-ui/src/api/scenarios.js @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2021 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. + */ + +import { request } from './index' + +export function fetchScenario(auth, scenarioId) { + return request(auth, `scenarios/${scenarioId}`) +} + +export function fetchScenariosOfPortfolio(auth, portfolioId) { + return request(auth, `portfolios/${portfolioId}/scenarios`) +} + +export function addScenario(auth, scenario) { + return request(auth, `portfolios/${scenario.portfolioId}/scenarios`, 'POST', { scenario }) +} + +export function updateScenario(auth, scenarioId, scenario) { + return request(auth, `scenarios/${scenarioId}`, 'PUT', { scenario }) +} + +export function deleteScenario(auth, scenarioId) { + return request(auth, `scenarios/${scenarioId}`, 'DELETE') +} diff --git a/opendc-web/opendc-web-ui/src/api/schedulers.js b/opendc-web/opendc-web-ui/src/api/schedulers.js new file mode 100644 index 00000000..0b8b8153 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/api/schedulers.js @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2021 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. + */ + +import { request } from './index' + +export function fetchSchedulers(auth) { + return request(auth, 'schedulers/') +} diff --git a/opendc-web/opendc-web-ui/src/api/socket.js b/opendc-web/opendc-web-ui/src/api/socket.js deleted file mode 100644 index 87facda8..00000000 --- a/opendc-web/opendc-web-ui/src/api/socket.js +++ /dev/null @@ -1,50 +0,0 @@ -import io from 'socket.io-client' -import { getAuthToken } from '../auth/index' -import config from '../config' - -let socket -let requestIdCounter = 0 -const callbacks = {} - -export function setupSocketConnection(onConnect) { - const apiUrl = - config['API_BASE_URL'] || `${window.location.protocol}//${window.location.hostname}:${window.location.port}` - - socket = io.connect(apiUrl) - 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/opendc-web/opendc-web-ui/src/api/topologies.js b/opendc-web/opendc-web-ui/src/api/topologies.js new file mode 100644 index 00000000..bd4e3bc4 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/api/topologies.js @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2021 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. + */ + +import { request } from './index' + +export function fetchTopology(auth, topologyId) { + return request(auth, `topologies/${topologyId}`) +} + +export function fetchTopologiesOfProject(auth, projectId) { + return request(auth, `projects/${projectId}/topologies`) +} + +export function addTopology(auth, topology) { + return request(auth, `projects/${topology.projectId}/topologies`, 'POST', { topology }) +} + +export function updateTopology(auth, topology) { + // eslint-disable-next-line no-unused-vars + const { _id, ...data } = topology + return request(auth, `topologies/${topology._id}`, 'PUT', { topology: data }) +} + +export function deleteTopology(auth, topologyId) { + return request(auth, `topologies/${topologyId}`, 'DELETE') +} diff --git a/opendc-web/opendc-web-ui/src/api/traces.js b/opendc-web/opendc-web-ui/src/api/traces.js new file mode 100644 index 00000000..fd637ac3 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/api/traces.js @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2021 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. + */ + +import { request } from './index' + +export function fetchTraces(auth) { + return request(auth, 'traces/') +} diff --git a/opendc-web/opendc-web-ui/src/auth.js b/opendc-web/opendc-web-ui/src/auth.js new file mode 100644 index 00000000..e670476c --- /dev/null +++ b/opendc-web/opendc-web-ui/src/auth.js @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2021 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. + */ + +import PropTypes from 'prop-types' +import { Auth0Provider, useAuth0 } from '@auth0/auth0-react' +import { useEffect } from 'react' + +/** + * Obtain the authentication context. + */ +export function useAuth() { + return useAuth0() +} + +/** + * Force the user to be authenticated or redirect to the homepage. + */ +export function useRequireAuth() { + const auth = useAuth() + const { loginWithRedirect, isLoading, isAuthenticated } = auth + + useEffect(() => { + if (!isLoading && !isAuthenticated) { + loginWithRedirect() + } + }, [loginWithRedirect, isLoading, isAuthenticated]) + + return auth +} + +/** + * AuthProvider which provides an authentication context. + */ +export function AuthProvider({ children }) { + return ( + <Auth0Provider + domain={process.env.NEXT_PUBLIC_AUTH0_DOMAIN} + clientId={process.env.NEXT_PUBLIC_AUTH0_CLIENT_ID} + redirectUri={global.window && global.window.location.origin} + audience={process.env.NEXT_PUBLIC_AUTH0_AUDIENCE} + > + {children} + </Auth0Provider> + ) +} + +AuthProvider.propTypes = { + children: PropTypes.node, +} diff --git a/opendc-web/opendc-web-ui/src/auth/index.js b/opendc-web/opendc-web-ui/src/auth/index.js deleted file mode 100644 index b5953990..00000000 --- a/opendc-web/opendc-web-ui/src/auth/index.js +++ /dev/null @@ -1,57 +0,0 @@ -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 = '/projects' - break - case LOG_OUT: - case DELETE_CURRENT_USER_SUCCEEDED: - clearAuthLocalStorage() - window.location.href = '/' - break - default: - next(action) - return - } - - next(action) -} diff --git a/opendc-web/opendc-web-ui/src/components/AppHeader.js b/opendc-web/opendc-web-ui/src/components/AppHeader.js new file mode 100644 index 00000000..b33212c4 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/AppHeader.js @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2021 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. + */ + +import { PageHeader } from '@patternfly/react-core' +import React from 'react' +import Image from 'next/image' +import AppHeaderTools from './AppHeaderTools' +import { AppNavigation } from './AppNavigation' +import AppLogo from './AppLogo' + +export function AppHeader() { + const logo = <Image src="/img/logo.png" layout="fixed" width={30} height={30} alt="OpenDC" /> + + return ( + <PageHeader + logo={logo} + logoProps={{ href: '/' }} + logoComponent={AppLogo} + headerTools={<AppHeaderTools />} + topNav={<AppNavigation />} + /> + ) +} + +AppHeader.propTypes = {} diff --git a/opendc-web/opendc-web-ui/src/components/AppHeaderTools.js b/opendc-web/opendc-web-ui/src/components/AppHeaderTools.js new file mode 100644 index 00000000..02e5d265 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/AppHeaderTools.js @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2021 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. + */ + +import { + Avatar, + Button, + ButtonVariant, + Dropdown, + DropdownGroup, + DropdownItem, + DropdownToggle, + KebabToggle, + PageHeaderTools, + PageHeaderToolsGroup, + PageHeaderToolsItem, + Skeleton, +} from '@patternfly/react-core' +import { useState } from 'react' +import { useAuth } from '../auth' +import { GithubIcon, HelpIcon } from '@patternfly/react-icons' + +function AppHeaderTools() { + const auth = useAuth() + + const [isKebabDropdownOpen, setKebabDropdownOpen] = useState(false) + const kebabDropdownItems = [ + <DropdownItem + key={0} + component={ + <a href="https://opendc.org" target="_blank" rel="noreferrer"> + <HelpIcon /> Help + </a> + } + />, + ] + + const [isDropdownOpen, setDropdownOpen] = useState(false) + const userDropdownItems = [ + <DropdownGroup key="group 2"> + <DropdownItem key="group 2 logout" onClick={() => auth.logout({ returnTo: window.location.origin })}> + Logout + </DropdownItem> + </DropdownGroup>, + ] + + return ( + <PageHeaderTools> + <PageHeaderToolsGroup visibility={{ default: 'hidden', lg: 'visible' }}> + <PageHeaderToolsItem> + <Button + component="a" + href="https://github.com/atlarge-research/opendc" + target="_blank" + aria-label="Source code" + variant={ButtonVariant.plain} + > + <GithubIcon /> + </Button> + </PageHeaderToolsItem> + <PageHeaderToolsItem> + <Button + component="a" + href="https://opendc.org/" + target="_blank" + aria-label="Help actions" + variant={ButtonVariant.plain} + > + <HelpIcon /> + </Button> + </PageHeaderToolsItem> + </PageHeaderToolsGroup> + <PageHeaderToolsGroup> + <PageHeaderToolsItem visibility={{ lg: 'hidden' }}> + <Dropdown + isPlain + position="right" + toggle={<KebabToggle onToggle={() => setKebabDropdownOpen(!isKebabDropdownOpen)} />} + isOpen={isKebabDropdownOpen} + dropdownItems={kebabDropdownItems} + /> + </PageHeaderToolsItem> + <PageHeaderToolsItem visibility={{ default: 'hidden', md: 'visible' }}> + <Dropdown + isPlain + position="right" + isOpen={isDropdownOpen} + toggle={ + <DropdownToggle onToggle={() => setDropdownOpen(!isDropdownOpen)}> + {auth?.user?.name ?? ( + <Skeleton + fontSize="xs" + width="150px" + className="pf-u-display-inline-flex" + screenreaderText="Loading username" + /> + )} + </DropdownToggle> + } + dropdownItems={userDropdownItems} + /> + </PageHeaderToolsItem> + </PageHeaderToolsGroup> + {auth?.user?.picture ? ( + <Avatar src={auth.user.picture} alt="Avatar image" /> + ) : ( + <Skeleton className="pf-c-avatar" shape="circle" width="2.25rem" screenreaderText="Loading avatar" /> + )} + </PageHeaderTools> + ) +} + +AppHeaderTools.propTypes = {} + +export default AppHeaderTools diff --git a/opendc-web/opendc-web-ui/src/components/AppLogo.js b/opendc-web/opendc-web-ui/src/components/AppLogo.js new file mode 100644 index 00000000..92663295 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/AppLogo.js @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2021 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. + */ + +import PropTypes from 'prop-types' +import Link from 'next/link' +import { appLogo } from './AppLogo.module.scss' + +function AppLogo({ href, children, className, ...props }) { + return ( + <> + <Link href={href}> + <a {...props} className={`${className ?? ''} ${appLogo}`}> + {children} + <span>OpenDC</span> + </a> + </Link> + </> + ) +} + +AppLogo.propTypes = { + href: PropTypes.string.isRequired, + children: PropTypes.node, + className: PropTypes.string, +} + +export default AppLogo diff --git a/opendc-web/opendc-web-ui/src/components/AppLogo.module.scss b/opendc-web/opendc-web-ui/src/components/AppLogo.module.scss new file mode 100644 index 00000000..3d228cb6 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/AppLogo.module.scss @@ -0,0 +1,33 @@ +/*! + * Copyright (c) 2021 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. + */ + +.appLogo { + span { + margin-left: 4px; + color: #fff; + } + + &:hover, + &:focus { + text-decoration: none; + } +} diff --git a/opendc-web/opendc-web-ui/src/components/AppNavigation.js b/opendc-web/opendc-web-ui/src/components/AppNavigation.js new file mode 100644 index 00000000..178c3ec0 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/AppNavigation.js @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2021 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. + */ + +import { Nav, NavItem, NavList } from '@patternfly/react-core' +import { useRouter } from 'next/router' +import NavItemLink from './util/NavItemLink' +import { useProject } from '../data/project' + +export function AppNavigation() { + const { pathname, query } = useRouter() + const { project: projectId } = query + const { data: project } = useProject(projectId) + + const nextTopologyId = project?.topologyIds?.[0] + const nextPortfolioId = project?.portfolioIds?.[0] + + return ( + <Nav variant="horizontal"> + <NavList> + <NavItem + id="projects" + to="/projects" + itemId={0} + component={NavItemLink} + isActive={pathname === '/projects' || pathname === '/projects/[project]'} + > + Projects + </NavItem> + {pathname.startsWith('/projects/[project]') && ( + <> + <NavItem + id="topologies" + to={nextTopologyId ? `/projects/${projectId}/topologies/${nextTopologyId}` : '/projects'} + itemId={1} + component={NavItemLink} + isActive={pathname === '/projects/[project]/topologies/[topology]'} + > + Topologies + </NavItem> + <NavItem + id="portfolios" + to={nextPortfolioId ? `/projects/${projectId}/portfolios/${nextPortfolioId}` : '/projects'} + itemId={2} + component={NavItemLink} + isActive={pathname === '/projects/[project]/portfolios/[portfolio]'} + > + Portfolios + </NavItem> + </> + )} + </NavList> + </Nav> + ) +} + +AppNavigation.propTypes = {} diff --git a/opendc-web/opendc-web-ui/src/components/AppPage.js b/opendc-web/opendc-web-ui/src/components/AppPage.js new file mode 100644 index 00000000..25afaf9a --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/AppPage.js @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2021 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. + */ + +import PropTypes from 'prop-types' +import { AppHeader } from './AppHeader' +import React from 'react' +import { Page, PageGroup, PageBreadcrumb } from '@patternfly/react-core' + +export function AppPage({ children, breadcrumb, contextSelectors }) { + return ( + <Page header={<AppHeader />}> + <PageGroup> + {contextSelectors} + {breadcrumb && <PageBreadcrumb>{breadcrumb}</PageBreadcrumb>} + </PageGroup> + {children} + </Page> + ) +} + +AppPage.propTypes = { + breadcrumb: PropTypes.node, + contextSelectors: PropTypes.node, + children: PropTypes.node, +} diff --git a/opendc-web/opendc-web-ui/src/components/app/map/LoadingScreen.js b/opendc-web/opendc-web-ui/src/components/app/map/LoadingScreen.js deleted file mode 100644 index 7efea9b0..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/LoadingScreen.js +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react' -import FontAwesome from 'react-fontawesome' - -const LoadingScreen = () => ( - <div className="display-4"> - <FontAwesome name="refresh" className="mr-4" spin /> - Loading your project... - </div> -) - -export default LoadingScreen diff --git a/opendc-web/opendc-web-ui/src/components/app/map/MapStageComponent.js b/opendc-web/opendc-web-ui/src/components/app/map/MapStageComponent.js deleted file mode 100644 index 2cd0ed6e..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/MapStageComponent.js +++ /dev/null @@ -1,103 +0,0 @@ -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 { 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) - } - - componentDidMount() { - this.updateDimensions() - - 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(window.innerWidth, window.innerHeight - 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/opendc-web/opendc-web-ui/src/components/app/map/controls/ExportCanvasComponent.js b/opendc-web/opendc-web-ui/src/components/app/map/controls/ExportCanvasComponent.js deleted file mode 100644 index 8487f47b..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/controls/ExportCanvasComponent.js +++ /dev/null @@ -1,13 +0,0 @@ -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/opendc-web/opendc-web-ui/src/components/app/map/controls/ScaleIndicatorComponent.js b/opendc-web/opendc-web-ui/src/components/app/map/controls/ScaleIndicatorComponent.js deleted file mode 100644 index 7cbb45c0..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/controls/ScaleIndicatorComponent.js +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react' -import { TILE_SIZE_IN_METERS, TILE_SIZE_IN_PIXELS } from '../MapConstants' -import './ScaleIndicatorComponent.sass' - -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/opendc-web/opendc-web-ui/src/components/app/map/controls/ScaleIndicatorComponent.sass b/opendc-web/opendc-web-ui/src/components/app/map/controls/ScaleIndicatorComponent.sass deleted file mode 100644 index 03a72c99..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/controls/ScaleIndicatorComponent.sass +++ /dev/null @@ -1,9 +0,0 @@ -.scale-indicator - position: absolute - right: 10px - bottom: 10px - z-index: 50 - - border: solid 2px #212529 - border-top: none - border-left: none diff --git a/opendc-web/opendc-web-ui/src/components/app/map/controls/ToolPanelComponent.js b/opendc-web/opendc-web-ui/src/components/app/map/controls/ToolPanelComponent.js deleted file mode 100644 index f372734d..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/controls/ToolPanelComponent.js +++ /dev/null @@ -1,13 +0,0 @@ -import React from 'react' -import ZoomControlContainer from '../../../../containers/app/map/controls/ZoomControlContainer' -import ExportCanvasComponent from './ExportCanvasComponent' -import './ToolPanelComponent.sass' - -const ToolPanelComponent = () => ( - <div className="tool-panel"> - <ZoomControlContainer /> - <ExportCanvasComponent /> - </div> -) - -export default ToolPanelComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/map/controls/ToolPanelComponent.sass b/opendc-web/opendc-web-ui/src/components/app/map/controls/ToolPanelComponent.sass deleted file mode 100644 index 8b27d24a..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/controls/ToolPanelComponent.sass +++ /dev/null @@ -1,5 +0,0 @@ -.tool-panel - position: absolute - left: 10px - bottom: 10px - z-index: 50 diff --git a/opendc-web/opendc-web-ui/src/components/app/map/controls/ZoomControlComponent.js b/opendc-web/opendc-web-ui/src/components/app/map/controls/ZoomControlComponent.js deleted file mode 100644 index 65944bea..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/controls/ZoomControlComponent.js +++ /dev/null @@ -1,24 +0,0 @@ -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/opendc-web/opendc-web-ui/src/components/app/map/elements/GrayLayer.js b/opendc-web/opendc-web-ui/src/components/app/map/elements/GrayLayer.js deleted file mode 100644 index c54a34ad..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/elements/GrayLayer.js +++ /dev/null @@ -1,17 +0,0 @@ -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/opendc-web/opendc-web-ui/src/components/app/map/elements/HoverTile.js b/opendc-web/opendc-web-ui/src/components/app/map/elements/HoverTile.js deleted file mode 100644 index 912229c4..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/elements/HoverTile.js +++ /dev/null @@ -1,27 +0,0 @@ -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/opendc-web/opendc-web-ui/src/components/app/map/elements/ImageComponent.js b/opendc-web/opendc-web-ui/src/components/app/map/elements/ImageComponent.js deleted file mode 100644 index 2b5c569f..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/elements/ImageComponent.js +++ /dev/null @@ -1,48 +0,0 @@ -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/opendc-web/opendc-web-ui/src/components/app/map/elements/RoomTile.js b/opendc-web/opendc-web-ui/src/components/app/map/elements/RoomTile.js deleted file mode 100644 index 43bf918e..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/elements/RoomTile.js +++ /dev/null @@ -1,20 +0,0 @@ -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/opendc-web/opendc-web-ui/src/components/app/map/elements/TileObject.js b/opendc-web/opendc-web-ui/src/components/app/map/elements/TileObject.js deleted file mode 100644 index 9e87cc82..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/elements/TileObject.js +++ /dev/null @@ -1,25 +0,0 @@ -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/opendc-web/opendc-web-ui/src/components/app/map/elements/TilePlusIcon.js b/opendc-web/opendc-web-ui/src/components/app/map/elements/TilePlusIcon.js deleted file mode 100644 index be3a00a8..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/elements/TilePlusIcon.js +++ /dev/null @@ -1,44 +0,0 @@ -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/opendc-web/opendc-web-ui/src/components/app/map/layers/HoverLayerComponent.js b/opendc-web/opendc-web-ui/src/components/app/map/layers/HoverLayerComponent.js deleted file mode 100644 index bead87de..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/layers/HoverLayerComponent.js +++ /dev/null @@ -1,75 +0,0 @@ -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/opendc-web/opendc-web-ui/src/components/app/map/layers/MapLayerComponent.js b/opendc-web/opendc-web-ui/src/components/app/map/layers/MapLayerComponent.js deleted file mode 100644 index 8ee14c9c..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/layers/MapLayerComponent.js +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react' -import { Group, Layer } from 'react-konva' -import TopologyContainer from '../../../../containers/app/map/TopologyContainer' -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 /> - <TopologyContainer /> - <GridGroup /> - </Group> - </Layer> -) - -export default MapLayerComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/map/layers/ObjectHoverLayerComponent.js b/opendc-web/opendc-web-ui/src/components/app/map/layers/ObjectHoverLayerComponent.js deleted file mode 100644 index 661fc255..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/layers/ObjectHoverLayerComponent.js +++ /dev/null @@ -1,11 +0,0 @@ -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/opendc-web/opendc-web-ui/src/components/app/map/layers/RoomHoverLayerComponent.js b/opendc-web/opendc-web-ui/src/components/app/map/layers/RoomHoverLayerComponent.js deleted file mode 100644 index 887e2891..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/layers/RoomHoverLayerComponent.js +++ /dev/null @@ -1,6 +0,0 @@ -import React from 'react' -import HoverLayerComponent from './HoverLayerComponent' - -const RoomHoverLayerComponent = (props) => <HoverLayerComponent {...props} /> - -export default RoomHoverLayerComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/results/PortfolioResultsComponent.js b/opendc-web/opendc-web-ui/src/components/app/results/PortfolioResultsComponent.js deleted file mode 100644 index c0b16fee..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/results/PortfolioResultsComponent.js +++ /dev/null @@ -1,94 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' -import { Bar, CartesianGrid, ComposedChart, ErrorBar, ResponsiveContainer, Scatter, Tooltip, XAxis, YAxis } from 'recharts' -import { AVAILABLE_METRICS, METRIC_NAMES_SHORT, METRIC_UNITS } from '../../../util/available-metrics' -import { mean, std } from 'mathjs' -import Shapes from '../../../shapes/index' -import approx from 'approximate-number' - -const PortfolioResultsComponent = ({ portfolio, scenarios }) => { - if (!portfolio) { - return <div>Loading...</div> - } - - const nonFinishedScenarios = scenarios.filter((s) => s.simulation.state !== 'FINISHED') - - if (nonFinishedScenarios.length > 0) { - if (nonFinishedScenarios.every((s) => s.simulation.state === 'QUEUED' || s.simulation.state === 'RUNNING')) { - return ( - <div> - <h1>Simulation running...</h1> - <p>{nonFinishedScenarios.length} of the scenarios are still being simulated</p> - </div> - ) - } - if (nonFinishedScenarios.some((s) => s.simulation.state === 'FAILED')) { - return ( - <div> - <h1>Simulation failed.</h1> - <p> - Try again by creating a new scenario. Please contact the OpenDC team for support, if issues - persist. - </p> - </div> - ) - } - } - - const dataPerMetric = {} - - AVAILABLE_METRICS.forEach((metric) => { - dataPerMetric[metric] = scenarios.map((scenario) => ({ - name: scenario.name, - value: mean(scenario.results[metric]), - errorX: std(scenario.results[metric]), - })) - }) - - return ( - <div className="full-height" style={{ overflowY: 'scroll', overflowX: 'hidden' }}> - <h2>Portfolio: {portfolio.name}</h2> - <p>Repeats per Scenario: {portfolio.targets.repeatsPerScenario}</p> - <div className="row"> - {AVAILABLE_METRICS.map((metric) => ( - <div className="col-6 mb-2" key={metric}> - <h4>{METRIC_NAMES_SHORT[metric]}</h4> - <ResponsiveContainer aspect={16 / 9} width="100%"> - <ComposedChart - data={dataPerMetric[metric]} - margin={{ left: 35, bottom: 15 }} - layout="vertical" - > - <CartesianGrid strokeDasharray="3 3" /> - <XAxis - tickFormatter={(tick) => approx(tick)} - label={{ value: METRIC_UNITS[metric], position: 'bottom', offset: 0 }} - type="number" - /> - <YAxis dataKey="name" type="category" /> - <Bar dataKey="value" fill="#3399FF" isAnimationActive={false} /> - <Scatter dataKey="value" opacity={0} isAnimationActive={false}> - <ErrorBar - dataKey="errorX" - width={10} - strokeWidth={3} - stroke="#FF6600" - direction="x" - /> - </Scatter> - <Tooltip/> - </ComposedChart> - </ResponsiveContainer> - </div> - ))} - </div> - </div> - ) -} - -PortfolioResultsComponent.propTypes = { - portfolio: Shapes.Portfolio, - scenarios: PropTypes.arrayOf(Shapes.Scenario), -} - -export default PortfolioResultsComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/Sidebar.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/Sidebar.js deleted file mode 100644 index f7368f54..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/Sidebar.js +++ /dev/null @@ -1,53 +0,0 @@ -import PropTypes from 'prop-types' -import classNames from 'classnames' -import React from 'react' -import './Sidebar.sass' - -class Sidebar extends React.Component { - static propTypes = { - isRight: PropTypes.bool.isRequired, - collapsible: PropTypes.bool, - } - - static defaultProps = { - collapsible: true, - } - - 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} - {this.props.collapsible && collapseButton} - </div> - ) - } -} - -export default Sidebar diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/Sidebar.sass b/opendc-web/opendc-web-ui/src/components/app/sidebars/Sidebar.sass deleted file mode 100644 index b8e15716..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/Sidebar.sass +++ /dev/null @@ -1,50 +0,0 @@ -@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: $side-bar-width - - 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/opendc-web/opendc-web-ui/src/components/app/sidebars/project/PortfolioListComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/project/PortfolioListComponent.js deleted file mode 100644 index b000b9e2..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/project/PortfolioListComponent.js +++ /dev/null @@ -1,66 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import Shapes from '../../../../shapes' -import { Link } from 'react-router-dom' -import FontAwesome from 'react-fontawesome' -import ScenarioListContainer from '../../../../containers/app/sidebars/project/ScenarioListContainer' - -class PortfolioListComponent extends React.Component { - static propTypes = { - portfolios: PropTypes.arrayOf(Shapes.Portfolio), - currentProjectId: PropTypes.string.isRequired, - currentPortfolioId: PropTypes.string, - onNewPortfolio: PropTypes.func.isRequired, - onChoosePortfolio: PropTypes.func.isRequired, - onDeletePortfolio: PropTypes.func.isRequired, - } - - onDelete(id) { - this.props.onDeletePortfolio(id) - } - - render() { - return ( - <div className="pb-3"> - <h2> - Portfolios - <button - className="btn btn-outline-primary float-right" - onClick={this.props.onNewPortfolio.bind(this)} - > - <FontAwesome name="plus" /> - </button> - </h2> - - {this.props.portfolios.map((portfolio, idx) => ( - <div key={portfolio._id}> - <div className="row mb-1"> - <div - className={ - 'col-7 align-self-center ' + - (portfolio._id === this.props.currentPortfolioId ? 'font-weight-bold' : '') - } - > - {portfolio.name} - </div> - <div className="col-5 text-right"> - <Link - className="btn btn-outline-primary mr-1 fa fa-play" - to={`/projects/${this.props.currentProjectId}/portfolios/${portfolio._id}`} - onClick={() => this.props.onChoosePortfolio(portfolio._id)} - /> - <span - className="btn btn-outline-danger fa fa-trash" - onClick={() => this.onDelete(portfolio._id)} - /> - </div> - </div> - <ScenarioListContainer portfolioId={portfolio._id} /> - </div> - ))} - </div> - ) - } -} - -export default PortfolioListComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/project/ProjectSidebarComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/project/ProjectSidebarComponent.js deleted file mode 100644 index 4789315e..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/project/ProjectSidebarComponent.js +++ /dev/null @@ -1,15 +0,0 @@ -import React from 'react' -import Sidebar from '../Sidebar' -import TopologyListContainer from '../../../../containers/app/sidebars/project/TopologyListContainer' -import PortfolioListContainer from '../../../../containers/app/sidebars/project/PortfolioListContainer' - -const ProjectSidebarComponent = ({ collapsible }) => ( - <Sidebar isRight={false} collapsible={collapsible}> - <div className="h-100 overflow-auto container-fluid"> - <TopologyListContainer /> - <PortfolioListContainer /> - </div> - </Sidebar> -) - -export default ProjectSidebarComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/project/ScenarioListComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/project/ScenarioListComponent.js deleted file mode 100644 index e775a663..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/project/ScenarioListComponent.js +++ /dev/null @@ -1,62 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import Shapes from '../../../../shapes' -import { Link } from 'react-router-dom' -import FontAwesome from 'react-fontawesome' - -class ScenarioListComponent extends React.Component { - static propTypes = { - scenarios: PropTypes.arrayOf(Shapes.Scenario), - portfolioId: PropTypes.string, - currentProjectId: PropTypes.string.isRequired, - currentScenarioId: PropTypes.string, - onNewScenario: PropTypes.func.isRequired, - onChooseScenario: PropTypes.func.isRequired, - onDeleteScenario: PropTypes.func.isRequired, - } - - onDelete(id) { - this.props.onDeleteScenario(id) - } - - render() { - return ( - <> - {this.props.scenarios.map((scenario, idx) => ( - <div key={scenario._id} className="row mb-1"> - <div - className={ - 'col-7 pl-5 align-self-center ' + - (scenario._id === this.props.currentScenarioId ? 'font-weight-bold' : '') - } - > - {scenario.name} - </div> - <div className="col-5 text-right"> - <Link - className="btn btn-outline-primary mr-1 fa fa-play disabled" - to={`/projects/${this.props.currentProjectId}/portfolios/${scenario.portfolioId}/scenarios/${scenario._id}`} - onClick={() => this.props.onChooseScenario(scenario.portfolioId, scenario._id)} - /> - <span - className={'btn btn-outline-danger fa fa-trash ' + (idx === 0 ? 'disabled' : '')} - onClick={() => (idx !== 0 ? this.onDelete(scenario._id) : undefined)} - /> - </div> - </div> - ))} - <div className="pl-4 mb-2"> - <div - className="btn btn-outline-primary" - onClick={() => this.props.onNewScenario(this.props.portfolioId)} - > - <FontAwesome name="plus" className="mr-1" /> - New scenario - </div> - </div> - </> - ) - } -} - -export default ScenarioListComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/project/TopologyListComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/project/TopologyListComponent.js deleted file mode 100644 index 2f42f7e4..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/project/TopologyListComponent.js +++ /dev/null @@ -1,60 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import Shapes from '../../../../shapes' -import FontAwesome from 'react-fontawesome' - -class TopologyListComponent extends React.Component { - static propTypes = { - topologies: PropTypes.arrayOf(Shapes.Topology), - currentTopologyId: PropTypes.string, - onChooseTopology: PropTypes.func.isRequired, - onNewTopology: PropTypes.func.isRequired, - onDeleteTopology: PropTypes.func.isRequired, - } - - onChoose(id) { - this.props.onChooseTopology(id) - } - - onDelete(id) { - this.props.onDeleteTopology(id) - } - - render() { - return ( - <div className="pb-3"> - <h2> - Topologies - <button className="btn btn-outline-primary float-right" onClick={this.props.onNewTopology}> - <FontAwesome name="plus" /> - </button> - </h2> - - {this.props.topologies.map((topology, idx) => ( - <div key={topology._id} className="row mb-1"> - <div - className={ - 'col-7 align-self-center ' + - (topology._id === this.props.currentTopologyId ? 'font-weight-bold' : '') - } - > - {topology.name} - </div> - <div className="col-5 text-right"> - <span - className="btn btn-outline-primary mr-1 fa fa-play" - onClick={() => this.onChoose(topology._id)} - /> - <span - className={'btn btn-outline-danger fa fa-trash ' + (idx === 0 ? 'disabled' : '')} - onClick={() => (idx !== 0 ? this.onDelete(topology._id) : undefined)} - /> - </div> - </div> - ))} - </div> - ) - } -} - -export default TopologyListComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/NameComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/NameComponent.js deleted file mode 100644 index 5fb0dc55..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/NameComponent.js +++ /dev/null @@ -1,13 +0,0 @@ -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/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/TopologySidebarComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/TopologySidebarComponent.js deleted file mode 100644 index f5eee36b..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/TopologySidebarComponent.js +++ /dev/null @@ -1,31 +0,0 @@ -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/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/BuildingSidebarComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/BuildingSidebarComponent.js deleted file mode 100644 index eea62f84..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/BuildingSidebarComponent.js +++ /dev/null @@ -1,13 +0,0 @@ -import React from 'react' -import NewRoomConstructionContainer from '../../../../../containers/app/sidebars/topology/building/NewRoomConstructionContainer' - -const BuildingSidebarComponent = () => { - return ( - <div> - <h2>Building</h2> - <NewRoomConstructionContainer /> - </div> - ) -} - -export default BuildingSidebarComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/NewRoomConstructionComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/NewRoomConstructionComponent.js deleted file mode 100644 index fd552c1e..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/NewRoomConstructionComponent.js +++ /dev/null @@ -1,26 +0,0 @@ -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/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/BackToRackComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/BackToRackComponent.js deleted file mode 100644 index 70d522b2..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/BackToRackComponent.js +++ /dev/null @@ -1,10 +0,0 @@ -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/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/DeleteMachineComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/DeleteMachineComponent.js deleted file mode 100644 index 37820316..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/DeleteMachineComponent.js +++ /dev/null @@ -1,10 +0,0 @@ -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/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/MachineNameComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/MachineNameComponent.js deleted file mode 100644 index 992383c4..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/MachineNameComponent.js +++ /dev/null @@ -1,5 +0,0 @@ -import React from 'react' - -const MachineNameComponent = ({ position }) => <h2>Machine at slot {position}</h2> - -export default MachineNameComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/MachineSidebarComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/MachineSidebarComponent.js deleted file mode 100644 index 7c78cf9e..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/MachineSidebarComponent.js +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react' -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 = ({ machineId }) => { - return ( - <div className="h-100 overflow-auto"> - <MachineNameContainer /> - <BackToRackContainer /> - <DeleteMachineContainer /> - <UnitTabsContainer /> - </div> - ) -} - -export default MachineSidebarComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitAddComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitAddComponent.js deleted file mode 100644 index 4e9dbc7e..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitAddComponent.js +++ /dev/null @@ -1,35 +0,0 @@ -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-70 mr-1" ref={(unitSelect) => (this.unitSelect = unitSelect)}> - {this.props.units.map((unit) => ( - <option value={unit._id} key={unit._id}> - {unit.name} - </option> - ))} - </select> - <button - type="submit" - className="btn btn-outline-primary" - onClick={() => this.props.onAdd(this.unitSelect.value)} - > - <span className="fa fa-plus mr-2" /> - Add - </button> - </div> - </div> - ) - } -} - -export default UnitAddComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitComponent.js deleted file mode 100644 index de55e506..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitComponent.js +++ /dev/null @@ -1,52 +0,0 @@ -import React from 'react' -import { UncontrolledPopover, PopoverHeader, PopoverBody, Button } from 'reactstrap' - -function UnitComponent({ index, unitType, unit, onDelete }) { - let unitInfo - if (unitType === 'cpu' || unitType === 'gpu') { - unitInfo = ( - <> - <strong>Clockrate: </strong> - <code>{unit.clockRateMhz}</code> - <br /> - <strong>Num. Cores: </strong> - <code>{unit.numberOfCores}</code> - <br /> - <strong>Energy Cons.: </strong> - <code>{unit.energyConsumptionW} W</code> - <br /> - </> - ) - } else if (unitType === 'memory' || unitType === 'storage') { - unitInfo = ( - <> - <strong>Speed:</strong> - <code>{unit.speedMbPerS} Mb/s</code> - <br /> - <strong>Size:</strong> - <code>{unit.sizeMb} MB</code> - <br /> - <strong>Energy Cons.:</strong> - <code>{unit.energyConsumptionW} W</code> - <br /> - </> - ) - } - - return ( - <li className="d-flex list-group-item justify-content-between align-items-center"> - <span style={{ maxWidth: '60%' }}>{unit.name}</span> - <span> - <Button outline={true} color="info" className="mr-1 fa fa-info-circle" id={`unit-${index}`} /> - <UncontrolledPopover trigger="focus" placement="left" target={`unit-${index}`}> - <PopoverHeader>Unit Information</PopoverHeader> - <PopoverBody>{unitInfo}</PopoverBody> - </UncontrolledPopover> - - <span className="btn btn-outline-danger fa fa-trash" onClick={onDelete} /> - </span> - </li> - ) -} - -export default UnitComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitListComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitListComponent.js deleted file mode 100644 index 2ade0f6a..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitListComponent.js +++ /dev/null @@ -1,20 +0,0 @@ -import React from 'react' -import UnitContainer from '../../../../../containers/app/sidebars/topology/machine/UnitContainer' - -const UnitListComponent = ({ unitType, unitIds }) => ( - <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"> - <span> - <strong>No units...</strong> Add some with the menu above! - </span> - </div> - )} - </ul> -) - -export default UnitListComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitTabsComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitTabsComponent.js deleted file mode 100644 index 6599fefd..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitTabsComponent.js +++ /dev/null @@ -1,78 +0,0 @@ -import React, { useState } from 'react' -import { Nav, NavItem, NavLink, TabContent, TabPane } from 'reactstrap' -import UnitAddContainer from '../../../../../containers/app/sidebars/topology/machine/UnitAddContainer' -import UnitListContainer from '../../../../../containers/app/sidebars/topology/machine/UnitListContainer' - -const UnitTabsComponent = () => { - const [activeTab, setActiveTab] = useState('cpu-units') - const toggle = (tab) => { - if (activeTab !== tab) setActiveTab(tab) - } - - return ( - <div> - <Nav tabs> - <NavItem> - <NavLink - className={activeTab === 'cpu-units' ? 'active' : ''} - onClick={() => { - toggle('cpu-units') - }} - > - CPU - </NavLink> - </NavItem> - <NavItem> - <NavLink - className={activeTab === 'gpu-units' ? 'active' : ''} - onClick={() => { - toggle('gpu-units') - }} - > - GPU - </NavLink> - </NavItem> - <NavItem> - <NavLink - className={activeTab === 'memory-units' ? 'active' : ''} - onClick={() => { - toggle('memory-units') - }} - > - Memory - </NavLink> - </NavItem> - <NavItem> - <NavLink - className={activeTab === 'storage-units' ? 'active' : ''} - onClick={() => { - toggle('storage-units') - }} - > - Stor. - </NavLink> - </NavItem> - </Nav> - <TabContent activeTab={activeTab}> - <TabPane tabId="cpu-units"> - <UnitAddContainer unitType="cpu" /> - <UnitListContainer unitType="cpu" /> - </TabPane> - <TabPane tabId="gpu-units"> - <UnitAddContainer unitType="gpu" /> - <UnitListContainer unitType="gpu" /> - </TabPane> - <TabPane tabId="memory-units"> - <UnitAddContainer unitType="memory" /> - <UnitListContainer unitType="memory" /> - </TabPane> - <TabPane tabId="storage-units"> - <UnitAddContainer unitType="storage" /> - <UnitListContainer unitType="storage" /> - </TabPane> - </TabContent> - </div> - ) -} - -export default UnitTabsComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/AddPrefabComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/AddPrefabComponent.js deleted file mode 100644 index 75418f9d..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/AddPrefabComponent.js +++ /dev/null @@ -1,10 +0,0 @@ -import React from 'react' - -const AddPrefabComponent = ({ onClick }) => ( - <div className="btn btn-primary btn-block" onClick={onClick}> - <span className="fa fa-floppy-o mr-2" /> - Save this rack to a prefab - </div> -) - -export default AddPrefabComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/BackToRoomComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/BackToRoomComponent.js deleted file mode 100644 index c14775bf..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/BackToRoomComponent.js +++ /dev/null @@ -1,10 +0,0 @@ -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/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/DeleteRackComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/DeleteRackComponent.js deleted file mode 100644 index 23b0daac..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/DeleteRackComponent.js +++ /dev/null @@ -1,10 +0,0 @@ -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/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/EmptySlotComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/EmptySlotComponent.js deleted file mode 100644 index d7e30f1d..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/EmptySlotComponent.js +++ /dev/null @@ -1,13 +0,0 @@ -import React from 'react' - -const EmptySlotComponent = ({ position, onAdd }) => ( - <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> - <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/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineComponent.js deleted file mode 100644 index caa3dc04..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineComponent.js +++ /dev/null @@ -1,43 +0,0 @@ -import React from 'react' -import Shapes from '../../../../../shapes' - -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, onClick }) => { - 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: 'white' }} - > - <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/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineListComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineListComponent.js deleted file mode 100644 index 12be26bd..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineListComponent.js +++ /dev/null @@ -1,20 +0,0 @@ -import React from 'react' -import EmptySlotContainer from '../../../../../containers/app/sidebars/topology/rack/EmptySlotContainer' -import MachineContainer from '../../../../../containers/app/sidebars/topology/rack/MachineContainer' -import './MachineListComponent.sass' - -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/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineListComponent.sass b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineListComponent.sass deleted file mode 100644 index 11b82c93..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineListComponent.sass +++ /dev/null @@ -1,2 +0,0 @@ -.machine-list li - min-height: 64px diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackNameComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackNameComponent.js deleted file mode 100644 index b701909a..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackNameComponent.js +++ /dev/null @@ -1,6 +0,0 @@ -import React from 'react' -import NameComponent from '../NameComponent' - -const RackNameComponent = ({ rackName, onEdit }) => <NameComponent name={rackName} onEdit={onEdit} /> - -export default RackNameComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebarComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebarComponent.js deleted file mode 100644 index ca41bf57..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebarComponent.js +++ /dev/null @@ -1,25 +0,0 @@ -import React from 'react' -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.sass' -import AddPrefabContainer from '../../../../../containers/app/sidebars/topology/rack/AddPrefabContainer' - -const RackSidebarComponent = () => { - return ( - <div className="rack-sidebar-container flex-column"> - <div className="rack-sidebar-header-container"> - <RackNameContainer /> - <BackToRoomContainer /> - <AddPrefabContainer /> - <DeleteRackContainer /> - </div> - <div className="machine-list-container mt-2"> - <MachineListContainer /> - </div> - </div> - ) -} - -export default RackSidebarComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebarComponent.sass b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebarComponent.sass deleted file mode 100644 index 29fec02a..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebarComponent.sass +++ /dev/null @@ -1,11 +0,0 @@ -.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/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/BackToBuildingComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/BackToBuildingComponent.js deleted file mode 100644 index 64c0a1f6..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/BackToBuildingComponent.js +++ /dev/null @@ -1,10 +0,0 @@ -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/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/DeleteRoomComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/DeleteRoomComponent.js deleted file mode 100644 index 78417359..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/DeleteRoomComponent.js +++ /dev/null @@ -1,10 +0,0 @@ -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/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/EditRoomComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/EditRoomComponent.js deleted file mode 100644 index 857a646f..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/EditRoomComponent.js +++ /dev/null @@ -1,22 +0,0 @@ -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/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RackConstructionComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RackConstructionComponent.js deleted file mode 100644 index 44566f61..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RackConstructionComponent.js +++ /dev/null @@ -1,27 +0,0 @@ -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/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomNameComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomNameComponent.js deleted file mode 100644 index d637828e..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomNameComponent.js +++ /dev/null @@ -1,6 +0,0 @@ -import React from 'react' -import NameComponent from '../NameComponent' - -const RoomNameComponent = ({ roomName, onEdit }) => <NameComponent name={roomName} onEdit={onEdit} /> - -export default RoomNameComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomSidebarComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomSidebarComponent.js deleted file mode 100644 index 1bc6533e..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomSidebarComponent.js +++ /dev/null @@ -1,20 +0,0 @@ -import React from 'react' -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' - -const RoomSidebarComponent = () => { - return ( - <div> - <RoomNameContainer /> - <BackToBuildingContainer /> - <RackConstructionContainer /> - <EditRoomContainer /> - <DeleteRoomContainer /> - </div> - ) -} - -export default RoomSidebarComponent diff --git a/opendc-web/opendc-web-ui/src/components/context/ContextSelectionSection.js b/opendc-web/opendc-web-ui/src/components/context/ContextSelectionSection.js new file mode 100644 index 00000000..5d3a6441 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/context/ContextSelectionSection.js @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2021 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. + */ + +import PropTypes from 'prop-types' +import { contextSelectionSection } from './ContextSelectionSection.module.scss' + +function ContextSelectionSection({ children }) { + return <section className={contextSelectionSection}>{children}</section> +} + +ContextSelectionSection.propTypes = { + children: PropTypes.node, +} + +export default ContextSelectionSection diff --git a/opendc-web/opendc-web-ui/src/components/context/ContextSelectionSection.module.scss b/opendc-web/opendc-web-ui/src/components/context/ContextSelectionSection.module.scss new file mode 100644 index 00000000..0e902af0 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/context/ContextSelectionSection.module.scss @@ -0,0 +1,28 @@ +/*! + * Copyright (c) 2021 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. + */ + +.contextSelectionSection { + padding-left: var(--pf-c-page__main-breadcrumb--PaddingLeft); + flex-shrink: 0; + border-bottom: var(--pf-global--BorderWidth--sm) solid var(--pf-global--BorderColor--100); + background-color: var(--pf-c-page__main-breadcrumb--BackgroundColor); +} diff --git a/opendc-web/opendc-web-ui/src/components/context/ContextSelector.js b/opendc-web/opendc-web-ui/src/components/context/ContextSelector.js new file mode 100644 index 00000000..3712cfa0 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/context/ContextSelector.js @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2021 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. + */ + +import PropTypes from 'prop-types' +import { ContextSelector as PFContextSelector, ContextSelectorItem } from '@patternfly/react-core' +import { useMemo, useState, useReducer } from 'react' +import { contextSelector } from './ContextSelector.module.scss' + +function ContextSelector({ activeItem, items, onSelect, label }) { + const [isOpen, toggle] = useReducer((isOpen) => !isOpen, false) + const [searchValue, setSearchValue] = useState('') + + const filteredItems = useMemo( + () => items.filter(({ name }) => name.toLowerCase().indexOf(searchValue.toLowerCase()) !== -1) || items, + [items, searchValue] + ) + + return ( + <PFContextSelector + menuAppendTo={global.document?.body} + className={contextSelector} + toggleText={activeItem ? `${label}: ${activeItem.name}` : label} + onSearchInputChange={(value) => setSearchValue(value)} + searchInputValue={searchValue} + isOpen={isOpen} + onToggle={toggle} + onSelect={(event) => { + const targetId = event.target.value + const target = items.find((item) => item._id === targetId) + + toggle() + onSelect(target) + }} + > + {filteredItems.map((item) => ( + <ContextSelectorItem key={item._id} value={item._id}> + {item.name} + </ContextSelectorItem> + ))} + </PFContextSelector> + ) +} + +const Item = PropTypes.shape({ + _id: PropTypes.string.isRequired, + name: PropTypes.string.isRequired, +}) + +ContextSelector.propTypes = { + activeItem: Item, + items: PropTypes.arrayOf(Item).isRequired, + onSelect: PropTypes.func.isRequired, + label: PropTypes.string, +} + +export default ContextSelector diff --git a/opendc-web/opendc-web-ui/src/components/context/ContextSelector.module.scss b/opendc-web/opendc-web-ui/src/components/context/ContextSelector.module.scss new file mode 100644 index 00000000..fefba41f --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/context/ContextSelector.module.scss @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2021 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. + */ + +.contextSelector { + width: auto; + margin-right: 20px; + + --pf-c-context-selector__toggle--PaddingTop: var(--pf-global--spacer--sm); + --pf-c-context-selector__toggle--PaddingRight: 0; + --pf-c-context-selector__toggle--PaddingBottom: var(--pf-global--spacer--sm); + --pf-c-context-selector__toggle--PaddingLeft: 0; + --pf-c-context-selector__toggle--BorderWidth: 0; + --pf-c-context-selector__toggle-text--FontSize: var(--pf-global--FontSize--sm); + + & :global(.pf-c-context-selector__toggle) { + &:active, + &:focus-within, + &:global(.pf-m-active) { + --pf-c-context-selector__toggle--after--BorderBottomWidth: 0; + } + } + + &:global(.pf-m-expanded) > :global(.pf-c-context-selector__toggle) { + --pf-c-context-selector__toggle--after--BorderBottomWidth: 0; + } +} diff --git a/opendc-web/opendc-web-ui/src/components/context/PortfolioSelector.js b/opendc-web/opendc-web-ui/src/components/context/PortfolioSelector.js new file mode 100644 index 00000000..694681ac --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/context/PortfolioSelector.js @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2021 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. + */ + +import { useRouter } from 'next/router' +import { useMemo } from 'react' +import { useProjectPortfolios } from '../../data/project' +import ContextSelector from './ContextSelector' + +function PortfolioSelector() { + const router = useRouter() + const { project, portfolio: activePortfolioId } = router.query + const { data: portfolios = [] } = useProjectPortfolios(project) + const activePortfolio = useMemo(() => portfolios.find((portfolio) => portfolio._id === activePortfolioId), [ + activePortfolioId, + portfolios, + ]) + + return ( + <ContextSelector + label="Portfolio" + activeItem={activePortfolio} + items={portfolios} + onSelect={(portfolio) => router.push(`/projects/${portfolio.projectId}/portfolios/${portfolio._id}`)} + /> + ) +} + +export default PortfolioSelector diff --git a/opendc-web/opendc-web-ui/src/components/context/ProjectSelector.js b/opendc-web/opendc-web-ui/src/components/context/ProjectSelector.js new file mode 100644 index 00000000..753632ab --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/context/ProjectSelector.js @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2021 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. + */ + +import PropTypes from 'prop-types' +import { useRouter } from 'next/router' +import { useMemo } from 'react' +import { useProjects } from '../../data/project' +import ContextSelector from './ContextSelector' + +function ProjectSelector({ projectId }) { + const router = useRouter() + const { data: projects = [] } = useProjects() + const activeProject = useMemo(() => projects.find((project) => project._id === projectId), [projectId, projects]) + + return ( + <ContextSelector + label="Project" + activeItem={activeProject} + items={projects} + onSelect={(project) => router.push(`/projects/${project._id}`)} + /> + ) +} + +ProjectSelector.propTypes = { + projectId: PropTypes.string, +} + +export default ProjectSelector diff --git a/opendc-web/opendc-web-ui/src/components/context/TopologySelector.js b/opendc-web/opendc-web-ui/src/components/context/TopologySelector.js new file mode 100644 index 00000000..d5e51c6c --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/context/TopologySelector.js @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2021 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. + */ + +import PropTypes from 'prop-types' +import { useRouter } from 'next/router' +import { useMemo } from 'react' +import { useProjectTopologies } from '../../data/topology' +import ContextSelector from './ContextSelector' + +function TopologySelector({ projectId, topologyId }) { + const router = useRouter() + const { data: topologies = [] } = useProjectTopologies(projectId) + const activeTopology = useMemo(() => topologies.find((topology) => topology._id === topologyId), [ + topologyId, + topologies, + ]) + + return ( + <ContextSelector + label="Topology" + activeItem={activeTopology} + items={topologies} + onSelect={(topology) => router.push(`/projects/${topology.projectId}/topologies/${topology._id}`)} + /> + ) +} + +TopologySelector.propTypes = { + projectId: PropTypes.string, + topologyId: PropTypes.string, +} + +export default TopologySelector diff --git a/opendc-web/opendc-web-ui/src/components/home/ContactSection.js b/opendc-web/opendc-web-ui/src/components/home/ContactSection.js deleted file mode 100644 index d5c6e55f..00000000 --- a/opendc-web/opendc-web-ui/src/components/home/ContactSection.js +++ /dev/null @@ -1,55 +0,0 @@ -import React from 'react' -import FontAwesome from 'react-fontawesome' -import './ContactSection.sass' -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 /> - <strong>Disclaimer: </strong> - 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/opendc-web/opendc-web-ui/src/components/home/ContactSection.sass b/opendc-web/opendc-web-ui/src/components/home/ContactSection.sass deleted file mode 100644 index 997f8d98..00000000 --- a/opendc-web/opendc-web-ui/src/components/home/ContactSection.sass +++ /dev/null @@ -1,15 +0,0 @@ -.contact-section - background-color: #444 - color: #ddd - - a - color: #ddd - - a:hover - color: #fff - - .tudelft-icon - height: 100px - - .disclaimer - color: #cccccc diff --git a/opendc-web/opendc-web-ui/src/components/home/ContentSection.js b/opendc-web/opendc-web-ui/src/components/home/ContentSection.js deleted file mode 100644 index 9d4832d9..00000000 --- a/opendc-web/opendc-web-ui/src/components/home/ContentSection.js +++ /dev/null @@ -1,19 +0,0 @@ -import classNames from 'classnames' -import PropTypes from 'prop-types' -import React from 'react' -import './ContentSection.sass' - -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/opendc-web/opendc-web-ui/src/components/home/ContentSection.sass b/opendc-web/opendc-web-ui/src/components/home/ContentSection.sass deleted file mode 100644 index a4c8bd66..00000000 --- a/opendc-web/opendc-web-ui/src/components/home/ContentSection.sass +++ /dev/null @@ -1,9 +0,0 @@ -@import ../../style-globals/_variables.sass - -.content-section - padding-top: 50px - padding-bottom: 150px - text-align: center - - h1 - margin-bottom: 30px diff --git a/opendc-web/opendc-web-ui/src/components/home/IntroSection.js b/opendc-web/opendc-web-ui/src/components/home/IntroSection.js deleted file mode 100644 index a799272a..00000000 --- a/opendc-web/opendc-web-ui/src/components/home/IntroSection.js +++ /dev/null @@ -1,40 +0,0 @@ -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/opendc-web/opendc-web-ui/src/components/home/JumbotronHeader.js b/opendc-web/opendc-web-ui/src/components/home/JumbotronHeader.js deleted file mode 100644 index 6a9ea00c..00000000 --- a/opendc-web/opendc-web-ui/src/components/home/JumbotronHeader.js +++ /dev/null @@ -1,29 +0,0 @@ -import React from 'react' -import { Container, Jumbotron, Button } from 'reactstrap' -import './JumbotronHeader.sass' - -const JumbotronHeader = () => ( - <section className="jumbotron-header"> - <Container> - <Jumbotron className="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" /> - <p className="lead mt-5"> - <Button - tag="a" - target="_blank" - href="https://atlarge-research.com/pdfs/ccgrid21-opendc-paper.pdf" - color="warning" - > - Read about <strong>OpenDC 2.0</strong> <i className="fa fa-external-link" /> - </Button> - </p> - </Jumbotron> - </Container> - </section> -) - -export default JumbotronHeader diff --git a/opendc-web/opendc-web-ui/src/components/home/JumbotronHeader.sass b/opendc-web/opendc-web-ui/src/components/home/JumbotronHeader.sass deleted file mode 100644 index 1b6a89fd..00000000 --- a/opendc-web/opendc-web-ui/src/components/home/JumbotronHeader.sass +++ /dev/null @@ -1,24 +0,0 @@ -.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/opendc-web/opendc-web-ui/src/components/home/ModelingSection.js b/opendc-web/opendc-web-ui/src/components/home/ModelingSection.js deleted file mode 100644 index 643dca65..00000000 --- a/opendc-web/opendc-web-ui/src/components/home/ModelingSection.js +++ /dev/null @@ -1,22 +0,0 @@ -import React from 'react' -import ScreenshotSection from './ScreenshotSection' - -const ModelingSection = () => ( - <ScreenshotSection - name="modeling" - title="Datacenter Modeling" - imageUrl="/img/screenshot-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/opendc-web/opendc-web-ui/src/components/home/ScreenshotSection.js b/opendc-web/opendc-web-ui/src/components/home/ScreenshotSection.js deleted file mode 100644 index 263590d5..00000000 --- a/opendc-web/opendc-web-ui/src/components/home/ScreenshotSection.js +++ /dev/null @@ -1,29 +0,0 @@ -import classNames from 'classnames' -import React from 'react' -import { Row, Col } from 'reactstrap' -import ContentSection from './ContentSection' -import './ScreenshotSection.sass' - -const ScreenshotSection = ({ name, title, imageUrl, caption, imageIsRight, children }) => ( - <ContentSection name={name} title={title}> - <Row> - <Col - xl="5" - lg="5" - md="5" - sm="!2" - className={classNames('text-left my-auto', { - 'order-1': !imageIsRight, - })} - > - {children} - </Col> - <Col xl="7" lg="7" md="7" sm="12"> - <img src={imageUrl} className="col-12 screenshot" alt={caption} /> - <Row className="text-muted justify-content-center">{caption}</Row> - </Col> - </Row> - </ContentSection> -) - -export default ScreenshotSection diff --git a/opendc-web/opendc-web-ui/src/components/home/ScreenshotSection.sass b/opendc-web/opendc-web-ui/src/components/home/ScreenshotSection.sass deleted file mode 100644 index 6b1a6ec4..00000000 --- a/opendc-web/opendc-web-ui/src/components/home/ScreenshotSection.sass +++ /dev/null @@ -1,4 +0,0 @@ -.screenshot - padding-left: 0 - padding-right: 0 - margin-bottom: 5px diff --git a/opendc-web/opendc-web-ui/src/components/home/SimulationSection.js b/opendc-web/opendc-web-ui/src/components/home/SimulationSection.js deleted file mode 100644 index 8e98717a..00000000 --- a/opendc-web/opendc-web-ui/src/components/home/SimulationSection.js +++ /dev/null @@ -1,50 +0,0 @@ -import React from 'react' -import { Col, Row } from 'reactstrap' -import ContentSection from './ContentSection' - -const SimulationSection = () => { - return ( - <ContentSection name="project" title="Datecenter Simulation"> - <Row> - <Col xl="5" lg="5" md="5" sm="2" className="text-left my-auto order-1"> - <h3>Working with OpenDC:</h3> - <ul> - <li>Seamlessly switch between construction and simulation modes</li> - <li> - Choose one of several predefined workloads (Business Critical, Workflows, Machine Learning, - Serverless, etc.) - </li> - <li>Compare datacenter topologies using automated plots and visual summaries</li> - </ul> - </Col> - <Col xl="7" lg="7" md="7" sm="12"> - <img - src="/img/screenshot-simulation.png" - className="col-12 screenshot" - alt="Running an experiment in OpenDC" - /> - <Row className="text-muted justify-content-center">Running an experiment in OpenDC</Row> - </Col> - </Row> - <Row className="mt-5"> - <Col xl="5" lg="5" md="5" sm="2" className="text-left my-auto"> - <h3>OpenDC's Simulator:</h3> - <ul> - <li>Includes a detailed operational model of modern datacenters</li> - <li> - Support for emerging datacenter technologies around <em>cloud computing</em>,{' '} - <em>serverless computing</em>, <em>big data</em>, and <em>machine learning</em> - </li> - </ul> - </Col> - - <Col xl="7" lg="7" md="7" sm="12"> - <img src="/img/opendc-architecture.png" className="col-12 screenshot" alt="OpenDC's Architecture" /> - <Row className="text-muted justify-content-center">OpenDC's Architecture</Row> - </Col> - </Row> - </ContentSection> - ) -} - -export default SimulationSection diff --git a/opendc-web/opendc-web-ui/src/components/home/StakeholderSection.js b/opendc-web/opendc-web-ui/src/components/home/StakeholderSection.js deleted file mode 100644 index e5ed9683..00000000 --- a/opendc-web/opendc-web-ui/src/components/home/StakeholderSection.js +++ /dev/null @@ -1,30 +0,0 @@ -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/opendc-web/opendc-web-ui/src/components/home/TeamSection.js b/opendc-web/opendc-web-ui/src/components/home/TeamSection.js deleted file mode 100644 index 1ee1cbf5..00000000 --- a/opendc-web/opendc-web-ui/src/components/home/TeamSection.js +++ /dev/null @@ -1,65 +0,0 @@ -import React from 'react' -import { Row, Col } from 'reactstrap' -import ContentSection from './ContentSection' - -const TeamLead = ({ photoId, name, description }) => ( - <Col xl="3" lg="3" md="4" sm="6" className="justify-content-center"> - <Col - tag="img" - src={'img/portraits/' + photoId + '.png'} - xl="10" - lg="10" - md="10" - sm="8" - col="5" - className="mb-2 mt-2" - alt={name} - /> - <Col> - <h4>{name}</h4> - <div className="team-member-description">{description}</div> - </Col> - </Col> -) - -const TeamMember = ({ photoId, name }) => ( - <Col xl="2" lg="2" md="3" sm="4" className="justify-content-center"> - <Col - tag="img" - src={'img/portraits/' + photoId + '.png'} - xl="10" - lg="10" - md="10" - sm="8" - col="5" - className="mb-2 mt-2" - alt={name} - /> - <Col> - <h5>{name}</h5> - </Col> - </Col> -) - -const TeamSection = () => ( - <ContentSection name="team" title="OpenDC Team"> - <Row className="justify-content-center"> - <TeamLead photoId="aiosup" name="Prof. dr. ir. Alexandru Iosup" description="Project Lead" /> - <TeamLead photoId="fmastenbroek" name="Fabian Mastenbroek" description="Technology Lead" /> - <TeamLead photoId="gandreadis" name="Georgios Andreadis" description="Former Technology Lead (2018-2020)" /> - <TeamLead photoId="vvanbeek" name="Vincent van Beek" description="Former Technology Lead (2017-2018)" /> - </Row> - <Row className="justify-content-center mt-5"> - <TeamMember photoId="loverweel" name="Leon Overweel" /> - <TeamMember photoId="lfdversluis" name="Laurens Versluis" /> - <TeamMember photoId="evaneyk" name="Erwin van Eyk" /> - <TeamMember photoId="sjounaid" name="Soufiane Jounaid" /> - <TeamMember photoId="wlai" name="Wenchen Lai" /> - <TeamMember photoId="hhe" name="Hongyu He" /> - <TeamMember photoId="jburley" name="Jacob Burley" /> - <TeamMember photoId="jbosch" name="Jaro Bosch" /> - </Row> - </ContentSection> -) - -export default TeamSection diff --git a/opendc-web/opendc-web-ui/src/components/home/TechnologiesSection.js b/opendc-web/opendc-web-ui/src/components/home/TechnologiesSection.js deleted file mode 100644 index c6013c71..00000000 --- a/opendc-web/opendc-web-ui/src/components/home/TechnologiesSection.js +++ /dev/null @@ -1,40 +0,0 @@ -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">MongoDB</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/opendc-web/opendc-web-ui/src/components/modals/ConfirmationModal.js b/opendc-web/opendc-web-ui/src/components/modals/ConfirmationModal.js deleted file mode 100644 index 589047dc..00000000 --- a/opendc-web/opendc-web-ui/src/components/modals/ConfirmationModal.js +++ /dev/null @@ -1,37 +0,0 @@ -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/opendc-web/opendc-web-ui/src/components/modals/Modal.js b/opendc-web/opendc-web-ui/src/components/modals/Modal.js deleted file mode 100644 index 21b7f119..00000000 --- a/opendc-web/opendc-web-ui/src/components/modals/Modal.js +++ /dev/null @@ -1,53 +0,0 @@ -import React, { useState, useEffect } from 'react' -import PropTypes from 'prop-types' -import { Modal as RModal, ModalHeader, ModalBody, ModalFooter, Button } from 'reactstrap' - -function Modal({ children, title, show, onSubmit, onCancel, submitButtonType, submitButtonText }) { - const [modal, setModal] = useState(show) - - useEffect(() => setModal(show), [show]) - - const toggle = () => setModal(!modal) - const cancel = () => { - if (onCancel() !== false) { - toggle() - } - } - const submit = () => { - if (onSubmit() !== false) { - toggle() - } - } - - return ( - <RModal isOpen={modal} toggle={cancel}> - <ModalHeader toggle={cancel}>{title}</ModalHeader> - <ModalBody>{children}</ModalBody> - <ModalFooter> - <Button color="secondary" onClick={cancel}> - Close - </Button> - <Button color={submitButtonType} onClick={submit}> - {submitButtonText} - </Button> - </ModalFooter> - </RModal> - ) -} - -Modal.propTypes = { - title: PropTypes.string.isRequired, - show: PropTypes.bool.isRequired, - onSubmit: PropTypes.func.isRequired, - onCancel: PropTypes.func.isRequired, - submitButtonType: PropTypes.string, - submitButtonText: PropTypes.string, -} - -Modal.defaultProps = { - submitButtonType: 'primary', - submitButtonText: 'Save', - show: false, -} - -export default Modal diff --git a/opendc-web/opendc-web-ui/src/components/modals/TextInputModal.js b/opendc-web/opendc-web-ui/src/components/modals/TextInputModal.js deleted file mode 100644 index d0918c7e..00000000 --- a/opendc-web/opendc-web-ui/src/components/modals/TextInputModal.js +++ /dev/null @@ -1,54 +0,0 @@ -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) { - 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/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewPortfolioModalComponent.js b/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewPortfolioModalComponent.js deleted file mode 100644 index 3c6b8724..00000000 --- a/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewPortfolioModalComponent.js +++ /dev/null @@ -1,78 +0,0 @@ -import PropTypes from 'prop-types' -import React, { useRef } from 'react' -import { Form, FormGroup, Input, Label } from 'reactstrap' -import Modal from '../Modal' -import { AVAILABLE_METRICS, METRIC_NAMES } from '../../../util/available-metrics' - -const NewPortfolioModalComponent = ({ show, callback }) => { - const form = useRef(null) - const textInput = useRef(null) - const repeatsInput = useRef(null) - const metricCheckboxes = useRef({}) - - const onSubmit = () => { - if (form.current.reportValidity()) { - callback(textInput.current.value, { - enabledMetrics: AVAILABLE_METRICS.filter((metric) => metricCheckboxes.current[metric].checked), - repeatsPerScenario: parseInt(repeatsInput.current.value), - }) - - return true - } else { - return false - } - } - const onCancel = () => callback(undefined) - - return ( - <Modal title="New Portfolio" show={show} onSubmit={onSubmit} onCancel={onCancel}> - <Form - onSubmit={(e) => { - e.preventDefault() - this.onSubmit() - }} - innerRef={form} - > - <FormGroup> - <Label for="name">Name</Label> - <Input name="name" type="text" required innerRef={textInput} placeholder="My Portfolio" /> - </FormGroup> - <h4>Targets</h4> - <h5>Metrics</h5> - <FormGroup> - {AVAILABLE_METRICS.map((metric) => ( - <FormGroup check key={metric}> - <Label for={metric} check> - <Input - name={metric} - type="checkbox" - innerRef={(ref) => (metricCheckboxes.current[metric] = ref)} - /> - {METRIC_NAMES[metric]} - </Label> - </FormGroup> - ))} - </FormGroup> - <FormGroup> - <Label for="repeats">Repeats per scenario</Label> - <Input - name="repeats" - type="number" - required - innerRef={repeatsInput} - defaultValue="1" - min="1" - step="1" - /> - </FormGroup> - </Form> - </Modal> - ) -} - -NewPortfolioModalComponent.propTypes = { - show: PropTypes.bool.isRequired, - callback: PropTypes.func.isRequired, -} - -export default NewPortfolioModalComponent diff --git a/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewScenarioModalComponent.js b/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewScenarioModalComponent.js deleted file mode 100644 index 01a5719c..00000000 --- a/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewScenarioModalComponent.js +++ /dev/null @@ -1,144 +0,0 @@ -import PropTypes from 'prop-types' -import React, { useRef } from 'react' -import { Form, FormGroup, Input, Label } from 'reactstrap' -import Shapes from '../../../shapes' -import Modal from '../Modal' - -const NewScenarioModalComponent = ({ - show, - callback, - currentPortfolioId, - currentPortfolioScenarioIds, - traces, - topologies, - schedulers, -}) => { - const form = useRef(null) - const textInput = useRef(null) - const traceSelect = useRef(null) - const traceLoadInput = useRef(null) - const topologySelect = useRef(null) - const failuresCheckbox = useRef(null) - const performanceInterferenceCheckbox = useRef(null) - const schedulerSelect = useRef(null) - - const onSubmit = () => { - if (!form.current.reportValidity()) { - return false - } - callback( - textInput.current.value, - currentPortfolioId, - { - traceId: traceSelect.current.value, - loadSamplingFraction: parseFloat(traceLoadInput.current.value), - }, - { - topologyId: topologySelect.current.value, - }, - { - failuresEnabled: failuresCheckbox.current.checked, - performanceInterferenceEnabled: performanceInterferenceCheckbox.current.checked, - schedulerName: schedulerSelect.current.value, - } - ) - return true - } - const onCancel = () => { - callback(undefined) - } - - return ( - <Modal title="New Scenario" show={show} onSubmit={onSubmit} onCancel={onCancel}> - <Form - onSubmit={(e) => { - e.preventDefault() - onSubmit() - }} - innerRef={form} - > - <FormGroup> - <Label for="name">Name</Label> - <Input - name="name" - type="text" - required - disabled={currentPortfolioScenarioIds.length === 0} - defaultValue={currentPortfolioScenarioIds.length === 0 ? 'Base scenario' : ''} - innerRef={textInput} - /> - </FormGroup> - <h4>Trace</h4> - <FormGroup> - <Label for="trace">Trace</Label> - <Input name="trace" type="select" innerRef={traceSelect}> - {traces.map((trace) => ( - <option value={trace._id} key={trace._id}> - {trace.name} - </option> - ))} - </Input> - </FormGroup> - <FormGroup> - <Label for="trace-load">Load sampling fraction</Label> - <Input - name="trace-load" - type="number" - innerRef={traceLoadInput} - required - defaultValue="1" - min="0" - max="1" - step="0.1" - /> - </FormGroup> - <h4>Topology</h4> - <div className="form-group"> - <Label for="topology">Topology</Label> - <Input name="topology" type="select" innerRef={topologySelect}> - {topologies.map((topology) => ( - <option value={topology._id} key={topology._id}> - {topology.name} - </option> - ))} - </Input> - </div> - <h4>Operational Phenomena</h4> - <FormGroup check> - <Label check for="failures"> - <Input type="checkbox" name="failures" innerRef={failuresCheckbox} />{' '} - <span className="ml-2">Enable failures</span> - </Label> - </FormGroup> - <FormGroup check> - <Label check for="perf-interference"> - <Input type="checkbox" name="perf-interference" innerRef={performanceInterferenceCheckbox} />{' '} - <span className="ml-2">Enable performance interference</span> - </Label> - </FormGroup> - <FormGroup> - <Label for="scheduler">Scheduler</Label> - <Input name="scheduler" type="select" innerRef={schedulerSelect}> - {schedulers.map((scheduler) => ( - <option value={scheduler.name} key={scheduler.name}> - {scheduler.name} - </option> - ))} - </Input> - </FormGroup> - </Form> - </Modal> - ) -} - -NewScenarioModalComponent.propTypes = { - show: PropTypes.bool.isRequired, - currentPortfolioId: PropTypes.string.isRequired, - currentPortfolioScenarioIds: PropTypes.arrayOf(PropTypes.string), - traces: PropTypes.arrayOf(Shapes.Trace), - topologies: PropTypes.arrayOf(Shapes.Topology), - schedulers: PropTypes.arrayOf(Shapes.Scheduler), - callback: PropTypes.func.isRequired, -} - -export default NewScenarioModalComponent diff --git a/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewTopologyModalComponent.js b/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewTopologyModalComponent.js deleted file mode 100644 index 9fee8831..00000000 --- a/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewTopologyModalComponent.js +++ /dev/null @@ -1,71 +0,0 @@ -import PropTypes from 'prop-types' -import { Form, FormGroup, Input, Label } from 'reactstrap' -import React, { useRef } from 'react' -import Shapes from '../../../shapes' -import Modal from '../Modal' - -const NewTopologyModalComponent = ({ show, onCreateTopology, onDuplicateTopology, onCancel, topologies }) => { - const form = useRef(null) - const textInput = useRef(null) - const originTopology = useRef(null) - - const onCreate = () => { - onCreateTopology(textInput.current.value) - } - - const onDuplicate = () => { - onDuplicateTopology(textInput.current.value, originTopology.current.value) - } - - const onSubmit = () => { - if (!form.current.reportValidity()) { - return false - } else if (originTopology.current.selectedIndex === 0) { - onCreate() - } else { - onDuplicate() - } - - return true - } - - return ( - <Modal title="New Topology" show={show} onSubmit={onSubmit} onCancel={onCancel}> - <Form - onSubmit={(e) => { - e.preventDefault() - onSubmit() - }} - innerRef={form} - > - <FormGroup> - <Label for="name">Name</Label> - <Input name="name" type="text" required innerRef={textInput} /> - </FormGroup> - <FormGroup> - <Label for="origin">Topology to duplicate</Label> - <Input name="origin" type="select" innerRef={originTopology}> - <option value={-1} key={-1}> - None - start from scratch - </option> - {topologies.map((topology) => ( - <option value={topology._id} key={topology._id}> - {topology.name} - </option> - ))} - </Input> - </FormGroup> - </Form> - </Modal> - ) -} - -NewTopologyModalComponent.propTypes = { - show: PropTypes.bool.isRequired, - topologies: PropTypes.arrayOf(Shapes.Topology), - onCreateTopology: PropTypes.func.isRequired, - onDuplicateTopology: PropTypes.func.isRequired, - onCancel: PropTypes.func.isRequired, -} - -export default NewTopologyModalComponent diff --git a/opendc-web/opendc-web-ui/src/components/navigation/AppNavbarComponent.js b/opendc-web/opendc-web-ui/src/components/navigation/AppNavbarComponent.js deleted file mode 100644 index c5de3d0b..00000000 --- a/opendc-web/opendc-web-ui/src/components/navigation/AppNavbarComponent.js +++ /dev/null @@ -1,26 +0,0 @@ -import React from 'react' -import FontAwesome from 'react-fontawesome' -import { Link } from 'react-router-dom' -import { NavLink } from 'reactstrap' -import Navbar, { NavItem } from './Navbar' -import './Navbar.sass' - -const AppNavbarComponent = ({ project, fullWidth }) => ( - <Navbar fullWidth={fullWidth}> - <NavItem route="/projects"> - <NavLink tag={Link} title="My Projects" to="/projects"> - <FontAwesome name="list" className="mr-2" /> - My Projects - </NavLink> - </NavItem> - {project ? ( - <NavItem> - <NavLink tag={Link} title="Current Project" to={`/projects/${project._id}`}> - <span>{project.name}</span> - </NavLink> - </NavItem> - ) : undefined} - </Navbar> -) - -export default AppNavbarComponent diff --git a/opendc-web/opendc-web-ui/src/components/navigation/HomeNavbar.js b/opendc-web/opendc-web-ui/src/components/navigation/HomeNavbar.js deleted file mode 100644 index 08d222ea..00000000 --- a/opendc-web/opendc-web-ui/src/components/navigation/HomeNavbar.js +++ /dev/null @@ -1,23 +0,0 @@ -import React from 'react' -import { NavItem, NavLink } from 'reactstrap' -import Navbar from './Navbar' -import './Navbar.sass' - -const ScrollNavItem = ({ id, name }) => ( - <NavItem> - <NavLink href={id}>{name}</NavLink> - </NavItem> -) - -const HomeNavbar = () => ( - <Navbar fullWidth={false}> - <ScrollNavItem id="#stakeholders" name="Stakeholders" /> - <ScrollNavItem id="#modeling" name="Modeling" /> - <ScrollNavItem id="#project" name="Project" /> - <ScrollNavItem id="#technologies" name="Technologies" /> - <ScrollNavItem id="#team" name="Team" /> - <ScrollNavItem id="#contact" name="Contact" /> - </Navbar> -) - -export default HomeNavbar diff --git a/opendc-web/opendc-web-ui/src/components/navigation/LogoutButton.js b/opendc-web/opendc-web-ui/src/components/navigation/LogoutButton.js deleted file mode 100644 index 78b02b44..00000000 --- a/opendc-web/opendc-web-ui/src/components/navigation/LogoutButton.js +++ /dev/null @@ -1,17 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import FontAwesome from 'react-fontawesome' -import { Link } from 'react-router-dom' -import { NavLink } from 'reactstrap' - -const LogoutButton = ({ onLogout }) => ( - <NavLink tag={Link} className="logout" title="Sign out" to="#" onClick={onLogout}> - <FontAwesome name="power-off" size="lg" /> - </NavLink> -) - -LogoutButton.propTypes = { - onLogout: PropTypes.func.isRequired, -} - -export default LogoutButton diff --git a/opendc-web/opendc-web-ui/src/components/navigation/Navbar.js b/opendc-web/opendc-web-ui/src/components/navigation/Navbar.js deleted file mode 100644 index 55f98900..00000000 --- a/opendc-web/opendc-web-ui/src/components/navigation/Navbar.js +++ /dev/null @@ -1,92 +0,0 @@ -import React, { useState } from 'react' -import { Link, useLocation } from 'react-router-dom' -import { - Navbar as RNavbar, - NavItem as RNavItem, - NavLink, - NavbarBrand, - NavbarToggler, - Collapse, - Nav, - Container, -} from 'reactstrap' -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.sass' - -export const NAVBAR_HEIGHT = 60 - -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> -) - -export const NavItem = ({ route, children }) => { - const location = useLocation() - return <RNavItem active={location.pathname === route}>{children}</RNavItem> -} - -export const LoggedInSection = () => { - const location = useLocation() - return ( - <Nav navbar className="auth-links"> - {userIsLoggedIn() ? ( - [ - location.pathname === '/' ? ( - <NavItem route="/projects" key="projects"> - <NavLink tag={Link} title="My Projects" to="/projects"> - My Projects - </NavLink> - </NavItem> - ) : ( - <NavItem route="/profile" key="profile"> - <NavLink tag={Link} title="My Profile" to="/profile"> - <ProfileName /> - </NavLink> - </NavItem> - ), - <NavItem route="logout" key="logout"> - <Logout /> - </NavItem>, - ] - ) : ( - <NavItem route="login"> - <GitHubLink /> - <Login visible={true} /> - </NavItem> - )} - </Nav> - ) -} - -const Navbar = ({ fullWidth, children }) => { - const [isOpen, setIsOpen] = useState(false) - const toggle = () => setIsOpen(!isOpen) - - return ( - <RNavbar fixed="top" color="light" light expand="lg" id="navbar"> - <Container fluid={fullWidth}> - <NavbarToggler onClick={toggle} /> - <NavbarBrand tag={Link} to="/" title="OpenDC" className="opendc-brand"> - <img src="/img/logo.png" alt="OpenDC" /> - </NavbarBrand> - - <Collapse isOpen={isOpen} navbar> - <Nav className="mr-auto" navbar> - {children} - </Nav> - <LoggedInSection /> - </Collapse> - </Container> - </RNavbar> - ) -} - -export default Navbar diff --git a/opendc-web/opendc-web-ui/src/components/navigation/Navbar.sass b/opendc-web/opendc-web-ui/src/components/navigation/Navbar.sass deleted file mode 100644 index c9d2aad2..00000000 --- a/opendc-web/opendc-web-ui/src/components/navigation/Navbar.sass +++ /dev/null @@ -1,30 +0,0 @@ -@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 - padding-top: 10px - +clickable - - &:hover - background: $blue-dark diff --git a/opendc-web/opendc-web-ui/src/components/not-found/BlinkingCursor.js b/opendc-web/opendc-web-ui/src/components/not-found/BlinkingCursor.js deleted file mode 100644 index dbdba212..00000000 --- a/opendc-web/opendc-web-ui/src/components/not-found/BlinkingCursor.js +++ /dev/null @@ -1,6 +0,0 @@ -import React from 'react' -import './BlinkingCursor.sass' - -const BlinkingCursor = () => <span className="blinking-cursor">_</span> - -export default BlinkingCursor diff --git a/opendc-web/opendc-web-ui/src/components/not-found/BlinkingCursor.sass b/opendc-web/opendc-web-ui/src/components/not-found/BlinkingCursor.sass deleted file mode 100644 index ad91df85..00000000 --- a/opendc-web/opendc-web-ui/src/components/not-found/BlinkingCursor.sass +++ /dev/null @@ -1,35 +0,0 @@ -.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/opendc-web/opendc-web-ui/src/components/not-found/CodeBlock.js b/opendc-web/opendc-web-ui/src/components/not-found/CodeBlock.js deleted file mode 100644 index bcc522c9..00000000 --- a/opendc-web/opendc-web-ui/src/components/not-found/CodeBlock.js +++ /dev/null @@ -1,28 +0,0 @@ -import React from 'react' -import './CodeBlock.sass' - -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/opendc-web/opendc-web-ui/src/components/not-found/CodeBlock.sass b/opendc-web/opendc-web-ui/src/components/not-found/CodeBlock.sass deleted file mode 100644 index e452f917..00000000 --- a/opendc-web/opendc-web-ui/src/components/not-found/CodeBlock.sass +++ /dev/null @@ -1,3 +0,0 @@ -.code-block - white-space: pre-wrap - margin-top: 60px diff --git a/opendc-web/opendc-web-ui/src/components/not-found/TerminalWindow.js b/opendc-web/opendc-web-ui/src/components/not-found/TerminalWindow.js deleted file mode 100644 index a25e558a..00000000 --- a/opendc-web/opendc-web-ui/src/components/not-found/TerminalWindow.js +++ /dev/null @@ -1,33 +0,0 @@ -import React from 'react' -import { Link } from 'react-router-dom' -import BlinkingCursor from './BlinkingCursor' -import CodeBlock from './CodeBlock' -import './TerminalWindow.sass' - -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/opendc-web/opendc-web-ui/src/components/not-found/TerminalWindow.sass b/opendc-web/opendc-web-ui/src/components/not-found/TerminalWindow.sass deleted file mode 100644 index 7f05335a..00000000 --- a/opendc-web/opendc-web-ui/src/components/not-found/TerminalWindow.sass +++ /dev/null @@ -1,70 +0,0 @@ -.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/opendc-web/opendc-web-ui/src/components/portfolios/NewScenario.js b/opendc-web/opendc-web-ui/src/components/portfolios/NewScenario.js new file mode 100644 index 00000000..856282a7 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/portfolios/NewScenario.js @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2021 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. + */ + +import PropTypes from 'prop-types' +import { PlusIcon } from '@patternfly/react-icons' +import { Button } from '@patternfly/react-core' +import { useState } from 'react' +import { useMutation } from 'react-query' +import NewScenarioModal from './NewScenarioModal' + +function NewScenario({ portfolioId }) { + const [isVisible, setVisible] = useState(false) + const { mutate: addScenario } = useMutation('addScenario') + + const onSubmit = (name, portfolioId, trace, topology, operational) => { + addScenario({ + portfolioId, + name, + trace, + topology, + operational, + }) + setVisible(false) + } + + return ( + <> + <Button icon={<PlusIcon />} isSmall onClick={() => setVisible(true)}> + New Scenario + </Button> + <NewScenarioModal + portfolioId={portfolioId} + isOpen={isVisible} + onSubmit={onSubmit} + onCancel={() => setVisible(false)} + /> + </> + ) +} + +NewScenario.propTypes = { + portfolioId: PropTypes.string, +} + +export default NewScenario diff --git a/opendc-web/opendc-web-ui/src/components/portfolios/NewScenarioModal.js b/opendc-web/opendc-web-ui/src/components/portfolios/NewScenarioModal.js new file mode 100644 index 00000000..7f620c8c --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/portfolios/NewScenarioModal.js @@ -0,0 +1,159 @@ +import PropTypes from 'prop-types' +import React, { useRef, useState } from 'react' +import Modal from '../util/modals/Modal' +import { + Checkbox, + Form, + FormGroup, + FormSection, + FormSelect, + FormSelectOption, + NumberInput, + TextInput, +} from '@patternfly/react-core' +import { useSchedulers, useTraces } from '../../data/experiments' +import { useProjectTopologies } from '../../data/topology' +import { usePortfolio } from '../../data/project' + +const NewScenarioModal = ({ portfolioId, isOpen, onSubmit: onSubmitUpstream, onCancel: onCancelUpstream }) => { + const { data: portfolio } = usePortfolio(portfolioId) + const { data: topologies = [] } = useProjectTopologies(portfolio?.projectId) + const { data: traces = [] } = useTraces() + const { data: schedulers = [] } = useSchedulers() + + // eslint-disable-next-line no-unused-vars + const [isSubmitted, setSubmitted] = useState(false) + const [traceLoad, setTraceLoad] = useState(100) + const [trace, setTrace] = useState(undefined) + const [topology, setTopology] = useState(undefined) + const [scheduler, setScheduler] = useState(undefined) + const [failuresEnabled, setFailuresEnabled] = useState(false) + const [opPhenEnabled, setOpPhenEnabled] = useState(false) + const nameInput = useRef(null) + + const resetState = () => { + setSubmitted(false) + setTraceLoad(100) + setTrace(undefined) + setTopology(undefined) + setScheduler(undefined) + setFailuresEnabled(false) + setOpPhenEnabled(false) + nameInput.current.value = '' + } + + const onSubmit = (event) => { + setSubmitted(true) + + if (event) { + event.preventDefault() + } + + const name = nameInput.current.value + + onSubmitUpstream( + name, + portfolio._id, + { + traceId: trace || traces[0]._id, + loadSamplingFraction: traceLoad / 100, + }, + { + topologyId: topology || topologies[0]._id, + }, + { + failuresEnabled, + performanceInterferenceEnabled: opPhenEnabled, + schedulerName: scheduler || schedulers[0].name, + } + ) + + resetState() + return true + } + const onCancel = () => { + onCancelUpstream() + resetState() + } + + return ( + <Modal title="New Scenario" isOpen={isOpen} onSubmit={onSubmit} onCancel={onCancel}> + <Form onSubmit={onSubmit}> + <FormGroup label="Name" fieldId="name" isRequired> + <TextInput + id="name" + name="name" + type="text" + isDisabled={portfolio?.scenarioIds?.length === 0} + defaultValue={portfolio?.scenarioIds?.length === 0 ? 'Base scenario' : ''} + ref={nameInput} + /> + </FormGroup> + <FormSection title="Workload"> + <FormGroup label="Trace" fieldId="trace" isRequired> + <FormSelect id="trace" name="trace" value={trace} onChange={setTrace}> + {traces.map((trace) => ( + <FormSelectOption value={trace._id} key={trace._id} label={trace.name} /> + ))} + </FormSelect> + </FormGroup> + <FormGroup label="Load Sampling Fraction" fieldId="trace-load" isRequired> + <NumberInput + name="trace-load" + type="number" + min={0} + max={100} + value={traceLoad} + onMinus={() => setTraceLoad((load) => load - 1)} + onPlus={() => setTraceLoad((load) => load + 1)} + onChange={(e) => setTraceLoad(Number(e.target.value))} + unit="%" + /> + </FormGroup> + </FormSection> + <FormSection title="Topology"> + <FormGroup label="Topology" fieldId="topology" isRequired> + <FormSelect id="topology" name="topology" value={topology} onChange={setTopology}> + {topologies.map((topology) => ( + <FormSelectOption value={topology._id} key={topology._id} label={topology.name} /> + ))} + </FormSelect> + </FormGroup> + + <FormGroup label="Scheduler" fieldId="scheduler" isRequired> + <FormSelect id="scheduler" name="scheduler" value={scheduler} onChange={setScheduler}> + {schedulers.map((scheduler) => ( + <FormSelectOption value={scheduler.name} key={scheduler.name} label={scheduler.name} /> + ))} + </FormSelect> + </FormGroup> + </FormSection> + <FormSection title="Operational Phenomena"> + <Checkbox + label="Failures" + id="failures" + name="failures" + isChecked={failuresEnabled} + onChange={() => setFailuresEnabled((e) => !e)} + /> + <Checkbox + label="Performance Interference" + id="perf-interference" + name="perf-interference" + isChecked={opPhenEnabled} + onChange={() => setOpPhenEnabled((e) => !e)} + /> + </FormSection> + </Form> + </Modal> + ) +} + +NewScenarioModal.propTypes = { + portfolioId: PropTypes.string, + isOpen: PropTypes.bool.isRequired, + onSubmit: PropTypes.func.isRequired, + onCancel: PropTypes.func.isRequired, +} + +export default NewScenarioModal diff --git a/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioOverview.js b/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioOverview.js new file mode 100644 index 00000000..580b0a29 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioOverview.js @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2021 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. + */ + +import PropTypes from 'prop-types' +import { + Card, + CardActions, + CardBody, + CardHeader, + CardTitle, + Chip, + ChipGroup, + DescriptionList, + DescriptionListDescription, + DescriptionListGroup, + DescriptionListTerm, + Grid, + GridItem, + Skeleton, +} from '@patternfly/react-core' +import React from 'react' +import { usePortfolio } from '../../data/project' +import { METRIC_NAMES } from '../../util/available-metrics' +import NewScenario from './NewScenario' +import ScenarioTable from './ScenarioTable' + +function PortfolioOverview({ portfolioId }) { + const { data: portfolio } = usePortfolio(portfolioId) + + return ( + <Grid hasGutter> + <GridItem md={2}> + <Card> + <CardTitle>Details</CardTitle> + <CardBody> + <DescriptionList> + <DescriptionListGroup> + <DescriptionListTerm>Name</DescriptionListTerm> + <DescriptionListDescription> + {portfolio?.name ?? <Skeleton screenreaderText="Loading portfolio" />} + </DescriptionListDescription> + </DescriptionListGroup> + <DescriptionListGroup> + <DescriptionListTerm>Scenarios</DescriptionListTerm> + <DescriptionListDescription> + {portfolio?.scenarioIds.length ?? <Skeleton screenreaderText="Loading portfolio" />} + </DescriptionListDescription> + </DescriptionListGroup> + <DescriptionListGroup> + <DescriptionListTerm>Metrics</DescriptionListTerm> + <DescriptionListDescription> + {portfolio?.targets?.enabledMetrics ? ( + portfolio.targets.enabledMetrics.length > 0 ? ( + <ChipGroup> + {portfolio.targets.enabledMetrics.map((metric) => ( + <Chip isReadOnly key={metric}> + {METRIC_NAMES[metric]} + </Chip> + ))} + </ChipGroup> + ) : ( + 'No metrics enabled' + ) + ) : ( + <Skeleton screenreaderText="Loading portfolio" /> + )} + </DescriptionListDescription> + </DescriptionListGroup> + <DescriptionListGroup> + <DescriptionListTerm>Repeats per Scenario</DescriptionListTerm> + <DescriptionListDescription> + {portfolio?.targets?.repeatsPerScenario ?? ( + <Skeleton screenreaderText="Loading portfolio" /> + )} + </DescriptionListDescription> + </DescriptionListGroup> + </DescriptionList> + </CardBody> + </Card> + </GridItem> + <GridItem md={6}> + <Card> + <CardHeader> + <CardActions> + <NewScenario portfolioId={portfolioId} /> + </CardActions> + <CardTitle>Scenarios</CardTitle> + </CardHeader> + <CardBody> + <ScenarioTable portfolioId={portfolioId} /> + </CardBody> + </Card> + </GridItem> + </Grid> + ) +} + +PortfolioOverview.propTypes = { + portfolioId: PropTypes.string, +} + +export default PortfolioOverview diff --git a/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioResultInfo.js b/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioResultInfo.js new file mode 100644 index 00000000..dbfa928f --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioResultInfo.js @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2021 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. + */ + +import PropTypes from 'prop-types' +import { Tooltip } from '@patternfly/react-core' +import { OutlinedQuestionCircleIcon } from '@patternfly/react-icons' +import { METRIC_DESCRIPTIONS } from '../../util/available-metrics' + +function PortfolioResultInfo({ metric }) { + return ( + <Tooltip position="top" content={<div>{METRIC_DESCRIPTIONS[metric]}</div>}> + <OutlinedQuestionCircleIcon title="Metric information" /> + </Tooltip> + ) +} + +PortfolioResultInfo.propTypes = { + metric: PropTypes.string.isRequired, +} + +export default PortfolioResultInfo diff --git a/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioResults.js b/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioResults.js new file mode 100644 index 00000000..00023d9e --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioResults.js @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2021 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. + */ + +import React from 'react' +import PropTypes from 'prop-types' +import { Bar, CartesianGrid, ComposedChart, ErrorBar, ResponsiveContainer, Scatter, XAxis, YAxis } from 'recharts' +import { AVAILABLE_METRICS, METRIC_NAMES, METRIC_UNITS } from '../../util/available-metrics' +import { mean, std } from 'mathjs' +import approx from 'approximate-number' +import { + Bullseye, + Card, + CardActions, + CardBody, + CardHeader, + CardTitle, + EmptyState, + EmptyStateBody, + EmptyStateIcon, + Grid, + GridItem, + Spinner, + Title, +} from '@patternfly/react-core' +import { ErrorCircleOIcon, CubesIcon } from '@patternfly/react-icons' +import { usePortfolioScenarios } from '../../data/project' +import PortfolioResultInfo from './PortfolioResultInfo' +import NewScenario from './NewScenario' + +const PortfolioResults = ({ portfolioId }) => { + const { status, data: scenarios = [] } = usePortfolioScenarios(portfolioId) + + if (status === 'loading') { + return ( + <Bullseye> + <EmptyState> + <EmptyStateIcon variant="container" component={Spinner} /> + <Title size="lg" headingLevel="h4"> + Loading Results + </Title> + </EmptyState> + </Bullseye> + ) + } else if (status === 'error') { + return ( + <Bullseye> + <EmptyState> + <EmptyStateIcon variant="container" component={ErrorCircleOIcon} /> + <Title size="lg" headingLevel="h4"> + Unable to connect + </Title> + <EmptyStateBody> + There was an error retrieving data. Check your connection and try again. + </EmptyStateBody> + </EmptyState> + </Bullseye> + ) + } else if (scenarios.length === 0) { + return ( + <Bullseye> + <EmptyState> + <EmptyStateIcon variant="container" component={CubesIcon} /> + <Title size="lg" headingLevel="h4"> + No results + </Title> + <EmptyStateBody> + No results are currently available for this portfolio. Run a scenario to obtain simulation + results. + </EmptyStateBody> + <NewScenario portfolioId={portfolioId} /> + </EmptyState> + </Bullseye> + ) + } + + const dataPerMetric = {} + + AVAILABLE_METRICS.forEach((metric) => { + dataPerMetric[metric] = scenarios + .filter((scenario) => scenario.results) + .map((scenario) => ({ + name: scenario.name, + value: mean(scenario.results[metric]), + errorX: std(scenario.results[metric]), + })) + }) + + return ( + <Grid hasGutter> + {AVAILABLE_METRICS.map((metric) => ( + <GridItem xl={6} lg={12} key={metric}> + <Card> + <CardHeader> + <CardActions> + <PortfolioResultInfo metric={metric} /> + </CardActions> + <CardTitle>{METRIC_NAMES[metric]}</CardTitle> + </CardHeader> + <CardBody> + <ResponsiveContainer aspect={16 / 9} width="100%"> + <ComposedChart + data={dataPerMetric[metric]} + margin={{ left: 35, bottom: 15 }} + layout="vertical" + > + <CartesianGrid strokeDasharray="3 3" /> + <XAxis + tickFormatter={(tick) => approx(tick)} + label={{ value: METRIC_UNITS[metric], position: 'bottom', offset: 0 }} + type="number" + /> + <YAxis dataKey="name" type="category" /> + <Bar dataKey="value" fill="#3399FF" isAnimationActive={false} /> + <Scatter dataKey="value" opacity={0} isAnimationActive={false}> + <ErrorBar + dataKey="errorX" + width={10} + strokeWidth={3} + stroke="#FF6600" + direction="x" + /> + </Scatter> + </ComposedChart> + </ResponsiveContainer> + </CardBody> + </Card> + </GridItem> + ))} + </Grid> + ) +} + +PortfolioResults.propTypes = { + portfolioId: PropTypes.string, +} + +export default PortfolioResults diff --git a/opendc-web/opendc-web-ui/src/components/portfolios/ScenarioState.js b/opendc-web/opendc-web-ui/src/components/portfolios/ScenarioState.js new file mode 100644 index 00000000..66691580 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/portfolios/ScenarioState.js @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2021 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. + */ + +import PropTypes from 'prop-types' +import { ClockIcon, CheckCircleIcon, ErrorCircleOIcon } from '@patternfly/react-icons' + +function ScenarioState({ state }) { + switch (state) { + case 'CLAIMED': + case 'QUEUED': + return ( + <span> + <ClockIcon color="blue" /> Queued + </span> + ) + case 'RUNNING': + return ( + <span> + <ClockIcon color="green" /> Running + </span> + ) + case 'FINISHED': + return ( + <span> + <CheckCircleIcon color="green" /> Finished + </span> + ) + case 'FAILED': + return ( + <span> + <ErrorCircleOIcon color="red" /> Failed + </span> + ) + } + + return 'Unknown' +} + +ScenarioState.propTypes = { + state: PropTypes.oneOf(['QUEUED', 'CLAIMED', 'RUNNING', 'FINISHED', 'FAILED']), +} + +export default ScenarioState diff --git a/opendc-web/opendc-web-ui/src/components/portfolios/ScenarioTable.js b/opendc-web/opendc-web-ui/src/components/portfolios/ScenarioTable.js new file mode 100644 index 00000000..9966e3ba --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/portfolios/ScenarioTable.js @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2021 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. + */ + +import PropTypes from 'prop-types' +import Link from 'next/link' +import { Table, TableBody, TableHeader } from '@patternfly/react-table' +import React from 'react' +import TableEmptyState from '../util/TableEmptyState' +import ScenarioState from './ScenarioState' +import { usePortfolio, usePortfolioScenarios } from '../../data/project' +import { useProjectTopologies } from '../../data/topology' +import { useMutation } from 'react-query' + +const ScenarioTable = ({ portfolioId }) => { + const { data: portfolio } = usePortfolio(portfolioId) + const { status, data: scenarios = [] } = usePortfolioScenarios(portfolioId) + const { data: topologies } = useProjectTopologies(portfolio?.projectId, { + select: (topologies) => new Map(topologies.map((topology) => [topology._id, topology])), + }) + + const { mutate: deleteScenario } = useMutation('deleteScenario') + + const columns = ['Name', 'Topology', 'Trace', 'State'] + const rows = + scenarios.length > 0 + ? scenarios.map((scenario) => { + const topology = topologies?.get(scenario.topology.topologyId) + + return [ + scenario.name, + { + title: topology ? ( + <Link href={`/projects/${topology.projectId}/topologies/${topology._id}`}> + <a>{topology.name}</a> + </Link> + ) : ( + 'Unknown Topology' + ), + }, + scenario.trace.traceId, + { title: <ScenarioState state={scenario.simulation.state} /> }, + ] + }) + : [ + { + heightAuto: true, + cells: [ + { + props: { colSpan: 4 }, + title: ( + <TableEmptyState + status={status} + loadingTitle="Loading Scenarios" + emptyTitle="No scenarios" + emptyText="You have not created any scenario for this portfolio yet. Click the New Scenario button to create one." + /> + ), + }, + ], + }, + ] + + const actionResolver = (_, { rowIndex }) => [ + { + title: 'Delete Scenario', + onClick: (_, rowId) => deleteScenario(scenarios[rowId]._id), + isDisabled: rowIndex === 0, + }, + ] + + return ( + <Table + aria-label="Scenario List" + variant="compact" + cells={columns} + rows={rows} + actionResolver={scenarios.length > 0 ? actionResolver : undefined} + > + <TableHeader /> + <TableBody /> + </Table> + ) +} + +ScenarioTable.propTypes = { + portfolioId: PropTypes.string, +} + +export default ScenarioTable diff --git a/opendc-web/opendc-web-ui/src/components/projects/FilterButton.js b/opendc-web/opendc-web-ui/src/components/projects/FilterButton.js deleted file mode 100644 index 664f9b46..00000000 --- a/opendc-web/opendc-web-ui/src/components/projects/FilterButton.js +++ /dev/null @@ -1,24 +0,0 @@ -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/opendc-web/opendc-web-ui/src/components/projects/FilterPanel.js b/opendc-web/opendc-web-ui/src/components/projects/FilterPanel.js index 2b9795d0..285217e9 100644 --- a/opendc-web/opendc-web-ui/src/components/projects/FilterPanel.js +++ b/opendc-web/opendc-web-ui/src/components/projects/FilterPanel.js @@ -1,13 +1,26 @@ import React from 'react' -import FilterLink from '../../containers/projects/FilterLink' -import './FilterPanel.sass' - -const FilterPanel = () => ( - <div className="btn-group filter-panel mb-2"> - <FilterLink filter="SHOW_ALL">All Projects</FilterLink> - <FilterLink filter="SHOW_OWN">My Projects</FilterLink> - <FilterLink filter="SHOW_SHARED">Shared with me</FilterLink> - </div> +import PropTypes from 'prop-types' +import { ToggleGroup, ToggleGroupItem } from '@patternfly/react-core' +import { filterPanel } from './FilterPanel.module.scss' + +export const FILTERS = { SHOW_ALL: 'All Projects', SHOW_OWN: 'My Projects', SHOW_SHARED: 'Shared with me' } + +const FilterPanel = ({ onSelect, activeFilter = 'SHOW_ALL' }) => ( + <ToggleGroup className={`${filterPanel} mb-2`}> + {Object.keys(FILTERS).map((filter) => ( + <ToggleGroupItem + key={filter} + onChange={() => activeFilter === filter || onSelect(filter)} + isSelected={activeFilter === filter} + text={FILTERS[filter]} + /> + ))} + </ToggleGroup> ) +FilterPanel.propTypes = { + onSelect: PropTypes.func.isRequired, + activeFilter: PropTypes.string, +} + export default FilterPanel diff --git a/opendc-web/opendc-web-ui/src/components/projects/FilterPanel.module.scss b/opendc-web/opendc-web-ui/src/components/projects/FilterPanel.module.scss new file mode 100644 index 00000000..79cdf81a --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/projects/FilterPanel.module.scss @@ -0,0 +1,7 @@ +.filterPanel { + display: flex; + + button { + flex: 1 !important; + } +} diff --git a/opendc-web/opendc-web-ui/src/components/projects/FilterPanel.sass b/opendc-web/opendc-web-ui/src/components/projects/FilterPanel.sass deleted file mode 100644 index f71cf6c8..00000000 --- a/opendc-web/opendc-web-ui/src/components/projects/FilterPanel.sass +++ /dev/null @@ -1,5 +0,0 @@ -.filter-panel - display: flex - - button - flex: 1 !important diff --git a/opendc-web/opendc-web-ui/src/components/projects/NewPortfolio.js b/opendc-web/opendc-web-ui/src/components/projects/NewPortfolio.js new file mode 100644 index 00000000..87ea059d --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/projects/NewPortfolio.js @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2021 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. + */ + +import PropTypes from 'prop-types' +import { PlusIcon } from '@patternfly/react-icons' +import { Button } from '@patternfly/react-core' +import { useState } from 'react' +import { useMutation } from 'react-query' +import NewPortfolioModal from './NewPortfolioModal' + +function NewPortfolio({ projectId }) { + const [isVisible, setVisible] = useState(false) + const { mutate: addPortfolio } = useMutation('addPortfolio') + + const onSubmit = (name, targets) => { + addPortfolio({ projectId, name, targets }) + setVisible(false) + } + + return ( + <> + <Button icon={<PlusIcon />} isSmall onClick={() => setVisible(true)}> + New Portfolio + </Button> + <NewPortfolioModal isOpen={isVisible} onSubmit={onSubmit} onCancel={() => setVisible(false)} /> + </> + ) +} + +NewPortfolio.propTypes = { + projectId: PropTypes.string, +} + +export default NewPortfolio diff --git a/opendc-web/opendc-web-ui/src/components/projects/NewPortfolioModal.js b/opendc-web/opendc-web-ui/src/components/projects/NewPortfolioModal.js new file mode 100644 index 00000000..4276d7d4 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/projects/NewPortfolioModal.js @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2021 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. + */ + +import PropTypes from 'prop-types' +import React, { useRef, useState } from 'react' +import { + Form, + FormGroup, + FormSection, + NumberInput, + Select, + SelectGroup, + SelectOption, + SelectVariant, + TextInput, +} from '@patternfly/react-core' +import Modal from '../util/modals/Modal' +import { METRIC_GROUPS, METRIC_NAMES } from '../../util/available-metrics' + +const NewPortfolioModal = ({ isOpen, onSubmit: onSubmitUpstream, onCancel: onUpstreamCancel }) => { + const nameInput = useRef(null) + const [repeats, setRepeats] = useState(1) + const [isSelectOpen, setSelectOpen] = useState(false) + const [selectedMetrics, setSelectedMetrics] = useState([]) + + const [isSubmitted, setSubmitted] = useState(false) + const [errors, setErrors] = useState({}) + + const clearState = () => { + setSubmitted(false) + setErrors({}) + nameInput.current.value = '' + setRepeats(1) + setSelectOpen(false) + setSelectedMetrics([]) + } + + const onSubmit = (event) => { + setSubmitted(true) + + if (event) { + event.preventDefault() + } + + const name = nameInput.current.value + + if (!name) { + setErrors({ name: true }) + return false + } else { + onSubmitUpstream(name, { enabledMetrics: selectedMetrics, repeatsPerScenario: repeats }) + } + + clearState() + return false + } + const onCancel = () => { + onUpstreamCancel() + clearState() + } + + const onSelect = (event, selection) => { + if (selectedMetrics.includes(selection)) { + setSelectedMetrics((metrics) => metrics.filter((item) => item !== selection)) + } else { + setSelectedMetrics((metrics) => [...metrics, selection]) + } + } + + return ( + <Modal title="New Portfolio" isOpen={isOpen} onSubmit={onSubmit} onCancel={onCancel}> + <Form onSubmit={onSubmit}> + <FormSection> + <FormGroup + label="Name" + fieldId="name" + isRequired + validated={isSubmitted && errors.name ? 'error' : 'default'} + helperTextInvalid="This field cannot be empty" + > + <TextInput + name="name" + id="name" + type="text" + isRequired + ref={nameInput} + placeholder="My Portfolio" + /> + </FormGroup> + </FormSection> + <FormSection title="Targets" titleElement="h4"> + <FormGroup label="Metrics" fieldId="metrics"> + <Select + variant={SelectVariant.typeaheadMulti} + typeAheadAriaLabel="Select a metric" + onToggle={() => setSelectOpen(!isSelectOpen)} + onSelect={onSelect} + onClear={() => setSelectedMetrics([])} + selections={selectedMetrics} + isOpen={isSelectOpen} + placeholderText="Select a metric" + menuAppendTo="parent" + maxHeight="300px" + chipGroupProps={{ numChips: 1 }} + isGrouped + > + {Object.entries(METRIC_GROUPS).map(([group, metrics]) => ( + <SelectGroup label={group} key={group}> + {metrics.map((metric) => ( + <SelectOption key={metric} value={metric}> + {METRIC_NAMES[metric]} + </SelectOption> + ))} + </SelectGroup> + ))} + </Select> + </FormGroup> + <FormGroup label="Repeats per Scenario" fieldId="repeats"> + <NumberInput + id="repeats" + inputName="repeats" + type="number" + value={repeats} + onChange={(e) => setRepeats(Number(e.target.value))} + onPlus={() => setRepeats((r) => r + 1)} + onMinus={() => setRepeats((r) => r - 1)} + min={1} + /> + </FormGroup> + </FormSection> + </Form> + </Modal> + ) +} + +NewPortfolioModal.propTypes = { + isOpen: PropTypes.bool.isRequired, + onSubmit: PropTypes.func.isRequired, + onCancel: PropTypes.func.isRequired, +} + +export default NewPortfolioModal diff --git a/opendc-web/opendc-web-ui/src/components/projects/NewProject.js b/opendc-web/opendc-web-ui/src/components/projects/NewProject.js new file mode 100644 index 00000000..984264dc --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/projects/NewProject.js @@ -0,0 +1,39 @@ +import React, { useState } from 'react' +import { Button } from '@patternfly/react-core' +import { useMutation } from 'react-query' +import { PlusIcon } from '@patternfly/react-icons' +import { buttonContainer } from './NewProject.module.scss' +import TextInputModal from '../util/modals/TextInputModal' + +/** + * A container for creating a new project. + */ +const NewProject = () => { + const [isVisible, setVisible] = useState(false) + const { mutate: addProject } = useMutation('addProject') + + const onSubmit = (name) => { + if (name) { + addProject({ name }) + } + setVisible(false) + } + + return ( + <> + <div className={buttonContainer}> + <Button + icon={<PlusIcon />} + color="primary" + className="pf-u-float-right" + onClick={() => setVisible(true)} + > + New Project + </Button> + </div> + <TextInputModal title="New Project" label="Project name" isOpen={isVisible} callback={onSubmit} /> + </> + ) +} + +export default NewProject diff --git a/opendc-web/opendc-web-ui/src/components/projects/NewProject.module.scss b/opendc-web/opendc-web-ui/src/components/projects/NewProject.module.scss new file mode 100644 index 00000000..5a0e74fc --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/projects/NewProject.module.scss @@ -0,0 +1,26 @@ +/*! + * Copyright (c) 2021 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. + */ + +.buttonContainer { + flex: 0 1 auto; + padding: 20px 0; +} diff --git a/opendc-web/opendc-web-ui/src/components/projects/NewProjectButtonComponent.js b/opendc-web/opendc-web-ui/src/components/projects/NewProjectButtonComponent.js deleted file mode 100644 index 312671c6..00000000 --- a/opendc-web/opendc-web-ui/src/components/projects/NewProjectButtonComponent.js +++ /dev/null @@ -1,17 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' - -const NewProjectButtonComponent = ({ onClick }) => ( - <div className="bottom-btn-container"> - <div className="btn btn-primary float-right" onClick={onClick}> - <span className="fa fa-plus mr-2" /> - New Project - </div> - </div> -) - -NewProjectButtonComponent.propTypes = { - onClick: PropTypes.func.isRequired, -} - -export default NewProjectButtonComponent diff --git a/opendc-web/opendc-web-ui/src/components/projects/NewTopology.js b/opendc-web/opendc-web-ui/src/components/projects/NewTopology.js new file mode 100644 index 00000000..bf59e020 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/projects/NewTopology.js @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2021 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. + */ + +import PropTypes from 'prop-types' +import { PlusIcon } from '@patternfly/react-icons' +import { Button } from '@patternfly/react-core' +import { useState } from 'react' +import { useDispatch } from 'react-redux' +import { addTopology } from '../../redux/actions/topologies' +import NewTopologyModal from './NewTopologyModal' + +function NewTopology({ projectId }) { + const [isVisible, setVisible] = useState(false) + const dispatch = useDispatch() + + const onSubmit = (name, duplicateId) => { + dispatch(addTopology(projectId, name, duplicateId)) + setVisible(false) + } + return ( + <> + <Button icon={<PlusIcon />} isSmall onClick={() => setVisible(true)}> + New Topology + </Button> + <NewTopologyModal + projectId={projectId} + isOpen={isVisible} + onSubmit={onSubmit} + onCancel={() => setVisible(false)} + /> + </> + ) +} + +NewTopology.propTypes = { + projectId: PropTypes.string, +} + +export default NewTopology diff --git a/opendc-web/opendc-web-ui/src/components/projects/NewTopologyModal.js b/opendc-web/opendc-web-ui/src/components/projects/NewTopologyModal.js new file mode 100644 index 00000000..a495f73e --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/projects/NewTopologyModal.js @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2021 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. + */ + +import PropTypes from 'prop-types' +import React, { useRef, useState } from 'react' +import { Form, FormGroup, FormSelect, FormSelectOption, TextInput } from '@patternfly/react-core' +import { useProjectTopologies } from '../../data/topology' +import Modal from '../util/modals/Modal' + +const NewTopologyModal = ({ projectId, isOpen, onSubmit: onSubmitUpstream, onCancel: onCancelUpstream }) => { + const nameInput = useRef(null) + const [isSubmitted, setSubmitted] = useState(false) + const [originTopology, setOriginTopology] = useState(-1) + const [errors, setErrors] = useState({}) + + const { data: topologies = [] } = useProjectTopologies(projectId) + + const clearState = () => { + nameInput.current.value = '' + setSubmitted(false) + setOriginTopology(-1) + setErrors({}) + } + + const onSubmit = (event) => { + setSubmitted(true) + + if (event) { + event.preventDefault() + } + + const name = nameInput.current.value + + if (!name) { + setErrors({ name: true }) + return false + } else if (originTopology === -1) { + onSubmitUpstream(name) + } else { + onSubmitUpstream(name, originTopology) + } + + clearState() + return true + } + + const onCancel = () => { + onCancelUpstream() + clearState() + } + + return ( + <Modal title="New Topology" isOpen={isOpen} onSubmit={onSubmit} onCancel={onCancel}> + <Form onSubmit={onSubmit}> + <FormGroup + label="Name" + fieldId="name" + isRequired + validated={isSubmitted && errors.name ? 'error' : 'default'} + helperTextInvalid="This field cannot be empty" + > + <TextInput id="name" name="name" type="text" isRequired ref={nameInput} /> + </FormGroup> + <FormGroup label="Topology to duplicate" fieldId="origin" isRequired> + <FormSelect id="origin" name="origin" value={originTopology} onChange={setOriginTopology}> + <FormSelectOption value={-1} key={-1} label="None - start from scratch" /> + {topologies.map((topology) => ( + <FormSelectOption value={topology._id} key={topology._id} label={topology.name} /> + ))} + </FormSelect> + </FormGroup> + </Form> + </Modal> + ) +} + +NewTopologyModal.propTypes = { + projectId: PropTypes.string, + isOpen: PropTypes.bool.isRequired, + onSubmit: PropTypes.func.isRequired, + onCancel: PropTypes.func.isRequired, +} + +export default NewTopologyModal diff --git a/opendc-web/opendc-web-ui/src/components/projects/PortfolioTable.js b/opendc-web/opendc-web-ui/src/components/projects/PortfolioTable.js new file mode 100644 index 00000000..45e399ed --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/projects/PortfolioTable.js @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2021 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. + */ + +import PropTypes from 'prop-types' +import Link from 'next/link' +import { Table, TableBody, TableHeader } from '@patternfly/react-table' +import React from 'react' +import TableEmptyState from '../util/TableEmptyState' +import { useProjectPortfolios } from '../../data/project' +import { useMutation } from 'react-query' + +const PortfolioTable = ({ projectId }) => { + const { status, data: portfolios = [] } = useProjectPortfolios(projectId) + const { mutate: deletePortfolio } = useMutation('deletePortfolio') + + const columns = ['Name', 'Scenarios', 'Metrics', 'Repeats'] + const rows = + portfolios.length > 0 + ? portfolios.map((portfolio) => [ + { + title: ( + <Link href={`/projects/${portfolio.projectId}/portfolios/${portfolio._id}`}> + {portfolio.name} + </Link> + ), + }, + + portfolio.scenarioIds.length === 1 ? '1 scenario' : `${portfolio.scenarioIds.length} scenarios`, + + portfolio.targets.enabledMetrics.length === 1 + ? '1 metric' + : `${portfolio.targets.enabledMetrics.length} metrics`, + portfolio.targets.repeatsPerScenario === 1 + ? '1 repeat' + : `${portfolio.targets.repeatsPerScenario} repeats`, + ]) + : [ + { + heightAuto: true, + cells: [ + { + props: { colSpan: 4 }, + title: ( + <TableEmptyState + status={status} + loadingTitle="Loading portfolios" + emptyTitle="No portfolios" + emptyText="You have not created any portfolio for this project yet. Click the New Portfolio button to create one." + /> + ), + }, + ], + }, + ] + + const actions = + portfolios.length > 0 + ? [ + { + title: 'Delete Portfolio', + onClick: (_, rowId) => deletePortfolio(portfolios[rowId]._id), + }, + ] + : [] + + return ( + <Table aria-label="Portfolio List" variant="compact" cells={columns} rows={rows} actions={actions}> + <TableHeader /> + <TableBody /> + </Table> + ) +} + +PortfolioTable.propTypes = { + projectId: PropTypes.string, +} + +export default PortfolioTable diff --git a/opendc-web/opendc-web-ui/src/components/projects/ProjectActionButtons.js b/opendc-web/opendc-web-ui/src/components/projects/ProjectActionButtons.js deleted file mode 100644 index 1c76cc7f..00000000 --- a/opendc-web/opendc-web-ui/src/components/projects/ProjectActionButtons.js +++ /dev/null @@ -1,29 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import { Link } from 'react-router-dom' - -const ProjectActionButtons = ({ projectId, onViewUsers, onDelete }) => ( - <td className="text-right"> - <Link to={'/projects/' + projectId} className="btn btn-outline-primary btn-sm mr-2" title="Open this project"> - <span className="fa fa-play" /> - </Link> - <div - className="btn btn-outline-success btn-sm disabled mr-2" - title="View and edit collaborators (not supported currently)" - onClick={() => onViewUsers(projectId)} - > - <span className="fa fa-users" /> - </div> - <div className="btn btn-outline-danger btn-sm" title="Delete this project" onClick={() => onDelete(projectId)}> - <span className="fa fa-trash" /> - </div> - </td> -) - -ProjectActionButtons.propTypes = { - projectId: PropTypes.string.isRequired, - onViewUsers: PropTypes.func, - onDelete: PropTypes.func, -} - -export default ProjectActionButtons diff --git a/opendc-web/opendc-web-ui/src/components/projects/ProjectAuthList.js b/opendc-web/opendc-web-ui/src/components/projects/ProjectAuthList.js deleted file mode 100644 index 8eb4f93b..00000000 --- a/opendc-web/opendc-web-ui/src/components/projects/ProjectAuthList.js +++ /dev/null @@ -1,39 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import Shapes from '../../shapes/index' -import ProjectAuthRow from './ProjectAuthRow' - -const ProjectAuthList = ({ 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 projects here yet...</strong> Add some with the 'New Project' button! - </div> - ) : ( - <table className="table table-striped"> - <thead> - <tr> - <th>Project name</th> - <th>Last edited</th> - <th>Access rights</th> - <th /> - </tr> - </thead> - <tbody> - {authorizations.map((authorization) => ( - <ProjectAuthRow projectAuth={authorization} key={authorization.project._id} /> - ))} - </tbody> - </table> - )} - </div> - ) -} - -ProjectAuthList.propTypes = { - authorizations: PropTypes.arrayOf(Shapes.Authorization).isRequired, -} - -export default ProjectAuthList diff --git a/opendc-web/opendc-web-ui/src/components/projects/ProjectAuthRow.js b/opendc-web/opendc-web-ui/src/components/projects/ProjectAuthRow.js deleted file mode 100644 index 3f904061..00000000 --- a/opendc-web/opendc-web-ui/src/components/projects/ProjectAuthRow.js +++ /dev/null @@ -1,24 +0,0 @@ -import classNames from 'classnames' -import React from 'react' -import ProjectActions from '../../containers/projects/ProjectActions' -import Shapes from '../../shapes/index' -import { AUTH_DESCRIPTION_MAP, AUTH_ICON_MAP } from '../../util/authorizations' -import { parseAndFormatDateTime } from '../../util/date-time' - -const ProjectAuthRow = ({ projectAuth }) => ( - <tr> - <td className="pt-3">{projectAuth.project.name}</td> - <td className="pt-3">{parseAndFormatDateTime(projectAuth.project.datetimeLastEdited)}</td> - <td className="pt-3"> - <span className={classNames('fa', 'fa-' + AUTH_ICON_MAP[projectAuth.authorizationLevel], 'mr-2')} /> - {AUTH_DESCRIPTION_MAP[projectAuth.authorizationLevel]} - </td> - <ProjectActions projectId={projectAuth.project._id} /> - </tr> -) - -ProjectAuthRow.propTypes = { - projectAuth: Shapes.Authorization.isRequired, -} - -export default ProjectAuthRow diff --git a/opendc-web/opendc-web-ui/src/components/projects/ProjectOverview.js b/opendc-web/opendc-web-ui/src/components/projects/ProjectOverview.js new file mode 100644 index 00000000..65b8f5a0 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/projects/ProjectOverview.js @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2021 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. + */ + +import PropTypes from 'prop-types' +import { + Card, + CardActions, + CardBody, + CardHeader, + CardTitle, + DescriptionList, + DescriptionListDescription, + DescriptionListGroup, + DescriptionListTerm, + Grid, + GridItem, + Skeleton, +} from '@patternfly/react-core' +import NewTopology from './NewTopology' +import TopologyTable from './TopologyTable' +import NewPortfolio from './NewPortfolio' +import PortfolioTable from './PortfolioTable' +import { useProject } from '../../data/project' + +function ProjectOverview({ projectId }) { + const { data: project } = useProject(projectId) + + return ( + <Grid hasGutter> + <GridItem md={2}> + <Card> + <CardTitle>Details</CardTitle> + <CardBody> + <DescriptionList> + <DescriptionListGroup> + <DescriptionListTerm>Name</DescriptionListTerm> + <DescriptionListDescription> + {project?.name ?? <Skeleton screenreaderText="Loading project" />} + </DescriptionListDescription> + </DescriptionListGroup> + </DescriptionList> + </CardBody> + </Card> + </GridItem> + <GridItem md={5}> + <Card> + <CardHeader> + <CardActions> + <NewTopology projectId={projectId} /> + </CardActions> + <CardTitle>Topologies</CardTitle> + </CardHeader> + <CardBody> + <TopologyTable projectId={projectId} /> + </CardBody> + </Card> + </GridItem> + <GridItem md={5}> + <Card> + <CardHeader> + <CardActions> + <NewPortfolio projectId={projectId} /> + </CardActions> + <CardTitle>Portfolios</CardTitle> + </CardHeader> + <CardBody> + <PortfolioTable projectId={projectId} /> + </CardBody> + </Card> + </GridItem> + </Grid> + ) +} + +ProjectOverview.propTypes = { + projectId: PropTypes.string, +} + +export default ProjectOverview diff --git a/opendc-web/opendc-web-ui/src/components/projects/ProjectTable.js b/opendc-web/opendc-web-ui/src/components/projects/ProjectTable.js new file mode 100644 index 00000000..a7290259 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/projects/ProjectTable.js @@ -0,0 +1,76 @@ +import PropTypes from 'prop-types' +import React from 'react' +import Link from 'next/link' +import { Project, Status } from '../../shapes' +import { Table, TableBody, TableHeader } from '@patternfly/react-table' +import { parseAndFormatDateTime } from '../../util/date-time' +import { AUTH_DESCRIPTION_MAP, AUTH_ICON_MAP } from '../../util/authorizations' +import { useAuth } from '../../auth' +import TableEmptyState from '../util/TableEmptyState' + +const ProjectTable = ({ status, projects, onDelete, isFiltering }) => { + const { user } = useAuth() + const columns = ['Project name', 'Last edited', 'Access Rights'] + const rows = + projects.length > 0 + ? projects.map((project) => { + const { level } = project.authorizations.find((auth) => auth.userId === user.sub) + const Icon = AUTH_ICON_MAP[level] + return [ + { + title: <Link href={`/projects/${project._id}`}>{project.name}</Link>, + }, + parseAndFormatDateTime(project.datetimeLastEdited), + { + title: ( + <> + <Icon className="pf-u-mr-md" key="auth" /> {AUTH_DESCRIPTION_MAP[level]} + </> + ), + }, + ] + }) + : [ + { + heightAuto: true, + cells: [ + { + props: { colSpan: 3 }, + title: ( + <TableEmptyState + status={status} + loadingTitle="Loading Projects" + isFiltering={isFiltering} + /> + ), + }, + ], + }, + ] + + const actions = + projects.length > 0 + ? [ + { + title: 'Delete Project', + onClick: (_, rowId) => onDelete(projects[rowId]), + }, + ] + : [] + + return ( + <Table aria-label="Project List" variant="compact" cells={columns} rows={rows} actions={actions}> + <TableHeader /> + <TableBody /> + </Table> + ) +} + +ProjectTable.propTypes = { + status: Status.isRequired, + isFiltering: PropTypes.bool, + projects: PropTypes.arrayOf(Project).isRequired, + onDelete: PropTypes.func, +} + +export default ProjectTable diff --git a/opendc-web/opendc-web-ui/src/components/projects/TopologyTable.js b/opendc-web/opendc-web-ui/src/components/projects/TopologyTable.js new file mode 100644 index 00000000..80099ece --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/projects/TopologyTable.js @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2021 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. + */ + +import PropTypes from 'prop-types' +import Link from 'next/link' +import { Table, TableBody, TableHeader } from '@patternfly/react-table' +import React from 'react' +import TableEmptyState from '../util/TableEmptyState' +import { parseAndFormatDateTime } from '../../util/date-time' +import { useMutation } from 'react-query' +import { useProjectTopologies } from '../../data/topology' + +const TopologyTable = ({ projectId }) => { + const { status, data: topologies = [] } = useProjectTopologies(projectId) + const { mutate: deleteTopology } = useMutation('deleteTopology') + + const columns = ['Name', 'Rooms', 'Last Edited'] + const rows = + topologies.length > 0 + ? topologies.map((topology) => [ + { + title: ( + <Link href={`/projects/${topology.projectId}/topologies/${topology._id}`}> + {topology.name} + </Link> + ), + }, + topology.rooms.length === 1 ? '1 room' : `${topology.rooms.length} rooms`, + parseAndFormatDateTime(topology.datetimeLastEdited), + ]) + : [ + { + heightAuto: true, + cells: [ + { + props: { colSpan: 3 }, + title: ( + <TableEmptyState + status={status} + loadingTitle="Loading topologies" + emptyTitle="No topologies" + emptyText="You have not created any topology for this project yet. Click the New Topology button to create one." + /> + ), + }, + ], + }, + ] + + const actionResolver = (_, { rowIndex }) => [ + { + title: 'Delete Topology', + onClick: (_, rowId) => deleteTopology(topologies[rowId]._id), + isDisabled: rowIndex === 0, + }, + ] + + return ( + <Table + aria-label="Topology List" + variant="compact" + cells={columns} + rows={rows} + actionResolver={topologies.length > 0 ? actionResolver : () => []} + > + <TableHeader /> + <TableBody /> + </Table> + ) +} + +TopologyTable.propTypes = { + projectId: PropTypes.string, +} + +export default TopologyTable diff --git a/opendc-web/opendc-web-ui/src/components/topologies/RoomTable.js b/opendc-web/opendc-web-ui/src/components/topologies/RoomTable.js new file mode 100644 index 00000000..9bf369e9 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/RoomTable.js @@ -0,0 +1,69 @@ +import { Button } from '@patternfly/react-core' +import PropTypes from 'prop-types' +import React from 'react' +import { useDispatch } from 'react-redux' +import { useTopology } from '../../data/topology' +import { Table, TableBody, TableHeader } from '@patternfly/react-table' +import { deleteRoom } from '../../redux/actions/topology/room' +import TableEmptyState from '../util/TableEmptyState' + +function RoomTable({ topologyId, onSelect }) { + const dispatch = useDispatch() + const { status, data: topology } = useTopology(topologyId) + + const onDelete = (room) => dispatch(deleteRoom(room._id)) + + const columns = ['Name', 'Tiles', 'Racks'] + const rows = + topology?.rooms.length > 0 + ? topology.rooms.map((room) => { + const tileCount = room.tiles.length + const rackCount = room.tiles.filter((tile) => tile.rack).length + return [ + { + title: ( + <Button variant="link" isInline onClick={() => onSelect(room)}> + {room.name} + </Button> + ), + }, + tileCount === 1 ? '1 tile' : `${tileCount} tiles`, + rackCount === 1 ? '1 rack' : `${rackCount} racks`, + ] + }) + : [ + { + heightAuto: true, + cells: [ + { + props: { colSpan: 3 }, + title: <TableEmptyState status={status} loadingTitle="Loading Rooms" />, + }, + ], + }, + ] + + const actions = + topology?.rooms.length > 0 + ? [ + { + title: 'Delete room', + onClick: (_, rowId) => onDelete(topology.rooms[rowId]), + }, + ] + : [] + + return ( + <Table aria-label="Room list" variant="compact" cells={columns} rows={rows} actions={actions}> + <TableHeader /> + <TableBody /> + </Table> + ) +} + +RoomTable.propTypes = { + topologyId: PropTypes.string, + onSelect: PropTypes.func, +} + +export default RoomTable diff --git a/opendc-web/opendc-web-ui/src/components/topologies/TopologyMap.js b/opendc-web/opendc-web-ui/src/components/topologies/TopologyMap.js new file mode 100644 index 00000000..2f27749f --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/TopologyMap.js @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2021 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. + */ + +import React, { useState } from 'react' +import { + Bullseye, + Drawer, + DrawerContent, + DrawerContentBody, + EmptyState, + EmptyStateIcon, + Spinner, + Title, +} from '@patternfly/react-core' +import { configure, HotKeys } from 'react-hotkeys' +import { KeymapConfiguration } from '../../hotkeys' +import MapStage from './map/MapStage' +import Collapse from './map/controls/Collapse' +import { useSelector } from 'react-redux' +import TopologySidebar from './sidebar/TopologySidebar' + +function TopologyMap() { + const topologyIsLoading = useSelector((state) => !state.topology.root) + const interactionLevel = useSelector((state) => state.interactionLevel) + + const [isExpanded, setExpanded] = useState(true) + const panelContent = <TopologySidebar interactionLevel={interactionLevel} onClose={() => setExpanded(false)} /> + + // Make sure that holding down a key will generate repeated events + configure({ + ignoreRepeatedEventsWhenKeyHeldDown: false, + }) + + return topologyIsLoading ? ( + <Bullseye> + <EmptyState> + <EmptyStateIcon variant="container" component={Spinner} /> + <Title size="lg" headingLevel="h4"> + Loading Topology + </Title> + </EmptyState> + </Bullseye> + ) : ( + <HotKeys keyMap={KeymapConfiguration} allowChanges={true} className="full-height"> + <Drawer isExpanded={isExpanded}> + <DrawerContent panelContent={panelContent}> + <DrawerContentBody> + <MapStage /> + <Collapse onClick={() => setExpanded(true)} /> + </DrawerContentBody> + </DrawerContent> + </Drawer> + </HotKeys> + ) +} + +export default TopologyMap diff --git a/opendc-web/opendc-web-ui/src/components/topologies/TopologyOverview.js b/opendc-web/opendc-web-ui/src/components/topologies/TopologyOverview.js new file mode 100644 index 00000000..213a4868 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/TopologyOverview.js @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2021 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. + */ + +import PropTypes from 'prop-types' +import { + Card, + CardBody, + CardTitle, + DescriptionList, + DescriptionListDescription, + DescriptionListGroup, + DescriptionListTerm, + Grid, + GridItem, + Skeleton, +} from '@patternfly/react-core' +import React from 'react' +import { useTopology } from '../../data/topology' +import { parseAndFormatDateTime } from '../../util/date-time' +import RoomTable from './RoomTable' + +function TopologyOverview({ topologyId, onSelect }) { + const { data: topology } = useTopology(topologyId) + return ( + <Grid hasGutter> + <GridItem md={2}> + <Card> + <CardTitle>Details</CardTitle> + <CardBody> + <DescriptionList> + <DescriptionListGroup> + <DescriptionListTerm>Name</DescriptionListTerm> + <DescriptionListDescription> + {topology?.name ?? <Skeleton screenreaderText="Loading topology" />} + </DescriptionListDescription> + </DescriptionListGroup> + <DescriptionListGroup> + <DescriptionListTerm>Last edited</DescriptionListTerm> + <DescriptionListDescription> + {topology ? ( + parseAndFormatDateTime(topology.datetimeLastEdited) + ) : ( + <Skeleton screenreaderText="Loading topology" /> + )} + </DescriptionListDescription> + </DescriptionListGroup> + </DescriptionList> + </CardBody> + </Card> + </GridItem> + <GridItem md={5}> + <Card> + <CardTitle>Rooms</CardTitle> + <CardBody> + <RoomTable topologyId={topologyId} onSelect={(room) => onSelect('room', room)} /> + </CardBody> + </Card> + </GridItem> + </Grid> + ) +} + +TopologyOverview.propTypes = { + topologyId: PropTypes.string, + onSelect: PropTypes.func, +} + +export default TopologyOverview diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/GrayContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/GrayContainer.js new file mode 100644 index 00000000..ccf637e5 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/GrayContainer.js @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2021 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. + */ + +import React from 'react' +import { useDispatch } from 'react-redux' +import { goDownOneInteractionLevel } from '../../../redux/actions/interaction-level' +import GrayLayer from './elements/GrayLayer' + +function GrayContainer() { + const dispatch = useDispatch() + const onClick = () => dispatch(goDownOneInteractionLevel()) + return <GrayLayer onClick={onClick} /> +} + +export default GrayContainer diff --git a/opendc-web/opendc-web-ui/src/components/app/map/MapConstants.js b/opendc-web/opendc-web-ui/src/components/topologies/map/MapConstants.js index d6ea1f84..4c3b2757 100644 --- a/opendc-web/opendc-web-ui/src/components/app/map/MapConstants.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/MapConstants.js @@ -8,13 +8,10 @@ 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 WALL_WIDTH_IN_PIXELS = TILE_SIZE_IN_PIXELS / 16 +export const OBJECT_BORDER_WIDTH_IN_PIXELS = TILE_SIZE_IN_PIXELS / 16 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 diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/MapStage.js b/opendc-web/opendc-web-ui/src/components/topologies/map/MapStage.js new file mode 100644 index 00000000..d8735cf1 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/MapStage.js @@ -0,0 +1,83 @@ +import React, { useRef, useState, useContext } from 'react' +import { HotKeys } from 'react-hotkeys' +import { Stage } from 'react-konva' +import { MAP_MAX_SCALE, MAP_MIN_SCALE, MAP_MOVE_PIXELS_PER_EVENT, MAP_SCALE_PER_EVENT } from './MapConstants' +import { ReactReduxContext } from 'react-redux' +import useResizeObserver from 'use-resize-observer' +import { mapContainer } from './MapStage.module.scss' +import MapLayer from './layers/MapLayer' +import RoomHoverLayer from './layers/RoomHoverLayer' +import ObjectHoverLayer from './layers/ObjectHoverLayer' +import ScaleIndicator from './controls/ScaleIndicator' +import Toolbar from './controls/Toolbar' + +function MapStage() { + const reduxContext = useContext(ReactReduxContext) + const { ref, width = 100, height = 100 } = useResizeObserver() + const stageRef = useRef(null) + const [[x, y], setPos] = useState([0, 0]) + const [scale, setScale] = useState(1) + + const clampScale = (target) => Math.min(Math.max(target, MAP_MIN_SCALE), MAP_MAX_SCALE) + const moveWithDelta = (deltaX, deltaY) => setPos(([x, y]) => [x + deltaX, y + deltaY]) + + const onZoom = (e) => { + e.evt.preventDefault() + + const stage = stageRef.current.getStage() + const oldScale = scale + + const pointer = stage.getPointerPosition() + const mousePointTo = { + x: (pointer.x - x) / oldScale, + y: (pointer.y - y) / oldScale, + } + + const newScale = clampScale(e.evt.deltaY > 0 ? oldScale * MAP_SCALE_PER_EVENT : oldScale / MAP_SCALE_PER_EVENT) + + setScale(newScale) + setPos([pointer.x - mousePointTo.x * newScale, pointer.y - mousePointTo.y * newScale]) + } + const onZoomButton = (zoomIn) => + setScale((scale) => clampScale(zoomIn ? scale * MAP_SCALE_PER_EVENT : scale / MAP_SCALE_PER_EVENT)) + const onDragEnd = (e) => setPos([e.target.x(), e.target.y()]) + const onExport = () => { + const download = document.createElement('a') + download.href = stageRef.current.getStage().toDataURL() + download.download = 'opendc-canvas-export-' + Date.now() + '.png' + download.click() + } + + const handlers = { + MOVE_LEFT: () => moveWithDelta(MAP_MOVE_PIXELS_PER_EVENT, 0), + MOVE_RIGHT: () => moveWithDelta(-MAP_MOVE_PIXELS_PER_EVENT, 0), + MOVE_UP: () => moveWithDelta(0, MAP_MOVE_PIXELS_PER_EVENT), + MOVE_DOWN: () => moveWithDelta(0, -MAP_MOVE_PIXELS_PER_EVENT), + } + + return ( + <HotKeys handlers={handlers} allowChanges={true} innerRef={ref} className={mapContainer}> + <Stage + ref={stageRef} + onWheel={onZoom} + onDragEnd={onDragEnd} + draggable + width={width} + height={height} + scale={{ x: scale, y: scale }} + x={x} + y={y} + > + <ReactReduxContext.Provider value={reduxContext}> + <MapLayer /> + <RoomHoverLayer /> + <ObjectHoverLayer /> + </ReactReduxContext.Provider> + </Stage> + <ScaleIndicator scale={scale} /> + <Toolbar onZoom={onZoomButton} onExport={onExport} /> + </HotKeys> + ) +} + +export default MapStage diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/MapStage.module.scss b/opendc-web/opendc-web-ui/src/components/topologies/map/MapStage.module.scss new file mode 100644 index 00000000..d879b4c8 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/MapStage.module.scss @@ -0,0 +1,31 @@ +/*! + * Copyright (c) 2021 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. + */ + +.mapContainer { + background-color: var(--pf-global--Color--light-200); + position: relative; + display: flex; + justify-content: center; + align-items: center; + width: 100%; + height: 100%; +} diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/RackContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/RackContainer.js new file mode 100644 index 00000000..14449a91 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/RackContainer.js @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2021 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. + */ + +import React from 'react' +import { useSelector } from 'react-redux' +import { Tile } from '../../../shapes' +import RackGroup from './groups/RackGroup' + +function RackContainer({ tile }) { + const interactionLevel = useSelector((state) => state.interactionLevel) + return <RackGroup interactionLevel={interactionLevel} tile={tile} /> +} + +RackContainer.propTypes = { + tile: Tile, +} + +export default RackContainer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/RackEnergyFillContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/RackEnergyFillContainer.js new file mode 100644 index 00000000..be1f3e45 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/RackEnergyFillContainer.js @@ -0,0 +1,34 @@ +import React from 'react' +import PropTypes from 'prop-types' +import { useSelector } from 'react-redux' +import RackFillBar from './elements/RackFillBar' + +function RackSpaceFillContainer({ tileId, ...props }) { + const fillFraction = useSelector((state) => { + let energyConsumptionTotal = 0 + const rack = state.topology.racks[state.topology.tiles[tileId].rack] + const machineIds = rack.machines + machineIds.forEach((machineId) => { + if (machineId !== null) { + const machine = state.topology.machines[machineId] + machine.cpus.forEach((id) => (energyConsumptionTotal += state.topology.cpus[id].energyConsumptionW)) + machine.gpus.forEach((id) => (energyConsumptionTotal += state.topology.gpus[id].energyConsumptionW)) + machine.memories.forEach( + (id) => (energyConsumptionTotal += state.topology.memories[id].energyConsumptionW) + ) + machine.storages.forEach( + (id) => (energyConsumptionTotal += state.topology.storages[id].energyConsumptionW) + ) + } + }) + + return Math.min(1, energyConsumptionTotal / rack.powerCapacityW) + }) + return <RackFillBar {...props} type="energy" fillFraction={fillFraction} /> +} + +RackSpaceFillContainer.propTypes = { + tileId: PropTypes.string.isRequired, +} + +export default RackSpaceFillContainer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/RackSpaceFillContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/RackSpaceFillContainer.js new file mode 100644 index 00000000..0c15d54b --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/RackSpaceFillContainer.js @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2021 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. + */ + +import React from 'react' +import PropTypes from 'prop-types' +import { useSelector } from 'react-redux' +import RackFillBar from './elements/RackFillBar' + +function RackSpaceFillContainer({ tileId, ...props }) { + const rack = useSelector((state) => state.topology.racks[state.topology.tiles[tileId].rack]) + return <RackFillBar {...props} type="space" fillFraction={rack.machines.length / rack.capacity} /> +} + +RackSpaceFillContainer.propTypes = { + tileId: PropTypes.string.isRequired, +} + +export default RackSpaceFillContainer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/RoomContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/RoomContainer.js new file mode 100644 index 00000000..65189891 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/RoomContainer.js @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2021 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. + */ + +import PropTypes from 'prop-types' +import React from 'react' +import { useDispatch, useSelector } from 'react-redux' +import { goFromBuildingToRoom } from '../../../redux/actions/interaction-level' +import RoomGroup from './groups/RoomGroup' + +function RoomContainer({ roomId, ...props }) { + const state = useSelector((state) => { + return { + interactionLevel: state.interactionLevel, + currentRoomInConstruction: state.construction.currentRoomInConstruction, + room: state.topology.rooms[roomId], + } + }) + const dispatch = useDispatch() + return <RoomGroup {...props} {...state} onClick={() => dispatch(goFromBuildingToRoom(roomId))} /> +} + +RoomContainer.propTypes = { + roomId: PropTypes.string, +} + +export default RoomContainer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/TileContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/TileContainer.js new file mode 100644 index 00000000..411a5ca7 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/TileContainer.js @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2021 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. + */ + +import React from 'react' +import PropTypes from 'prop-types' +import { useDispatch, useSelector } from 'react-redux' +import { goFromRoomToRack } from '../../../redux/actions/interaction-level' +import TileGroup from './groups/TileGroup' + +function TileContainer({ tileId, ...props }) { + const interactionLevel = useSelector((state) => state.interactionLevel) + const tile = useSelector((state) => state.topology.tiles[tileId]) + + const dispatch = useDispatch() + const onClick = (tile) => { + if (tile.rack) { + dispatch(goFromRoomToRack(tile._id)) + } + } + return <TileGroup {...props} onClick={onClick} tile={tile} interactionLevel={interactionLevel} /> +} + +TileContainer.propTypes = { + tileId: PropTypes.string.isRequired, +} + +export default TileContainer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/TopologyContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/TopologyContainer.js new file mode 100644 index 00000000..cc0d46b3 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/TopologyContainer.js @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2021 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. + */ + +import React from 'react' +import { useSelector } from 'react-redux' +import TopologyGroup from './groups/TopologyGroup' + +function TopologyContainer() { + const topology = useSelector((state) => state.topology.root) + const interactionLevel = useSelector((state) => state.interactionLevel) + + return <TopologyGroup topology={topology} interactionLevel={interactionLevel} /> +} + +export default TopologyContainer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/WallContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/WallContainer.js new file mode 100644 index 00000000..143f70c2 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/WallContainer.js @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2021 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. + */ + +import React from 'react' +import PropTypes from 'prop-types' +import { useSelector } from 'react-redux' +import WallGroup from './groups/WallGroup' + +function WallContainer({ roomId, ...props }) { + const tiles = useSelector((state) => { + return state.topology.rooms[roomId].tiles.map((tileId) => state.topology.tiles[tileId]) + }) + return <WallGroup {...props} tiles={tiles} /> +} + +WallContainer.propTypes = { + roomId: PropTypes.string.isRequired, +} + +export default WallContainer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/controls/Collapse.js b/opendc-web/opendc-web-ui/src/components/topologies/map/controls/Collapse.js new file mode 100644 index 00000000..f54b7c84 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/controls/Collapse.js @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2021 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. + */ + +import PropTypes from 'prop-types' +import { ChevronLeftIcon } from '@patternfly/react-icons' +import { collapseContainer } from './Collapse.module.scss' +import { Button } from '@patternfly/react-core' + +function Collapse({ onClick }) { + return ( + <div className={collapseContainer}> + <Button variant="tertiary" onClick={onClick}> + <ChevronLeftIcon /> + </Button> + </div> + ) +} + +Collapse.propTypes = { + onClick: PropTypes.func, +} + +export default Collapse diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/controls/Collapse.module.scss b/opendc-web/opendc-web-ui/src/components/topologies/map/controls/Collapse.module.scss new file mode 100644 index 00000000..0c1fac94 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/controls/Collapse.module.scss @@ -0,0 +1,55 @@ +/*! + * Copyright (c) 2021 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. + */ + +.collapseContainer { + position: absolute; + right: var(--pf-global--spacer--xs); + top: 0; + bottom: 10%; + margin: auto 0; + height: 50px; + + button:global(.pf-m-tertiary) { + height: 100%; + padding: 2px; + + margin-right: var(--pf-global--spacer--xs); + margin-top: var(--pf-global--spacer--xs); + background-color: var(--pf-global--BackgroundColor--100); + border: none; + border-radius: var(--pf-global--BorderRadius--sm); + box-shadow: var(--pf-global--BoxShadow--sm); + + &:not(:global(.pf-m-disabled)) { + background-color: var(--pf-global--BackgroundColor--100); + } + + &:after { + display: none; + } + + &:hover { + border: none; + box-shadow: var(--pf-global--BoxShadow--md); + } + } +} diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/controls/ScaleIndicator.js b/opendc-web/opendc-web-ui/src/components/topologies/map/controls/ScaleIndicator.js new file mode 100644 index 00000000..58d2ccc9 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/controls/ScaleIndicator.js @@ -0,0 +1,18 @@ +import PropTypes from 'prop-types' +import React from 'react' +import { TILE_SIZE_IN_METERS, TILE_SIZE_IN_PIXELS } from '../MapConstants' +import { scaleIndicator } from './ScaleIndicator.module.scss' + +function ScaleIndicator({ scale }) { + return ( + <div className={scaleIndicator} style={{ width: TILE_SIZE_IN_PIXELS * scale }}> + {TILE_SIZE_IN_METERS}m + </div> + ) +} + +ScaleIndicator.propTypes = { + scale: PropTypes.number.isRequired, +} + +export default ScaleIndicator diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/controls/ScaleIndicator.module.scss b/opendc-web/opendc-web-ui/src/components/topologies/map/controls/ScaleIndicator.module.scss new file mode 100644 index 00000000..f19e0ff2 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/controls/ScaleIndicator.module.scss @@ -0,0 +1,10 @@ +.scaleIndicator { + position: absolute; + right: 10px; + bottom: 10px; + z-index: 50; + + border: solid 2px #212529; + border-top: none; + border-left: none; +} diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/controls/Toolbar.js b/opendc-web/opendc-web-ui/src/components/topologies/map/controls/Toolbar.js new file mode 100644 index 00000000..469fd515 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/controls/Toolbar.js @@ -0,0 +1,35 @@ +import PropTypes from 'prop-types' +import React from 'react' +import { control, toolBar } from './Toolbar.module.scss' +import { Button } from '@patternfly/react-core' +import { SearchPlusIcon, SearchMinusIcon } from '@patternfly/react-icons' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { faCamera } from '@fortawesome/free-solid-svg-icons' + +function Toolbar({ onZoom, onExport }) { + return ( + <div className={toolBar}> + <Button variant="tertiary" title="Zoom in" onClick={() => onZoom(true)} className={control}> + <SearchPlusIcon /> + </Button> + <Button variant="tertiary" title="Zoom out" onClick={() => onZoom(false)} className={control}> + <SearchMinusIcon /> + </Button> + <Button + variant="tertiary" + title="Export Canvas to PNG Image" + onClick={() => onExport()} + className={control} + > + <FontAwesomeIcon icon={faCamera} /> + </Button> + </div> + ) +} + +Toolbar.propTypes = { + onZoom: PropTypes.func, + onExport: PropTypes.func, +} + +export default Toolbar diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/controls/Toolbar.module.scss b/opendc-web/opendc-web-ui/src/components/topologies/map/controls/Toolbar.module.scss new file mode 100644 index 00000000..0d505acc --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/controls/Toolbar.module.scss @@ -0,0 +1,29 @@ +.toolBar { + position: absolute; + bottom: var(--pf-global--spacer--md); + left: var(--pf-global--spacer--xl); +} + +.control { + &:global(.pf-m-tertiary) { + margin-right: var(--pf-global--spacer--xs); + margin-top: var(--pf-global--spacer--xs); + background-color: var(--pf-global--BackgroundColor--100); + border: none; + border-radius: var(--pf-global--BorderRadius--sm); + box-shadow: var(--pf-global--BoxShadow--sm); + + &:not(:global(.pf-m-disabled)) { + background-color: var(--pf-global--BackgroundColor--100); + } + + &:after { + display: none; + } + + &:hover { + border: none; + box-shadow: var(--pf-global--BoxShadow--md); + } + } +} diff --git a/opendc-web/opendc-web-ui/src/components/app/map/elements/Backdrop.js b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/Backdrop.js index 8ccfe584..93037b51 100644 --- a/opendc-web/opendc-web-ui/src/components/app/map/elements/Backdrop.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/Backdrop.js @@ -3,6 +3,8 @@ 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} /> +function Backdrop() { + return <Rect x={0} y={0} width={MAP_SIZE_IN_PIXELS} height={MAP_SIZE_IN_PIXELS} fill={BACKDROP_COLOR} /> +} export default Backdrop diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/elements/GrayLayer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/GrayLayer.js new file mode 100644 index 00000000..08c687f6 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/GrayLayer.js @@ -0,0 +1,24 @@ +import PropTypes from 'prop-types' +import React from 'react' +import { Rect } from 'react-konva' +import { GRAYED_OUT_AREA_COLOR } from '../../../../util/colors' +import { MAP_SIZE_IN_PIXELS } from '../MapConstants' + +function GrayLayer({ onClick }) { + return ( + <Rect + x={0} + y={0} + width={MAP_SIZE_IN_PIXELS} + height={MAP_SIZE_IN_PIXELS} + fill={GRAYED_OUT_AREA_COLOR} + onClick={onClick} + /> + ) +} + +GrayLayer.propTypes = { + onClick: PropTypes.func, +} + +export default GrayLayer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/elements/HoverTile.js b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/HoverTile.js new file mode 100644 index 00000000..20c2c6d1 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/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' + +function HoverTile({ x, y, isValid, scale = 1, onClick }) { + return ( + <Rect + x={x} + y={y} + 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 = { + x: PropTypes.number.isRequired, + y: PropTypes.number.isRequired, + isValid: PropTypes.bool.isRequired, + scale: PropTypes.number, + onClick: PropTypes.func.isRequired, +} + +export default HoverTile diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/elements/ImageComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/ImageComponent.js new file mode 100644 index 00000000..7d304b6b --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/ImageComponent.js @@ -0,0 +1,36 @@ +import PropTypes from 'prop-types' +import React, { useEffect, useState } from 'react' +import { Image } from 'react-konva' + +const imageCaches = {} + +function ImageComponent({ src, x, y, width, height, opacity }) { + const [image, setImage] = useState(null) + + useEffect(() => { + if (imageCaches[src]) { + setImage(imageCaches[src]) + return + } + + const image = new window.Image() + image.src = src + image.onload = () => { + setImage(image) + imageCaches[src] = image + } + }, [src]) + + return <Image image={image} x={x} y={y} width={width} height={height} opacity={opacity} /> +} + +ImageComponent.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, +} + +export default ImageComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/map/elements/RackFillBar.js b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/RackFillBar.js index 8c573a6f..aa284944 100644 --- a/opendc-web/opendc-web-ui/src/components/app/map/elements/RackFillBar.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/RackFillBar.js @@ -16,7 +16,7 @@ import { } from '../MapConstants' import ImageComponent from './ImageComponent' -const RackFillBar = ({ positionX, positionY, type, fillFraction }) => { +function RackFillBar({ positionX, positionY, type, fillFraction }) { const halfOfObjectBorderWidth = OBJECT_BORDER_WIDTH_IN_PIXELS / 2 const x = positionX * TILE_SIZE_IN_PIXELS + diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/elements/RoomTile.js b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/RoomTile.js new file mode 100644 index 00000000..e7329dc0 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/RoomTile.js @@ -0,0 +1,24 @@ +import PropTypes from 'prop-types' +import React from 'react' +import { Rect } from 'react-konva' +import { Tile } from '../../../../shapes' +import { TILE_SIZE_IN_PIXELS } from '../MapConstants' + +function RoomTile({ tile, color }) { + return ( + <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: Tile, + color: PropTypes.string, +} + +export default RoomTile diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/elements/TileObject.js b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/TileObject.js new file mode 100644 index 00000000..3211f187 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/TileObject.js @@ -0,0 +1,27 @@ +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' + +function TileObject({ positionX, positionY, color }) { + return ( + <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/opendc-web/opendc-web-ui/src/components/topologies/map/elements/TilePlusIcon.js b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/TilePlusIcon.js new file mode 100644 index 00000000..186c2b3a --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/TilePlusIcon.js @@ -0,0 +1,44 @@ +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' + +function TilePlusIcon({ x, y, scale = 1 }) { + const linePoints = [ + [ + x + 0.5 * TILE_SIZE_IN_PIXELS * scale, + y + TILE_PLUS_MARGIN_IN_PIXELS * scale, + x + 0.5 * TILE_SIZE_IN_PIXELS * scale, + y + TILE_SIZE_IN_PIXELS * scale - TILE_PLUS_MARGIN_IN_PIXELS * scale, + ], + [ + x + TILE_PLUS_MARGIN_IN_PIXELS * scale, + y + 0.5 * TILE_SIZE_IN_PIXELS * scale, + x + TILE_SIZE_IN_PIXELS * scale - TILE_PLUS_MARGIN_IN_PIXELS * scale, + y + 0.5 * TILE_SIZE_IN_PIXELS * scale, + ], + ] + return ( + <Group> + {linePoints.map((points, index) => ( + <Line + key={index} + points={points} + lineCap="round" + stroke={TILE_PLUS_COLOR} + strokeWidth={TILE_PLUS_WIDTH_IN_PIXELS * scale} + listening={false} + /> + ))} + </Group> + ) +} + +TilePlusIcon.propTypes = { + x: PropTypes.number, + y: PropTypes.number, + scale: PropTypes.number, +} + +export default TilePlusIcon diff --git a/opendc-web/opendc-web-ui/src/components/app/map/elements/WallSegment.js b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/WallSegment.js index 8aa2aebf..4f18813e 100644 --- a/opendc-web/opendc-web-ui/src/components/app/map/elements/WallSegment.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/WallSegment.js @@ -1,10 +1,10 @@ import React from 'react' import { Line } from 'react-konva' -import Shapes from '../../../../shapes/index' +import { WallSegment as WallSegmentShape } from '../../../../shapes' import { WALL_COLOR } from '../../../../util/colors' import { TILE_SIZE_IN_PIXELS, WALL_WIDTH_IN_PIXELS } from '../MapConstants' -const WallSegment = ({ wallSegment }) => { +function WallSegment({ wallSegment }) { let points if (wallSegment.isHorizontal) { points = [ @@ -26,7 +26,7 @@ const WallSegment = ({ wallSegment }) => { } WallSegment.propTypes = { - wallSegment: Shapes.WallSegment, + wallSegment: WallSegmentShape, } export default WallSegment diff --git a/opendc-web/opendc-web-ui/src/components/app/map/groups/GridGroup.js b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/GridGroup.js index ebc00244..d66a18de 100644 --- a/opendc-web/opendc-web-ui/src/components/app/map/groups/GridGroup.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/GridGroup.js @@ -17,18 +17,20 @@ const VERTICAL_POINT_PAIRS = MAP_COORDINATE_ENTRIES.map((index) => [ 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> -) +function GridGroup() { + return ( + <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/opendc-web/opendc-web-ui/src/components/app/map/groups/RackGroup.js b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/RackGroup.js index eb6dc24a..46030135 100644 --- a/opendc-web/opendc-web-ui/src/components/app/map/groups/RackGroup.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/RackGroup.js @@ -1,12 +1,12 @@ 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 { Tile } from '../../../../shapes' import { RACK_BACKGROUND_COLOR } from '../../../../util/colors' import TileObject from '../elements/TileObject' +import RackSpaceFillContainer from '../RackSpaceFillContainer' +import RackEnergyFillContainer from '../RackEnergyFillContainer' -const RackGroup = ({ tile }) => { +function RackGroup({ tile }) { return ( <Group> <TileObject positionX={tile.positionX} positionY={tile.positionY} color={RACK_BACKGROUND_COLOR} /> @@ -19,7 +19,7 @@ const RackGroup = ({ tile }) => { } RackGroup.propTypes = { - tile: Shapes.Tile, + tile: Tile, } export default RackGroup diff --git a/opendc-web/opendc-web-ui/src/components/app/map/groups/RoomGroup.js b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/RoomGroup.js index 1fd54687..a42e7bb7 100644 --- a/opendc-web/opendc-web-ui/src/components/app/map/groups/RoomGroup.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/RoomGroup.js @@ -1,15 +1,16 @@ +import PropTypes from 'prop-types' 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' +import { InteractionLevel, Room } from '../../../../shapes' +import GrayContainer from '../GrayContainer' +import TileContainer from '../TileContainer' +import WallContainer from '../WallContainer' -const RoomGroup = ({ room, interactionLevel, currentRoomInConstruction, onClick }) => { +function RoomGroup({ room, interactionLevel, currentRoomInConstruction, onClick }) { if (currentRoomInConstruction === room._id) { return ( <Group onClick={onClick}> - {room.tileIds.map((tileId) => ( + {room.tiles.map((tileId) => ( <TileContainer key={tileId} tileId={tileId} newTile={true} /> ))} </Group> @@ -24,16 +25,16 @@ const RoomGroup = ({ room, interactionLevel, currentRoomInConstruction, onClick interactionLevel.roomId === room._id ) { return [ - room.tileIds + room.tiles .filter((tileId) => tileId !== interactionLevel.tileId) .map((tileId) => <TileContainer key={tileId} tileId={tileId} />), <GrayContainer key={-1} />, - room.tileIds + room.tiles .filter((tileId) => tileId === interactionLevel.tileId) .map((tileId) => <TileContainer key={tileId} tileId={tileId} />), ] } else { - return room.tileIds.map((tileId) => <TileContainer key={tileId} tileId={tileId} />) + return room.tiles.map((tileId) => <TileContainer key={tileId} tileId={tileId} />) } })()} <WallContainer roomId={room._id} /> @@ -42,7 +43,10 @@ const RoomGroup = ({ room, interactionLevel, currentRoomInConstruction, onClick } RoomGroup.propTypes = { - room: Shapes.Room, + room: Room, + interactionLevel: InteractionLevel, + currentRoomInConstruction: PropTypes.string, + onClick: PropTypes.func, } export default RoomGroup diff --git a/opendc-web/opendc-web-ui/src/components/app/map/groups/TileGroup.js b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/TileGroup.js index 1e106823..f2084017 100644 --- a/opendc-web/opendc-web-ui/src/components/app/map/groups/TileGroup.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/TileGroup.js @@ -1,14 +1,14 @@ 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 { Tile } from '../../../../shapes' import { ROOM_DEFAULT_COLOR, ROOM_IN_CONSTRUCTION_COLOR } from '../../../../util/colors' import RoomTile from '../elements/RoomTile' +import RackContainer from '../RackContainer' -const TileGroup = ({ tile, newTile, roomLoad, onClick }) => { +function TileGroup({ tile, newTile, onClick }) { let tileObject - if (tile.rackId) { + if (tile.rack) { tileObject = <RackContainer tile={tile} /> } else { tileObject = null @@ -28,8 +28,9 @@ const TileGroup = ({ tile, newTile, roomLoad, onClick }) => { } TileGroup.propTypes = { - tile: Shapes.Tile, + tile: Tile, newTile: PropTypes.bool, + onClick: PropTypes.func, } export default TileGroup diff --git a/opendc-web/opendc-web-ui/src/components/app/map/groups/TopologyGroup.js b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/TopologyGroup.js index 6096fc8b..011dcf34 100644 --- a/opendc-web/opendc-web-ui/src/components/app/map/groups/TopologyGroup.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/TopologyGroup.js @@ -1,10 +1,10 @@ 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' +import { InteractionLevel, Topology } from '../../../../shapes' +import RoomContainer from '../RoomContainer' +import GrayContainer from '../GrayContainer' -const TopologyGroup = ({ topology, interactionLevel }) => { +function TopologyGroup({ topology, interactionLevel }) { if (!topology) { return <Group /> } @@ -12,7 +12,7 @@ const TopologyGroup = ({ topology, interactionLevel }) => { if (interactionLevel.mode === 'BUILDING') { return ( <Group> - {topology.roomIds.map((roomId) => ( + {topology.rooms.map((roomId) => ( <RoomContainer key={roomId} roomId={roomId} /> ))} </Group> @@ -21,13 +21,13 @@ const TopologyGroup = ({ topology, interactionLevel }) => { return ( <Group> - {topology.roomIds + {topology.rooms .filter((roomId) => roomId !== interactionLevel.roomId) .map((roomId) => ( <RoomContainer key={roomId} roomId={roomId} /> ))} {interactionLevel.mode === 'ROOM' ? <GrayContainer /> : null} - {topology.roomIds + {topology.rooms .filter((roomId) => roomId === interactionLevel.roomId) .map((roomId) => ( <RoomContainer key={roomId} roomId={roomId} /> @@ -37,8 +37,8 @@ const TopologyGroup = ({ topology, interactionLevel }) => { } TopologyGroup.propTypes = { - topology: Shapes.Topology, - interactionLevel: Shapes.InteractionLevel, + topology: Topology, + interactionLevel: InteractionLevel, } export default TopologyGroup diff --git a/opendc-web/opendc-web-ui/src/components/app/map/groups/WallGroup.js b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/WallGroup.js index 7b0f5ca0..6cbd1cd0 100644 --- a/opendc-web/opendc-web-ui/src/components/app/map/groups/WallGroup.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/WallGroup.js @@ -1,11 +1,11 @@ import PropTypes from 'prop-types' import React from 'react' import { Group } from 'react-konva' -import Shapes from '../../../../shapes/index' +import { Tile } from '../../../../shapes' import { deriveWallLocations } from '../../../../util/tile-calculations' import WallSegment from '../elements/WallSegment' -const WallGroup = ({ tiles }) => { +function WallGroup({ tiles }) { return ( <Group> {deriveWallLocations(tiles).map((wallSegment, index) => ( @@ -16,7 +16,7 @@ const WallGroup = ({ tiles }) => { } WallGroup.propTypes = { - tiles: PropTypes.arrayOf(Shapes.Tile).isRequired, + tiles: PropTypes.arrayOf(Tile).isRequired, } export default WallGroup diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/layers/HoverLayerComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/map/layers/HoverLayerComponent.js new file mode 100644 index 00000000..2b1060c0 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/layers/HoverLayerComponent.js @@ -0,0 +1,55 @@ +import PropTypes from 'prop-types' +import React, { useMemo, useState } from 'react' +import { Layer } from 'react-konva/lib/ReactKonva' +import HoverTile from '../elements/HoverTile' +import { TILE_SIZE_IN_PIXELS } from '../MapConstants' +import { useEffectRef } from '../../../../util/effect-ref' + +function HoverLayerComponent({ isEnabled, isValid, onClick, children }) { + const [[mouseWorldX, mouseWorldY], setPos] = useState([0, 0]) + + const layerRef = useEffectRef((layer) => { + if (!layer) { + return + } + + const stage = layer.getStage() + + // Transform used to convert mouse coordinates to world coordinates + const transform = stage.getAbsoluteTransform().copy() + transform.invert() + + stage.on('mousemove.hover', () => { + const { x, y } = transform.point(stage.getPointerPosition()) + setPos([x, y]) + }) + return () => stage.off('mousemove.hover') + }) + + const gridX = Math.floor(mouseWorldX / TILE_SIZE_IN_PIXELS) + const gridY = Math.floor(mouseWorldY / TILE_SIZE_IN_PIXELS) + const valid = useMemo(() => isEnabled && isValid(gridX, gridY), [isEnabled, isValid, gridX, gridY]) + + if (!isEnabled) { + return <Layer /> + } + + const x = gridX * TILE_SIZE_IN_PIXELS + const y = gridY * TILE_SIZE_IN_PIXELS + + return ( + <Layer opacity={0.6} ref={layerRef}> + <HoverTile x={x} y={y} isValid={valid} onClick={() => (valid ? onClick(gridX, gridY) : undefined)} /> + {children ? React.cloneElement(children, { x, y, scale: 1 }) : undefined} + </Layer> + ) +} + +HoverLayerComponent.propTypes = { + isEnabled: PropTypes.bool.isRequired, + isValid: PropTypes.func.isRequired, + onClick: PropTypes.func.isRequired, + children: PropTypes.node, +} + +export default HoverLayerComponent diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/layers/MapLayer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/layers/MapLayer.js new file mode 100644 index 00000000..c902532b --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/layers/MapLayer.js @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2021 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. + */ + +import React from 'react' +import { Group, Layer } from 'react-konva' +import Backdrop from '../elements/Backdrop' +import TopologyContainer from '../TopologyContainer' +import GridGroup from '../groups/GridGroup' + +function MapLayer() { + return ( + <Layer> + <Group> + <Backdrop /> + <TopologyContainer /> + <GridGroup /> + </Group> + </Layer> + ) +} + +export default MapLayer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/layers/ObjectHoverLayer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/layers/ObjectHoverLayer.js new file mode 100644 index 00000000..1f00de36 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/layers/ObjectHoverLayer.js @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2021 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. + */ + +import React from 'react' +import { useDispatch, useSelector } from 'react-redux' +import { addRackToTile } from '../../../../redux/actions/topology/room' +import { findTileWithPosition } from '../../../../util/tile-calculations' +import HoverLayerComponent from './HoverLayerComponent' +import TilePlusIcon from '../elements/TilePlusIcon' + +function ObjectHoverLayer() { + const isEnabled = useSelector((state) => state.construction.inRackConstructionMode) + const isValid = useSelector((state) => (x, y) => { + if (state.interactionLevel.mode !== 'ROOM') { + return false + } + + const currentRoom = state.topology.rooms[state.interactionLevel.roomId] + const tiles = currentRoom.tiles.map((tileId) => state.topology.tiles[tileId]) + const tile = findTileWithPosition(tiles, x, y) + + return !(tile === null || tile.rack) + }) + + const dispatch = useDispatch() + const onClick = (x, y) => dispatch(addRackToTile(x, y)) + return ( + <HoverLayerComponent onClick={onClick} isEnabled={isEnabled} isValid={isValid}> + <TilePlusIcon /> + </HoverLayerComponent> + ) +} + +export default ObjectHoverLayer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/layers/RoomHoverLayer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/layers/RoomHoverLayer.js new file mode 100644 index 00000000..5e351691 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/layers/RoomHoverLayer.js @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2021 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. + */ + +import React from 'react' +import { useDispatch, useSelector } from 'react-redux' +import { toggleTileAtLocation } from '../../../../redux/actions/topology/building' +import { + deriveValidNextTilePositions, + findPositionInPositions, + findPositionInRooms, +} from '../../../../util/tile-calculations' +import HoverLayerComponent from './HoverLayerComponent' + +function RoomHoverLayer() { + const dispatch = useDispatch() + const onClick = (x, y) => dispatch(toggleTileAtLocation(x, y)) + const isEnabled = useSelector((state) => state.construction.currentRoomInConstruction !== '-1') + const isValid = useSelector((state) => (x, y) => { + const newRoom = { ...state.topology.rooms[state.construction.currentRoomInConstruction] } + const oldRooms = Object.keys(state.topology.rooms) + .map((id) => ({ ...state.topology.rooms[id] })) + .filter( + (room) => + state.topology.root.rooms.indexOf(room._id) !== -1 && + room._id !== state.construction.currentRoomInConstruction + ) + + ;[...oldRooms, newRoom].forEach((room) => { + room.tiles = room.tiles.map((tileId) => state.topology.tiles[tileId]) + }) + if (newRoom.tiles.length === 0) { + return findPositionInRooms(oldRooms, x, y) === -1 + } + + const validNextPositions = deriveValidNextTilePositions(oldRooms, newRoom.tiles) + return findPositionInPositions(validNextPositions, x, y) !== -1 + }) + + return <HoverLayerComponent onClick={onClick} isEnabled={isEnabled} isValid={isValid} /> +} + +export default RoomHoverLayer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/NameComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/NameComponent.js new file mode 100644 index 00000000..ececd07b --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/NameComponent.js @@ -0,0 +1,69 @@ +import PropTypes from 'prop-types' +import React, { useRef, useState } from 'react' +import { Button, TextInput } from '@patternfly/react-core' +import { PencilAltIcon, CheckIcon, TimesIcon } from '@patternfly/react-icons' + +function NameComponent({ name, onEdit }) { + const [isEditing, setEditing] = useState(false) + const nameInput = useRef(null) + + const onCancel = () => { + nameInput.current.value = name + setEditing(false) + } + + const onSubmit = (event) => { + if (event) { + event.preventDefault() + } + + const name = nameInput.current.value + if (name) { + onEdit(name) + } + + setEditing(false) + } + + return ( + <form + className={`pf-c-inline-edit ${isEditing ? 'pf-m-inline-editable' : ''} pf-u-display-inline-block`} + onSubmit={onSubmit} + > + <div className="pf-c-inline-edit__group"> + <div className="pf-c-inline-edit__value" id="single-inline-edit-example-label"> + {name} + </div> + <div className="pf-c-inline-edit__action pf-m-enable-editable"> + <Button className="pf-u-py-0" variant="plain" aria-label="Edit" onClick={() => setEditing(true)}> + <PencilAltIcon /> + </Button> + </div> + </div> + <div className="pf-c-inline-edit__group"> + <div className="pf-c-inline-edit__input"> + <TextInput type="text" defaultValue={name} ref={nameInput} aria-label="Editable text input" /> + </div> + <div className="pf-c-inline-edit__group pf-m-action-group pf-m-icon-group"> + <div className="pf-c-inline-edit__action pf-m-valid"> + <Button className="pf-u-py-0" variant="plain" aria-label="Save edits" onClick={onSubmit}> + <CheckIcon /> + </Button> + </div> + <div className="pf-c-inline-edit__action"> + <Button className="pf-u-py-0" variant="plain" aria-label="Cancel edits" onClick={onCancel}> + <TimesIcon /> + </Button> + </div> + </div> + </div> + </form> + ) +} + +NameComponent.propTypes = { + name: PropTypes.string, + onEdit: PropTypes.func, +} + +export default NameComponent diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/TopologySidebar.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/TopologySidebar.js new file mode 100644 index 00000000..5d9340b2 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/TopologySidebar.js @@ -0,0 +1,83 @@ +import PropTypes from 'prop-types' +import React from 'react' +import { InteractionLevel } from '../../../shapes' +import BuildingSidebar from './building/BuildingSidebar' +import { + Button, + DrawerActions, + DrawerCloseButton, + DrawerHead, + DrawerPanelBody, + DrawerPanelContent, + Flex, + Title, +} from '@patternfly/react-core' +import { AngleLeftIcon } from '@patternfly/react-icons' +import { useDispatch } from 'react-redux' +import { backButton } from './TopologySidebar.module.scss' +import RoomSidebar from './room/RoomSidebar' +import RackSidebar from './rack/RackSidebar' +import MachineSidebar from './machine/MachineSidebar' +import { goDownOneInteractionLevel } from '../../../redux/actions/interaction-level' + +const name = { + BUILDING: 'Building', + ROOM: 'Room', + RACK: 'Rack', + MACHINE: 'Machine', +} + +function TopologySidebar({ interactionLevel, onClose }) { + let sidebarContent + + switch (interactionLevel.mode) { + case 'BUILDING': + sidebarContent = <BuildingSidebar /> + break + case 'ROOM': + sidebarContent = <RoomSidebar roomId={interactionLevel.roomId} /> + break + case 'RACK': + sidebarContent = <RackSidebar tileId={interactionLevel.tileId} /> + break + case 'MACHINE': + sidebarContent = <MachineSidebar tileId={interactionLevel.tileId} position={interactionLevel.position} /> + break + default: + sidebarContent = 'Missing Content' + } + + const dispatch = useDispatch() + const onClick = () => dispatch(goDownOneInteractionLevel()) + + return ( + <DrawerPanelContent isResizable defaultSize="450px" minSize="400px"> + <DrawerHead> + <Flex> + <Button + variant="tertiary" + isSmall + className={backButton} + onClick={interactionLevel.mode === 'BUILDING' ? onClose : onClick} + > + <AngleLeftIcon /> + </Button> + <Title className="pf-u-align-self-center" headingLevel="h1"> + {name[interactionLevel.mode]} + </Title> + </Flex> + <DrawerActions> + <DrawerCloseButton onClose={onClose} /> + </DrawerActions> + </DrawerHead> + <DrawerPanelBody>{sidebarContent}</DrawerPanelBody> + </DrawerPanelContent> + ) +} + +TopologySidebar.propTypes = { + interactionLevel: InteractionLevel, + onClose: PropTypes.func, +} + +export default TopologySidebar diff --git a/opendc-web/opendc-web-ui/src/config.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/TopologySidebar.module.scss index 13f4abf2..45dc98da 100644 --- a/opendc-web/opendc-web-ui/src/config.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/TopologySidebar.module.scss @@ -1,4 +1,4 @@ -/* +/*! * Copyright (c) 2021 AtLarge Research * * Permission is hereby granted, free of charge, to any person obtaining a copy @@ -20,21 +20,18 @@ * SOFTWARE. */ -function getConfig(name) { - if (process.env.NODE_ENV === 'production' && window.config_overrides) { - const value = window.config_overrides[name] - if (value !== `$${name}`) { - return value - } - } +.backButton { + &:global(.pf-c-button) { + align-self: center; + --pf-c-button--after--BorderColor: var(--pf-global--BorderColor--light-100); + color: var(--pf-global--Color--400); - return process.env[name] -} + --pf-c-button--PaddingRight: var(--pf-global--spacer--sm); + --pf-c-button--PaddingLeft: var(--pf-global--spacer--sm); -const config = { - API_BASE_URL: getConfig('REACT_APP_API_BASE_URL'), - OAUTH_CLIENT_ID: getConfig('REACT_APP_OAUTH_CLIENT_ID'), - SENTRY_DSN: getConfig('REACT_APP_SENTRY_DSN'), + &:hover, + &:focus { + --pf-c-button--after--BorderColor: var(--pf-global--BorderColor--100); + } + } } - -export default config diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/building/BuildingSidebar.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/building/BuildingSidebar.js new file mode 100644 index 00000000..5fcd46be --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/building/BuildingSidebar.js @@ -0,0 +1,8 @@ +import React from 'react' +import NewRoomConstructionContainer from './NewRoomConstructionContainer' + +function BuildingSidebar() { + return <NewRoomConstructionContainer /> +} + +export default BuildingSidebar diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/building/NewRoomConstructionComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/building/NewRoomConstructionComponent.js new file mode 100644 index 00000000..9fc85d0c --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/building/NewRoomConstructionComponent.js @@ -0,0 +1,46 @@ +import PropTypes from 'prop-types' +import React from 'react' +import { Button, Toolbar, ToolbarContent, ToolbarGroup, ToolbarItem } from '@patternfly/react-core' +import PlusIcon from '@patternfly/react-icons/dist/js/icons/plus-icon' +import CheckIcon from '@patternfly/react-icons/dist/js/icons/check-icon' + +function NewRoomConstructionComponent({ onStart, onFinish, onCancel, currentRoomInConstruction }) { + if (currentRoomInConstruction === '-1') { + return ( + <Button isBlock icon={<PlusIcon />} onClick={onStart}> + Construct a new room + </Button> + ) + } + return ( + <Toolbar + inset={{ + default: 'insetNone', + }} + > + <ToolbarContent> + <ToolbarGroup> + <ToolbarItem> + <Button icon={<CheckIcon />} onClick={onFinish}> + Finalize new room + </Button> + </ToolbarItem> + <ToolbarItem widths={{ default: '100%' }}> + <Button isBlock variant="secondary" onClick={onCancel}> + Cancel + </Button> + </ToolbarItem> + </ToolbarGroup> + </ToolbarContent> + </Toolbar> + ) +} + +NewRoomConstructionComponent.propTypes = { + onStart: PropTypes.func, + onFinish: PropTypes.func, + onCancel: PropTypes.func, + currentRoomInConstruction: PropTypes.string, +} + +export default NewRoomConstructionComponent diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/building/NewRoomConstructionContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/building/NewRoomConstructionContainer.js new file mode 100644 index 00000000..c149b224 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/building/NewRoomConstructionContainer.js @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2021 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. + */ + +import React from 'react' +import { useDispatch, useSelector } from 'react-redux' +import { + cancelNewRoomConstruction, + finishNewRoomConstruction, + startNewRoomConstruction, +} from '../../../../redux/actions/topology/building' +import NewRoomConstructionComponent from './NewRoomConstructionComponent' + +function NewRoomConstructionButton() { + const currentRoomInConstruction = useSelector((state) => state.construction.currentRoomInConstruction) + const dispatch = useDispatch() + + return ( + <NewRoomConstructionComponent + onStart={() => dispatch(startNewRoomConstruction())} + onFinish={() => dispatch(finishNewRoomConstruction())} + onCancel={() => dispatch(cancelNewRoomConstruction())} + currentRoomInConstruction={currentRoomInConstruction} + /> + ) +} + +export default NewRoomConstructionButton diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/DeleteMachine.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/DeleteMachine.js new file mode 100644 index 00000000..a4b9457b --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/DeleteMachine.js @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2021 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. + */ + +import PropTypes from 'prop-types' +import React, { useState } from 'react' +import { useDispatch } from 'react-redux' +import { Button } from '@patternfly/react-core' +import { TrashIcon } from '@patternfly/react-icons' +import ConfirmationModal from '../../../util/modals/ConfirmationModal' +import { deleteMachine } from '../../../../redux/actions/topology/machine' + +function DeleteMachine({ machineId }) { + const dispatch = useDispatch() + const [isVisible, setVisible] = useState(false) + const callback = (isConfirmed) => { + if (isConfirmed) { + dispatch(deleteMachine(machineId)) + } + setVisible(false) + } + return ( + <> + <Button variant="danger" icon={<TrashIcon />} isBlock onClick={() => setVisible(true)}> + Delete this machine + </Button> + <ConfirmationModal + title="Delete this machine" + message="Are you sure you want to delete this machine?" + isOpen={isVisible} + callback={callback} + /> + </> + ) +} + +DeleteMachine.propTypes = { + machineId: PropTypes.string.isRequired, +} + +export default DeleteMachine diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/MachineSidebar.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/MachineSidebar.js new file mode 100644 index 00000000..9268f615 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/MachineSidebar.js @@ -0,0 +1,49 @@ +import PropTypes from 'prop-types' +import React from 'react' +import UnitTabsComponent from './UnitTabsComponent' +import DeleteMachine from './DeleteMachine' +import { + TextContent, + TextList, + TextListItem, + TextListItemVariants, + TextListVariants, + Title, +} from '@patternfly/react-core' +import { useSelector } from 'react-redux' + +function MachineSidebar({ tileId, position }) { + const machine = useSelector(({ topology }) => { + const rack = topology.racks[topology.tiles[tileId].rack] + return topology.machines[rack.machines[position - 1]] + }) + const machineId = machine._id + return ( + <div> + <TextContent> + <Title headingLevel="h2">Details</Title> + <TextList component={TextListVariants.dl}> + <TextListItem component={TextListItemVariants.dt}>Name</TextListItem> + <TextListItem component={TextListItemVariants.dd}> + Machine at position {machine.position} + </TextListItem> + </TextList> + + <Title headingLevel="h2">Actions</Title> + <DeleteMachine machineId={machineId} /> + + <Title headingLevel="h2">Units</Title> + </TextContent> + <div className="pf-u-h-100"> + <UnitTabsComponent machineId={machineId} /> + </div> + </div> + ) +} + +MachineSidebar.propTypes = { + tileId: PropTypes.string.isRequired, + position: PropTypes.number.isRequired, +} + +export default MachineSidebar diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitAddComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitAddComponent.js new file mode 100644 index 00000000..88591208 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitAddComponent.js @@ -0,0 +1,42 @@ +import PropTypes from 'prop-types' +import React, { useState } from 'react' +import { Button, InputGroup, Select, SelectOption, SelectVariant } from '@patternfly/react-core' +import PlusIcon from '@patternfly/react-icons/dist/js/icons/plus-icon' + +function UnitAddComponent({ units, onAdd }) { + const [isOpen, setOpen] = useState(false) + const [selected, setSelected] = useState(null) + + return ( + <InputGroup> + <Select + variant={SelectVariant.single} + placeholderText="Select a unit" + aria-label="Select Unit" + onToggle={() => setOpen(!isOpen)} + isOpen={isOpen} + onSelect={(_, selection) => { + setSelected(selection) + setOpen(false) + }} + selections={selected} + > + {units.map((unit) => ( + <SelectOption value={unit._id} key={unit._id}> + {unit.name} + </SelectOption> + ))} + </Select> + <Button icon={<PlusIcon />} variant="control" onClick={() => onAdd(selected)}> + Add + </Button> + </InputGroup> + ) +} + +UnitAddComponent.propTypes = { + units: PropTypes.array.isRequired, + onAdd: PropTypes.func.isRequired, +} + +export default UnitAddComponent diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitAddContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitAddContainer.js new file mode 100644 index 00000000..6b136120 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitAddContainer.js @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2021 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. + */ + +import PropTypes from 'prop-types' +import React from 'react' +import { useDispatch, useSelector } from 'react-redux' +import UnitAddComponent from './UnitAddComponent' +import { addUnit } from '../../../../redux/actions/topology/machine' + +function UnitAddContainer({ machineId, unitType }) { + const units = useSelector((state) => Object.values(state.topology[unitType])) + const dispatch = useDispatch() + + const onAdd = (id) => dispatch(addUnit(machineId, unitType, id)) + + return <UnitAddComponent onAdd={onAdd} units={units} /> +} + +UnitAddContainer.propTypes = { + machineId: PropTypes.string.isRequired, + unitType: PropTypes.string.isRequired, +} + +export default UnitAddContainer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitListComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitListComponent.js new file mode 100644 index 00000000..daa3e7a7 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitListComponent.js @@ -0,0 +1,112 @@ +import PropTypes from 'prop-types' +import React from 'react' +import { + Button, + DataList, + DataListAction, + DataListCell, + DataListItem, + DataListItemCells, + DataListItemRow, + DescriptionList, + DescriptionListDescription, + DescriptionListGroup, + DescriptionListTerm, + EmptyState, + EmptyStateBody, + EmptyStateIcon, + Popover, + Title, +} from '@patternfly/react-core' +import { CubesIcon, InfoIcon, TrashIcon } from '@patternfly/react-icons' +import { ProcessingUnit, StorageUnit } from '../../../../shapes' + +function UnitInfo({ unit, unitType }) { + if (unitType === 'cpu' || unitType === 'gpu') { + return ( + <DescriptionList> + <DescriptionListGroup> + <DescriptionListTerm>Clock Frequency</DescriptionListTerm> + <DescriptionListDescription>{unit.clockRateMhz} MHz</DescriptionListDescription> + </DescriptionListGroup> + <DescriptionListGroup> + <DescriptionListTerm>Number of Cores</DescriptionListTerm> + <DescriptionListDescription>{unit.numberOfCores}</DescriptionListDescription> + </DescriptionListGroup> + <DescriptionListGroup> + <DescriptionListTerm>Energy Consumption</DescriptionListTerm> + <DescriptionListDescription>{unit.energyConsumptionW} W</DescriptionListDescription> + </DescriptionListGroup> + </DescriptionList> + ) + } + + return ( + <DescriptionList> + <DescriptionListGroup> + <DescriptionListTerm>Speed</DescriptionListTerm> + <DescriptionListDescription>{unit.speedMbPerS} Mb/s</DescriptionListDescription> + </DescriptionListGroup> + <DescriptionListGroup> + <DescriptionListTerm>Capacity</DescriptionListTerm> + <DescriptionListDescription>{unit.sizeMb} MB</DescriptionListDescription> + </DescriptionListGroup> + <DescriptionListGroup> + <DescriptionListTerm>Energy Consumption</DescriptionListTerm> + <DescriptionListDescription>{unit.energyConsumptionW} W</DescriptionListDescription> + </DescriptionListGroup> + </DescriptionList> + ) +} + +UnitInfo.propTypes = { + unitType: PropTypes.string.isRequired, + unit: PropTypes.oneOfType([ProcessingUnit, StorageUnit]).isRequired, +} + +function UnitListComponent({ unitType, units, onDelete }) { + if (units.length === 0) { + return ( + <EmptyState> + <EmptyStateIcon icon={CubesIcon} /> + <Title headingLevel="h5" size="lg"> + No units found + </Title> + <EmptyStateBody>You have not configured any units yet. Add some with the menu above!</EmptyStateBody> + </EmptyState> + ) + } + + return ( + <DataList aria-label="Machine Units" isCompact> + {units.map((unit, index) => ( + <DataListItem key={index}> + <DataListItemRow> + <DataListItemCells dataListCells={[<DataListCell key="unit">{unit.name}</DataListCell>]} /> + <DataListAction id="goto" aria-label="Goto Machine" aria-labelledby="goto"> + <Popover + headerContent="Unit Information" + bodyContent={<UnitInfo unitType={unitType} unit={unit} />} + > + <Button isSmall variant="plain" className="pf-u-p-0"> + <InfoIcon /> + </Button> + </Popover> + <Button isSmall variant="plain" className="pf-u-p-0" onClick={() => onDelete(units[index])}> + <TrashIcon /> + </Button> + </DataListAction> + </DataListItemRow> + </DataListItem> + ))} + </DataList> + ) +} + +UnitListComponent.propTypes = { + unitType: PropTypes.string.isRequired, + units: PropTypes.arrayOf(PropTypes.oneOfType([ProcessingUnit, StorageUnit])).isRequired, + onDelete: PropTypes.func, +} + +export default UnitListComponent diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitListContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitListContainer.js new file mode 100644 index 00000000..6dcc414f --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitListContainer.js @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2021 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. + */ + +import PropTypes from 'prop-types' +import React from 'react' +import { useDispatch, useSelector } from 'react-redux' +import UnitListComponent from './UnitListComponent' +import { deleteUnit } from '../../../../redux/actions/topology/machine' + +function UnitListContainer({ machineId, unitType }) { + const dispatch = useDispatch() + const units = useSelector((state) => { + const machine = state.topology.machines[machineId] + return machine[unitType].map((id) => state.topology[unitType][id]) + }) + + const onDelete = (unit) => dispatch(deleteUnit(machineId, unitType, unit._id)) + + return <UnitListComponent units={units} unitType={unitType} onDelete={onDelete} /> +} + +UnitListContainer.propTypes = { + machineId: PropTypes.string.isRequired, + unitType: PropTypes.string.isRequired, +} + +export default UnitListContainer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitTabsComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitTabsComponent.js new file mode 100644 index 00000000..b800e9d4 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitTabsComponent.js @@ -0,0 +1,36 @@ +import PropTypes from 'prop-types' +import React, { useState } from 'react' +import { Tab, Tabs, TabTitleText } from '@patternfly/react-core' +import UnitAddContainer from './UnitAddContainer' +import UnitListContainer from './UnitListContainer' + +function UnitTabsComponent({ machineId }) { + const [activeTab, setActiveTab] = useState('cpu-units') + + return ( + <Tabs activeKey={activeTab} onSelect={(_, tab) => setActiveTab(tab)}> + <Tab eventKey="cpu-units" title={<TabTitleText>CPU</TabTitleText>}> + <UnitAddContainer machineId={machineId} unitType="cpus" /> + <UnitListContainer machineId={machineId} unitType="cpus" /> + </Tab> + <Tab eventKey="gpu-units" title={<TabTitleText>GPU</TabTitleText>}> + <UnitAddContainer machineId={machineId} unitType="gpus" /> + <UnitListContainer machineId={machineId} unitType="gpus" /> + </Tab> + <Tab eventKey="memory-units" title={<TabTitleText>Memory</TabTitleText>}> + <UnitAddContainer machineId={machineId} unitType="memories" /> + <UnitListContainer machineId={machineId} unitType="memories" /> + </Tab> + <Tab eventKey="storage-units" title={<TabTitleText>Storage</TabTitleText>}> + <UnitAddContainer machineId={machineId} unitType="storages" /> + <UnitListContainer machineId={machineId} unitType="storages" /> + </Tab> + </Tabs> + ) +} + +UnitTabsComponent.propTypes = { + machineId: PropTypes.string.isRequired, +} + +export default UnitTabsComponent diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/AddPrefab.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/AddPrefab.js new file mode 100644 index 00000000..e944c2e8 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/AddPrefab.js @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2021 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. + */ + +import PropTypes from 'prop-types' +import React from 'react' +import { useDispatch } from 'react-redux' +import { Button } from '@patternfly/react-core' +import { SaveIcon } from '@patternfly/react-icons' +import { addPrefab } from '../../../../api/prefabs' + +function AddPrefab({ tileId }) { + const dispatch = useDispatch() + const onClick = () => dispatch(addPrefab('name', tileId)) + return ( + <Button variant="primary" icon={<SaveIcon />} isBlock onClick={onClick} className="pf-u-mb-sm"> + Save this rack to a prefab + </Button> + ) +} + +AddPrefab.propTypes = { + tileId: PropTypes.string.isRequired, +} + +export default AddPrefab diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/DeleteRackContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/DeleteRackContainer.js new file mode 100644 index 00000000..0583a7a4 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/DeleteRackContainer.js @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2021 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. + */ + +import PropTypes from 'prop-types' +import React, { useState } from 'react' +import { useDispatch, useSelector } from 'react-redux' +import TrashIcon from '@patternfly/react-icons/dist/js/icons/trash-icon' +import { Button } from '@patternfly/react-core' +import ConfirmationModal from '../../../util/modals/ConfirmationModal' +import { deleteRack } from '../../../../redux/actions/topology/rack' + +function DeleteRackContainer({ tileId }) { + const dispatch = useDispatch() + const [isVisible, setVisible] = useState(false) + const rackId = useSelector((state) => state.topology.tiles[tileId].rack) + const callback = (isConfirmed) => { + if (isConfirmed) { + dispatch(deleteRack(tileId, rackId)) + } + setVisible(false) + } + return ( + <> + <Button variant="danger" icon={<TrashIcon />} isBlock onClick={() => setVisible(true)}> + Delete this rack + </Button> + <ConfirmationModal + title="Delete this rack" + message="Are you sure you want to delete this rack?" + isOpen={isVisible} + callback={callback} + /> + </> + ) +} + +DeleteRackContainer.propTypes = { + tileId: PropTypes.string.isRequired, +} + +export default DeleteRackContainer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineComponent.js new file mode 100644 index 00000000..921622d6 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineComponent.js @@ -0,0 +1,46 @@ +import PropTypes from 'prop-types' +import React from 'react' +import Image from 'next/image' +import { Flex, Label } from '@patternfly/react-core' +import { Machine } from '../../../../shapes' + +const UnitIcon = ({ id, type }) => ( + <Image + src={'/img/topology/' + id + '-icon.png'} + alt={'Machine contains ' + type + ' units'} + layout="intrinsic" + height={24} + width={24} + /> +) + +UnitIcon.propTypes = { + id: PropTypes.string, + type: PropTypes.string, +} + +function MachineComponent({ machine, onClick }) { + const hasNoUnits = + machine.cpus.length + machine.gpus.length + machine.memories.length + machine.storages.length === 0 + + return ( + <Flex onClick={() => onClick()}> + {machine.cpus.length > 0 ? <UnitIcon id="cpu" type="CPU" /> : undefined} + {machine.gpus.length > 0 ? <UnitIcon id="gpu" type="GPU" /> : undefined} + {machine.memories.length > 0 ? <UnitIcon id="memory" type="memory" /> : undefined} + {machine.storages.length > 0 ? <UnitIcon id="storage" type="storage" /> : undefined} + {hasNoUnits ? ( + <Label variant="outline" color="orange"> + Machine with no units + </Label> + ) : undefined} + </Flex> + ) +} + +MachineComponent.propTypes = { + machine: Machine.isRequired, + onClick: PropTypes.func, +} + +export default MachineComponent diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineListComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineListComponent.js new file mode 100644 index 00000000..de7a2140 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineListComponent.js @@ -0,0 +1,73 @@ +import PropTypes from 'prop-types' +import React from 'react' +import MachineComponent from './MachineComponent' +import { + Badge, + Button, + DataList, + DataListAction, + DataListCell, + DataListItem, + DataListItemCells, + DataListItemRow, +} from '@patternfly/react-core' +import { AngleRightIcon, PlusIcon } from '@patternfly/react-icons' +import { Machine } from '../../../../shapes' + +function MachineListComponent({ machines = [], onSelect, onAdd }) { + return ( + <DataList aria-label="Rack Units"> + {machines.map((machine, index) => + machine ? ( + <DataListItem key={index} onClick={() => onSelect(index + 1)}> + <DataListItemRow> + <DataListItemCells + dataListCells={[ + <DataListCell isIcon key="icon"> + <Badge isRead>{machines.length - index}U</Badge> + </DataListCell>, + <DataListCell key="primary content"> + <MachineComponent onClick={() => onSelect(index + 1)} machine={machine} /> + </DataListCell>, + ]} + /> + <DataListAction id="goto" aria-label="Goto Machine" aria-labelledby="goto"> + <Button isSmall variant="plain" className="pf-u-p-0"> + <AngleRightIcon /> + </Button> + </DataListAction> + </DataListItemRow> + </DataListItem> + ) : ( + <DataListItem key={index}> + <DataListItemRow> + <DataListItemCells + dataListCells={[ + <DataListCell isIcon key="icon"> + <Badge isRead>{machines.length - index}U</Badge> + </DataListCell>, + <DataListCell key="add" className="text-secondary"> + Empty Slot + </DataListCell>, + ]} + /> + <DataListAction id="add" aria-label="Add Machine" aria-labelledby="add"> + <Button isSmall variant="plain" className="pf-u-p-0" onClick={() => onAdd(index + 1)}> + <PlusIcon /> + </Button> + </DataListAction> + </DataListItemRow> + </DataListItem> + ) + )} + </DataList> + ) +} + +MachineListComponent.propTypes = { + machines: PropTypes.arrayOf(Machine), + onSelect: PropTypes.func.isRequired, + onAdd: PropTypes.func.isRequired, +} + +export default MachineListComponent diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineListContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineListContainer.js new file mode 100644 index 00000000..619bb4e2 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineListContainer.js @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2021 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. + */ + +import PropTypes from 'prop-types' +import React, { useMemo } from 'react' +import { useDispatch, useSelector } from 'react-redux' +import MachineListComponent from './MachineListComponent' +import { goFromRackToMachine } from '../../../../redux/actions/interaction-level' +import { addMachine } from '../../../../redux/actions/topology/rack' + +function MachineListContainer({ tileId, ...props }) { + const rack = useSelector((state) => state.topology.racks[state.topology.tiles[tileId].rack]) + const machines = useSelector((state) => rack.machines.map((id) => state.topology.machines[id])) + const machinesNull = useMemo(() => { + const res = Array(rack.capacity).fill(null) + for (const machine of machines) { + res[machine.position - 1] = machine + } + return res + }, [rack, machines]) + const dispatch = useDispatch() + + return ( + <MachineListComponent + {...props} + machines={machinesNull} + onAdd={(index) => dispatch(addMachine(rack._id, index))} + onSelect={(index) => dispatch(goFromRackToMachine(index))} + /> + ) +} + +MachineListContainer.propTypes = { + tileId: PropTypes.string.isRequired, +} + +export default MachineListContainer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackNameContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackNameContainer.js new file mode 100644 index 00000000..30f38cce --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackNameContainer.js @@ -0,0 +1,22 @@ +import PropTypes from 'prop-types' +import React from 'react' +import { useDispatch, useSelector } from 'react-redux' +import NameComponent from '../NameComponent' +import { editRackName } from '../../../../redux/actions/topology/rack' + +const RackNameContainer = ({ tileId }) => { + const { name: rackName, _id } = useSelector((state) => state.topology.racks[state.topology.tiles[tileId].rack]) + const dispatch = useDispatch() + const callback = (name) => { + if (name) { + dispatch(editRackName(_id, name)) + } + } + return <NameComponent name={rackName} onEdit={callback} /> +} + +RackNameContainer.propTypes = { + tileId: PropTypes.string.isRequired, +} + +export default RackNameContainer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackSidebar.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackSidebar.js new file mode 100644 index 00000000..8f6ff135 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackSidebar.js @@ -0,0 +1,58 @@ +import PropTypes from 'prop-types' +import React from 'react' +import { machineListContainer, sidebarContainer } from './RackSidebar.module.scss' +import RackNameContainer from './RackNameContainer' +import AddPrefab from './AddPrefab' +import DeleteRackContainer from './DeleteRackContainer' +import MachineListContainer from './MachineListContainer' +import { + Skeleton, + TextContent, + TextList, + TextListItem, + TextListItemVariants, + TextListVariants, + Title, +} from '@patternfly/react-core' +import { useSelector } from 'react-redux' + +function RackSidebar({ tileId }) { + const rack = useSelector((state) => state.topology.racks[state.topology.tiles[tileId].rack]) + + return ( + <div className={sidebarContainer}> + <TextContent> + <Title headingLevel="h2">Details</Title> + <TextList component={TextListVariants.dl}> + <TextListItem + component={TextListItemVariants.dt} + className="pf-u-display-inline-flex pf-u-align-items-center" + > + Name + </TextListItem> + <TextListItem component={TextListItemVariants.dd}> + <RackNameContainer tileId={tileId} /> + </TextListItem> + <TextListItem component={TextListItemVariants.dt}>Capacity</TextListItem> + <TextListItem component={TextListItemVariants.dd}> + {rack?.capacity ?? <Skeleton screenreaderText="Loading rack" />} + </TextListItem> + </TextList> + <Title headingLevel="h2">Actions</Title> + <AddPrefab tileId={tileId} /> + <DeleteRackContainer tileId={tileId} /> + + <Title headingLevel="h2">Slots</Title> + </TextContent> + <div className={machineListContainer}> + <MachineListContainer tileId={tileId} /> + </div> + </div> + ) +} + +RackSidebar.propTypes = { + tileId: PropTypes.string.isRequired, +} + +export default RackSidebar diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackSidebar.module.scss b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackSidebar.module.scss new file mode 100644 index 00000000..6f258aec --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackSidebar.module.scss @@ -0,0 +1,12 @@ +.sidebarContainer { + display: flex; + height: 100%; + max-height: 100%; + flex-direction: column; +} + +.machineListContainer { + flex: 1; + overflow-y: scroll; + margin-top: 10px; +} diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/DeleteRoomContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/DeleteRoomContainer.js new file mode 100644 index 00000000..29b8f78a --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/DeleteRoomContainer.js @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2021 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. + */ + +import PropTypes from 'prop-types' +import React, { useState } from 'react' +import { useDispatch } from 'react-redux' +import ConfirmationModal from '../../../util/modals/ConfirmationModal' +import { deleteRoom } from '../../../../redux/actions/topology/room' +import TrashIcon from '@patternfly/react-icons/dist/js/icons/trash-icon' +import { Button } from '@patternfly/react-core' + +function DeleteRoomContainer({ roomId }) { + const dispatch = useDispatch() + const [isVisible, setVisible] = useState(false) + const callback = (isConfirmed) => { + if (isConfirmed) { + dispatch(deleteRoom(roomId)) + } + setVisible(false) + } + return ( + <> + <Button variant="danger" icon={<TrashIcon />} isBlock onClick={() => setVisible(true)}> + Delete this room + </Button> + <ConfirmationModal + title="Delete this room" + message="Are you sure you want to delete this room?" + isOpen={isVisible} + callback={callback} + /> + </> + ) +} + +DeleteRoomContainer.propTypes = { + roomId: PropTypes.string.isRequired, +} + +export default DeleteRoomContainer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/EditRoomContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/EditRoomContainer.js new file mode 100644 index 00000000..7a278cd6 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/EditRoomContainer.js @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2021 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. + */ + +import PropTypes from 'prop-types' +import React from 'react' +import { useDispatch, useSelector } from 'react-redux' +import { finishRoomEdit, startRoomEdit } from '../../../../redux/actions/topology/building' +import CheckIcon from '@patternfly/react-icons/dist/js/icons/check-icon' +import PencilAltIcon from '@patternfly/react-icons/dist/js/icons/pencil-alt-icon' +import { Button } from '@patternfly/react-core' + +function EditRoomContainer({ roomId }) { + const isEditing = useSelector((state) => state.construction.currentRoomInConstruction !== '-1') + const isInRackConstructionMode = useSelector((state) => state.construction.inRackConstructionMode) + + const dispatch = useDispatch() + const onEdit = () => dispatch(startRoomEdit(roomId)) + const onFinish = () => dispatch(finishRoomEdit()) + + return isEditing ? ( + <Button variant="tertiary" icon={<CheckIcon />} isBlock onClick={onFinish} className="pf-u-mb-sm"> + Finish editing room + </Button> + ) : ( + <Button + variant="tertiary" + icon={<PencilAltIcon />} + isBlock + disabled={isInRackConstructionMode} + onClick={() => (isInRackConstructionMode ? undefined : onEdit())} + className="pf-u-mb-sm" + > + Edit the tiles of this room + </Button> + ) +} + +EditRoomContainer.propTypes = { + roomId: PropTypes.string.isRequired, +} + +export default EditRoomContainer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RackConstructionComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RackConstructionComponent.js new file mode 100644 index 00000000..a384d5d5 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RackConstructionComponent.js @@ -0,0 +1,35 @@ +import PropTypes from 'prop-types' +import React from 'react' +import { Button } from '@patternfly/react-core' +import { PlusIcon, TimesIcon } from '@patternfly/react-icons' + +const RackConstructionComponent = ({ onStart, onStop, inRackConstructionMode, isEditingRoom }) => { + if (inRackConstructionMode) { + return ( + <Button isBlock={true} icon={<TimesIcon />} onClick={onStop} className="pf-u-mb-sm"> + Stop rack construction + </Button> + ) + } + + return ( + <Button + icon={<PlusIcon />} + isBlock + isDisabled={isEditingRoom} + onClick={() => (isEditingRoom ? undefined : onStart())} + className="pf-u-mb-sm" + > + Start rack construction + </Button> + ) +} + +RackConstructionComponent.propTypes = { + onStart: PropTypes.func, + onStop: PropTypes.func, + inRackConstructionMode: PropTypes.bool, + isEditingRoom: PropTypes.bool, +} + +export default RackConstructionComponent diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RackConstructionContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RackConstructionContainer.js new file mode 100644 index 00000000..e04287a5 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RackConstructionContainer.js @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2021 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. + */ + +import React from 'react' +import { useDispatch, useSelector } from 'react-redux' +import { startRackConstruction, stopRackConstruction } from '../../../../redux/actions/topology/room' +import RackConstructionComponent from './RackConstructionComponent' + +function RackConstructionContainer(props) { + const isRackConstructionMode = useSelector((state) => state.construction.inRackConstructionMode) + const isEditingRoom = useSelector((state) => state.construction.currentRoomInConstruction !== '-1') + + const dispatch = useDispatch() + const onStart = () => dispatch(startRackConstruction()) + const onStop = () => dispatch(stopRackConstruction()) + return ( + <RackConstructionComponent + {...props} + inRackConstructionMode={isRackConstructionMode} + isEditingRoom={isEditingRoom} + onStart={onStart} + onStop={onStop} + /> + ) +} + +export default RackConstructionContainer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RoomName.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RoomName.js new file mode 100644 index 00000000..fb52d826 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RoomName.js @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2021 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. + */ + +import PropTypes from 'prop-types' +import React from 'react' +import { useDispatch, useSelector } from 'react-redux' +import NameComponent from '../NameComponent' +import { editRoomName } from '../../../../redux/actions/topology/room' + +function RoomName({ roomId }) { + const { name: roomName, _id } = useSelector((state) => state.topology.rooms[roomId]) + const dispatch = useDispatch() + const callback = (name) => { + if (name) { + dispatch(editRoomName(_id, name)) + } + } + return <NameComponent name={roomName} onEdit={callback} /> +} + +RoomName.propTypes = { + roomId: PropTypes.string.isRequired, +} + +export default RoomName diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RoomSidebar.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RoomSidebar.js new file mode 100644 index 00000000..6ad489e0 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RoomSidebar.js @@ -0,0 +1,43 @@ +import PropTypes from 'prop-types' +import React from 'react' +import RoomName from './RoomName' +import RackConstructionContainer from './RackConstructionContainer' +import EditRoomContainer from './EditRoomContainer' +import DeleteRoomContainer from './DeleteRoomContainer' +import { + TextContent, + TextList, + TextListItem, + TextListItemVariants, + TextListVariants, + Title, +} from '@patternfly/react-core' + +const RoomSidebar = ({ roomId }) => { + return ( + <TextContent> + <Title headingLevel="h2">Details</Title> + <TextList component={TextListVariants.dl}> + <TextListItem + component={TextListItemVariants.dt} + className="pf-u-display-inline-flex pf-u-align-items-center" + > + Name + </TextListItem> + <TextListItem component={TextListItemVariants.dd}> + <RoomName roomId={roomId} /> + </TextListItem> + </TextList> + <Title headingLevel="h2">Construction</Title> + <RackConstructionContainer /> + <EditRoomContainer roomId={roomId} /> + <DeleteRoomContainer roomId={roomId} /> + </TextContent> + ) +} + +RoomSidebar.propTypes = { + roomId: PropTypes.string.isRequired, +} + +export default RoomSidebar diff --git a/opendc-web/opendc-web-ui/src/components/util/BreadcrumbLink.js b/opendc-web/opendc-web-ui/src/components/util/BreadcrumbLink.js new file mode 100644 index 00000000..c6ab214a --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/util/BreadcrumbLink.js @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2021 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. + */ + +import PropTypes from 'prop-types' +import Link from 'next/link' + +const BreadcrumbLink = ({ children, href, ...props }) => ( + <Link href={href}> + <a {...props}>{children}</a> + </Link> +) + +BreadcrumbLink.propTypes = { + children: PropTypes.node, + href: PropTypes.string.isRequired, +} + +export default BreadcrumbLink diff --git a/opendc-web/opendc-web-ui/src/components/util/NavItemLink.js b/opendc-web/opendc-web-ui/src/components/util/NavItemLink.js new file mode 100644 index 00000000..c0d109bd --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/util/NavItemLink.js @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2021 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. + */ + +import Link from 'next/link' +import PropTypes from 'prop-types' + +const NavItemLink = ({ children, href, ...props }) => ( + <Link href={href}> + <a {...props}>{children}</a> + </Link> +) + +NavItemLink.propTypes = { + children: PropTypes.node, + href: PropTypes.string.isRequired, +} + +export default NavItemLink diff --git a/opendc-web/opendc-web-ui/src/components/util/TableEmptyState.js b/opendc-web/opendc-web-ui/src/components/util/TableEmptyState.js new file mode 100644 index 00000000..9d16ffbb --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/util/TableEmptyState.js @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2021 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. + */ + +import PropTypes from 'prop-types' +import { Bullseye, EmptyState, EmptyStateBody, EmptyStateIcon, Spinner, Title } from '@patternfly/react-core' +import { SearchIcon, CubesIcon } from '@patternfly/react-icons' +import { Status } from '../../shapes' + +function TableEmptyState({ + status, + isFiltering, + loadingTitle = 'Loading', + emptyTitle = 'No results found', + emptyText = 'No results found of this type.', + emptyAction = '', +}) { + if (status === 'loading') { + return ( + <Bullseye> + <EmptyState variant="xs"> + <EmptyStateIcon variant="container" component={Spinner} /> + <Title headingLevel="h4" size="md"> + {loadingTitle} + </Title> + </EmptyState> + </Bullseye> + ) + } else if (status === 'error') { + return ( + <EmptyState variant="xs"> + <Title headingLevel="h4" size="md"> + Unable to connect + </Title> + <EmptyStateBody> + There was an error retrieving data. Check your connection and try again. + </EmptyStateBody> + </EmptyState> + ) + } else if (status === 'idle') { + return ( + <EmptyState variant="xs"> + <EmptyStateIcon icon={CubesIcon} /> + <Title headingLevel="h4" size="md"> + {emptyTitle} + </Title> + <EmptyStateBody>No results available at this moment.</EmptyStateBody> + </EmptyState> + ) + } else if (isFiltering) { + return ( + <EmptyState variant="xs"> + <EmptyStateIcon icon={SearchIcon} /> + <Title headingLevel="h4" size="md"> + No results found + </Title> + <EmptyStateBody> + No results match this filter criteria. Remove all filters or clear all filters to show results. + </EmptyStateBody> + </EmptyState> + ) + } + + return ( + <EmptyState variant="xs"> + <EmptyStateIcon icon={CubesIcon} /> + <Title headingLevel="h4" size="md"> + {emptyTitle} + </Title> + <EmptyStateBody>{emptyText}</EmptyStateBody> + {emptyAction} + </EmptyState> + ) +} + +TableEmptyState.propTypes = { + status: Status.isRequired, + isFiltering: PropTypes.bool, + loadingTitle: PropTypes.string, + emptyTitle: PropTypes.string, + emptyText: PropTypes.string, + emptyAction: PropTypes.node, +} + +export default TableEmptyState diff --git a/opendc-web/opendc-web-ui/src/components/util/modals/ConfirmationModal.js b/opendc-web/opendc-web-ui/src/components/util/modals/ConfirmationModal.js new file mode 100644 index 00000000..f6e1c98b --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/util/modals/ConfirmationModal.js @@ -0,0 +1,27 @@ +import PropTypes from 'prop-types' +import React from 'react' +import Modal from './Modal' + +function ConfirmationModal({ title, message, isOpen, callback }) { + return ( + <Modal + title={title} + isOpen={isOpen} + onSubmit={() => callback(true)} + onCancel={() => callback(false)} + submitButtonType="danger" + submitButtonText="Confirm" + > + {message} + </Modal> + ) +} + +ConfirmationModal.propTypes = { + title: PropTypes.string.isRequired, + message: PropTypes.string.isRequired, + isOpen: PropTypes.bool.isRequired, + callback: PropTypes.func.isRequired, +} + +export default ConfirmationModal diff --git a/opendc-web/opendc-web-ui/src/components/util/modals/Modal.js b/opendc-web/opendc-web-ui/src/components/util/modals/Modal.js new file mode 100644 index 00000000..d4577062 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/util/modals/Modal.js @@ -0,0 +1,38 @@ +import React from 'react' +import PropTypes from 'prop-types' +import { Button, Modal as PModal, ModalVariant } from '@patternfly/react-core' + +function Modal({ children, title, isOpen, onSubmit, onCancel, submitButtonType, submitButtonText }) { + const actions = [ + <Button variant={submitButtonType} onClick={onSubmit} key="confirm"> + {submitButtonText} + </Button>, + <Button variant="link" onClick={onCancel} key="cancel"> + Cancel + </Button>, + ] + + return ( + <PModal variant={ModalVariant.small} isOpen={isOpen} onClose={onCancel} title={title} actions={actions}> + {children} + </PModal> + ) +} + +Modal.propTypes = { + title: PropTypes.string.isRequired, + isOpen: PropTypes.bool, + onSubmit: PropTypes.func.isRequired, + onCancel: PropTypes.func.isRequired, + submitButtonType: PropTypes.string, + submitButtonText: PropTypes.string, + children: PropTypes.node, +} + +Modal.defaultProps = { + submitButtonType: 'primary', + submitButtonText: 'Save', + isOpen: false, +} + +export default Modal diff --git a/opendc-web/opendc-web-ui/src/components/util/modals/TextInputModal.js b/opendc-web/opendc-web-ui/src/components/util/modals/TextInputModal.js new file mode 100644 index 00000000..392a729e --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/util/modals/TextInputModal.js @@ -0,0 +1,70 @@ +import PropTypes from 'prop-types' +import React, { useRef, useState } from 'react' +import Modal from './Modal' +import { Form, FormGroup, TextInput } from '@patternfly/react-core' + +function TextInputModal({ title, label, isOpen, callback, initialValue }) { + const textInput = useRef(null) + const [isSubmitted, setSubmitted] = useState(false) + const [isValid, setValid] = useState(true) + + const resetState = () => { + textInput.current.value = '' + setSubmitted(false) + setValid(false) + } + const onSubmit = (event) => { + const value = textInput.current.value + setSubmitted(true) + + if (event) { + event.preventDefault() + } + + if (!value) { + setValid(false) + return false + } + + callback(value) + resetState() + return true + } + const onCancel = () => { + callback(undefined) + resetState() + } + + return ( + <Modal title={title} isOpen={isOpen} onSubmit={onSubmit} onCancel={onCancel}> + <Form onSubmit={onSubmit}> + <FormGroup + label={label} + fieldId="text-input" + isRequired + validated={isSubmitted && !isValid ? 'error' : 'default'} + helperTextInvalid="This field cannot be empty" + > + <TextInput + id="text-input" + name="text-input" + isRequired + type="text" + ref={textInput} + defaultValue={initialValue} + /> + </FormGroup> + </Form> + </Modal> + ) +} + +TextInputModal.propTypes = { + title: PropTypes.string.isRequired, + label: PropTypes.string.isRequired, + isOpen: PropTypes.bool.isRequired, + callback: PropTypes.func.isRequired, + initialValue: PropTypes.string, +} + +export default TextInputModal diff --git a/opendc-web/opendc-web-ui/src/containers/app/map/GrayContainer.js b/opendc-web/opendc-web-ui/src/containers/app/map/GrayContainer.js deleted file mode 100644 index 9e4a6969..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/map/GrayContainer.js +++ /dev/null @@ -1,13 +0,0 @@ -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/opendc-web/opendc-web-ui/src/containers/app/map/MapStage.js b/opendc-web/opendc-web-ui/src/containers/app/map/MapStage.js deleted file mode 100644 index 23c920b6..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/map/MapStage.js +++ /dev/null @@ -1,22 +0,0 @@ -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/opendc-web/opendc-web-ui/src/containers/app/map/RackContainer.js b/opendc-web/opendc-web-ui/src/containers/app/map/RackContainer.js deleted file mode 100644 index 40077608..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/map/RackContainer.js +++ /dev/null @@ -1,12 +0,0 @@ -import { connect } from 'react-redux' -import RackGroup from '../../../components/app/map/groups/RackGroup' - -const mapStateToProps = (state) => { - return { - interactionLevel: state.interactionLevel, - } -} - -const RackContainer = connect(mapStateToProps)(RackGroup) - -export default RackContainer diff --git a/opendc-web/opendc-web-ui/src/containers/app/map/RackEnergyFillContainer.js b/opendc-web/opendc-web-ui/src/containers/app/map/RackEnergyFillContainer.js deleted file mode 100644 index 53746271..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/map/RackEnergyFillContainer.js +++ /dev/null @@ -1,26 +0,0 @@ -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].rackId] - 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/opendc-web/opendc-web-ui/src/containers/app/map/RackSpaceFillContainer.js b/opendc-web/opendc-web-ui/src/containers/app/map/RackSpaceFillContainer.js deleted file mode 100644 index 0509a5a5..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/map/RackSpaceFillContainer.js +++ /dev/null @@ -1,14 +0,0 @@ -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].rackId].machineIds - return { - type: 'space', - fillFraction: machineIds.filter((id) => id !== null).length / machineIds.length, - } -} - -const RackSpaceFillContainer = connect(mapStateToProps)(RackFillBar) - -export default RackSpaceFillContainer diff --git a/opendc-web/opendc-web-ui/src/containers/app/map/RoomContainer.js b/opendc-web/opendc-web-ui/src/containers/app/map/RoomContainer.js deleted file mode 100644 index 91bf4e5d..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/map/RoomContainer.js +++ /dev/null @@ -1,21 +0,0 @@ -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/opendc-web/opendc-web-ui/src/containers/app/map/TileContainer.js b/opendc-web/opendc-web-ui/src/containers/app/map/TileContainer.js deleted file mode 100644 index 04d6c8d6..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/map/TileContainer.js +++ /dev/null @@ -1,26 +0,0 @@ -import { connect } from 'react-redux' -import { goFromRoomToRack } from '../../../actions/interaction-level' -import TileGroup from '../../../components/app/map/groups/TileGroup' - -const mapStateToProps = (state, ownProps) => { - const tile = state.objects.tile[ownProps.tileId] - - return { - interactionLevel: state.interactionLevel, - tile, - } -} - -const mapDispatchToProps = (dispatch) => { - return { - onClick: (tile) => { - if (tile.rackId) { - dispatch(goFromRoomToRack(tile._id)) - } - }, - } -} - -const TileContainer = connect(mapStateToProps, mapDispatchToProps)(TileGroup) - -export default TileContainer diff --git a/opendc-web/opendc-web-ui/src/containers/app/map/TopologyContainer.js b/opendc-web/opendc-web-ui/src/containers/app/map/TopologyContainer.js deleted file mode 100644 index de43a151..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/map/TopologyContainer.js +++ /dev/null @@ -1,17 +0,0 @@ -import { connect } from 'react-redux' -import TopologyGroup from '../../../components/app/map/groups/TopologyGroup' - -const mapStateToProps = (state) => { - if (state.currentTopologyId === '-1') { - return {} - } - - return { - topology: state.objects.topology[state.currentTopologyId], - interactionLevel: state.interactionLevel, - } -} - -const TopologyContainer = connect(mapStateToProps)(TopologyGroup) - -export default TopologyContainer diff --git a/opendc-web/opendc-web-ui/src/containers/app/map/WallContainer.js b/opendc-web/opendc-web-ui/src/containers/app/map/WallContainer.js deleted file mode 100644 index 67f8a242..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/map/WallContainer.js +++ /dev/null @@ -1,12 +0,0 @@ -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/opendc-web/opendc-web-ui/src/containers/app/map/controls/ScaleIndicatorContainer.js b/opendc-web/opendc-web-ui/src/containers/app/map/controls/ScaleIndicatorContainer.js deleted file mode 100644 index fa3b9d22..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/map/controls/ScaleIndicatorContainer.js +++ /dev/null @@ -1,12 +0,0 @@ -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/opendc-web/opendc-web-ui/src/containers/app/map/controls/ZoomControlContainer.js b/opendc-web/opendc-web-ui/src/containers/app/map/controls/ZoomControlContainer.js deleted file mode 100644 index ddc68cc7..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/map/controls/ZoomControlContainer.js +++ /dev/null @@ -1,19 +0,0 @@ -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/opendc-web/opendc-web-ui/src/containers/app/map/layers/MapLayer.js b/opendc-web/opendc-web-ui/src/containers/app/map/layers/MapLayer.js deleted file mode 100644 index 8596cb9c..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/map/layers/MapLayer.js +++ /dev/null @@ -1,13 +0,0 @@ -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/opendc-web/opendc-web-ui/src/containers/app/map/layers/ObjectHoverLayer.js b/opendc-web/opendc-web-ui/src/containers/app/map/layers/ObjectHoverLayer.js deleted file mode 100644 index a4927862..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/map/layers/ObjectHoverLayer.js +++ /dev/null @@ -1,33 +0,0 @@ -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.rackId) - }, - } -} - -const mapDispatchToProps = (dispatch) => { - return { - onClick: (x, y) => dispatch(addRackToTile(x, y)), - } -} - -const ObjectHoverLayer = connect(mapStateToProps, mapDispatchToProps)(ObjectHoverLayerComponent) - -export default ObjectHoverLayer diff --git a/opendc-web/opendc-web-ui/src/containers/app/map/layers/RoomHoverLayer.js b/opendc-web/opendc-web-ui/src/containers/app/map/layers/RoomHoverLayer.js deleted file mode 100644 index 66404f9e..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/map/layers/RoomHoverLayer.js +++ /dev/null @@ -1,46 +0,0 @@ -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.topology[state.currentTopologyId].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/opendc-web/opendc-web-ui/src/containers/app/results/PortfolioResultsContainer.js b/opendc-web/opendc-web-ui/src/containers/app/results/PortfolioResultsContainer.js deleted file mode 100644 index 4b430e54..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/results/PortfolioResultsContainer.js +++ /dev/null @@ -1,28 +0,0 @@ -import { connect } from 'react-redux' -import PortfolioResultsComponent from '../../../components/app/results/PortfolioResultsComponent' - -const mapStateToProps = (state) => { - if ( - state.currentPortfolioId === '-1' || - !state.objects.portfolio[state.currentPortfolioId] || - state.objects.portfolio[state.currentPortfolioId].scenarioIds - .map((scenarioId) => state.objects.scenario[scenarioId]) - .some((s) => s === undefined) - ) { - return { - portfolio: undefined, - scenarios: [], - } - } - - return { - portfolio: state.objects.portfolio[state.currentPortfolioId], - scenarios: state.objects.portfolio[state.currentPortfolioId].scenarioIds.map( - (scenarioId) => state.objects.scenario[scenarioId] - ), - } -} - -const PortfolioResultsContainer = connect(mapStateToProps)(PortfolioResultsComponent) - -export default PortfolioResultsContainer diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/PortfolioListContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/PortfolioListContainer.js deleted file mode 100644 index b32c8b1d..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/PortfolioListContainer.js +++ /dev/null @@ -1,45 +0,0 @@ -import { connect } from 'react-redux' -import { withRouter } from 'react-router-dom' -import PortfolioListComponent from '../../../../components/app/sidebars/project/PortfolioListComponent' -import { deletePortfolio, setCurrentPortfolio } from '../../../../actions/portfolios' -import { openNewPortfolioModal } from '../../../../actions/modals/portfolios' -import { getState } from '../../../../util/state-utils' -import { setCurrentTopology } from '../../../../actions/topology/building' - -const mapStateToProps = (state) => { - let portfolios = state.objects.project[state.currentProjectId] - ? state.objects.project[state.currentProjectId].portfolioIds.map((t) => state.objects.portfolio[t]) - : [] - if (portfolios.filter((t) => !t).length > 0) { - portfolios = [] - } - - return { - currentProjectId: state.currentProjectId, - currentPortfolioId: state.currentPortfolioId, - portfolios, - } -} - -const mapDispatchToProps = (dispatch, ownProps) => { - return { - onNewPortfolio: () => { - dispatch(openNewPortfolioModal()) - }, - onChoosePortfolio: (portfolioId) => { - dispatch(setCurrentPortfolio(portfolioId)) - }, - onDeletePortfolio: async (id) => { - if (id) { - const state = await getState(dispatch) - dispatch(deletePortfolio(id)) - dispatch(setCurrentTopology(state.objects.project[state.currentProjectId].topologyIds[0])) - ownProps.history.push(`/projects/${state.currentProjectId}`) - } - }, - } -} - -const PortfolioListContainer = withRouter(connect(mapStateToProps, mapDispatchToProps)(PortfolioListComponent)) - -export default PortfolioListContainer diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/ProjectSidebarContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/ProjectSidebarContainer.js deleted file mode 100644 index 49001099..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/ProjectSidebarContainer.js +++ /dev/null @@ -1,10 +0,0 @@ -import React from 'react' -import { withRouter } from 'react-router-dom' -import ProjectSidebarComponent from '../../../../components/app/sidebars/project/ProjectSidebarComponent' -import { isCollapsible } from '../../../../util/sidebar-space' - -const ProjectSidebarContainer = withRouter(({ location, ...props }) => ( - <ProjectSidebarComponent collapsible={isCollapsible(location)} {...props} /> -)) - -export default ProjectSidebarContainer diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/ScenarioListContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/ScenarioListContainer.js deleted file mode 100644 index 415e2792..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/ScenarioListContainer.js +++ /dev/null @@ -1,41 +0,0 @@ -import { connect } from 'react-redux' -import ScenarioListComponent from '../../../../components/app/sidebars/project/ScenarioListComponent' -import { openNewScenarioModal } from '../../../../actions/modals/scenarios' -import { deleteScenario, setCurrentScenario } from '../../../../actions/scenarios' -import { setCurrentPortfolio } from '../../../../actions/portfolios' - -const mapStateToProps = (state, ownProps) => { - let scenarios = state.objects.portfolio[ownProps.portfolioId] - ? state.objects.portfolio[ownProps.portfolioId].scenarioIds.map((t) => state.objects.scenario[t]) - : [] - if (scenarios.filter((t) => !t).length > 0) { - scenarios = [] - } - - return { - currentProjectId: state.currentProjectId, - currentScenarioId: state.currentScenarioId, - scenarios, - } -} - -const mapDispatchToProps = (dispatch) => { - return { - onNewScenario: (currentPortfolioId) => { - dispatch(setCurrentPortfolio(currentPortfolioId)) - dispatch(openNewScenarioModal()) - }, - onChooseScenario: (portfolioId, scenarioId) => { - dispatch(setCurrentScenario(portfolioId, scenarioId)) - }, - onDeleteScenario: (id) => { - if (id) { - dispatch(deleteScenario(id)) - } - }, - } -} - -const ScenarioListContainer = connect(mapStateToProps, mapDispatchToProps)(ScenarioListComponent) - -export default ScenarioListContainer diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/TopologyListContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/TopologyListContainer.js deleted file mode 100644 index e1de18f9..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/TopologyListContainer.js +++ /dev/null @@ -1,46 +0,0 @@ -import { connect } from 'react-redux' -import TopologyListComponent from '../../../../components/app/sidebars/project/TopologyListComponent' -import { setCurrentTopology } from '../../../../actions/topology/building' -import { openNewTopologyModal } from '../../../../actions/modals/topology' -import { withRouter } from 'react-router-dom' -import { getState } from '../../../../util/state-utils' -import { deleteTopology } from '../../../../actions/topologies' - -const mapStateToProps = (state) => { - let topologies = state.objects.project[state.currentProjectId] - ? state.objects.project[state.currentProjectId].topologyIds.map((t) => state.objects.topology[t]) - : [] - if (topologies.filter((t) => !t).length > 0) { - topologies = [] - } - - return { - currentTopologyId: state.currentTopologyId, - topologies, - } -} - -const mapDispatchToProps = (dispatch, ownProps) => { - return { - onChooseTopology: async (id) => { - dispatch(setCurrentTopology(id)) - const state = await getState(dispatch) - ownProps.history.push(`/projects/${state.currentProjectId}`) - }, - onNewTopology: () => { - dispatch(openNewTopologyModal()) - }, - onDeleteTopology: async (id) => { - if (id) { - const state = await getState(dispatch) - dispatch(deleteTopology(id)) - dispatch(setCurrentTopology(state.objects.project[state.currentProjectId].topologyIds[0])) - ownProps.history.push(`/projects/${state.currentProjectId}`) - } - }, - } -} - -const TopologyListContainer = withRouter(connect(mapStateToProps, mapDispatchToProps)(TopologyListComponent)) - -export default TopologyListContainer diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/TopologySidebarContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/TopologySidebarContainer.js deleted file mode 100644 index fe7c02fd..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/TopologySidebarContainer.js +++ /dev/null @@ -1,12 +0,0 @@ -import { connect } from 'react-redux' -import TopologySidebarComponent from '../../../../components/app/sidebars/topology/TopologySidebarComponent' - -const mapStateToProps = (state) => { - return { - interactionLevel: state.interactionLevel, - } -} - -const TopologySidebarContainer = connect(mapStateToProps)(TopologySidebarComponent) - -export default TopologySidebarContainer diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/building/BuildingSidebarContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/building/BuildingSidebarContainer.js deleted file mode 100644 index a0b52e56..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/building/BuildingSidebarContainer.js +++ /dev/null @@ -1,5 +0,0 @@ -import BuildingSidebarComponent from '../../../../../components/app/sidebars/topology/building/BuildingSidebarComponent' - -const BuildingSidebarContainer = BuildingSidebarComponent - -export default BuildingSidebarContainer diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/building/NewRoomConstructionContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/building/NewRoomConstructionContainer.js deleted file mode 100644 index ea9e9e60..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/building/NewRoomConstructionContainer.js +++ /dev/null @@ -1,25 +0,0 @@ -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/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/BackToRackContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/BackToRackContainer.js deleted file mode 100644 index 24287ab0..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/BackToRackContainer.js +++ /dev/null @@ -1,13 +0,0 @@ -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/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/DeleteMachineContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/DeleteMachineContainer.js deleted file mode 100644 index 65e683e6..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/DeleteMachineContainer.js +++ /dev/null @@ -1,13 +0,0 @@ -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/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/MachineNameContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/MachineNameContainer.js deleted file mode 100644 index 1cf35b05..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/MachineNameContainer.js +++ /dev/null @@ -1,12 +0,0 @@ -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/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/MachineSidebarContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/MachineSidebarContainer.js deleted file mode 100644 index b04e3118..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/MachineSidebarContainer.js +++ /dev/null @@ -1,15 +0,0 @@ -import { connect } from 'react-redux' -import MachineSidebarComponent from '../../../../../components/app/sidebars/topology/machine/MachineSidebarComponent' - -const mapStateToProps = (state) => { - return { - machineId: - state.objects.rack[state.objects.tile[state.interactionLevel.tileId].rackId].machineIds[ - state.interactionLevel.position - 1 - ], - } -} - -const MachineSidebarContainer = connect(mapStateToProps)(MachineSidebarComponent) - -export default MachineSidebarContainer diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/UnitAddContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/UnitAddContainer.js deleted file mode 100644 index 29e48016..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/UnitAddContainer.js +++ /dev/null @@ -1,19 +0,0 @@ -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/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/UnitContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/UnitContainer.js deleted file mode 100644 index f334f9f2..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/UnitContainer.js +++ /dev/null @@ -1,20 +0,0 @@ -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], - index: ownProps.unitId, - } -} - -const mapDispatchToProps = (dispatch, ownProps) => { - return { - onDelete: () => dispatch(deleteUnit(ownProps.unitType, ownProps.index)), - } -} - -const UnitContainer = connect(mapStateToProps, mapDispatchToProps)(UnitComponent) - -export default UnitContainer diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/UnitListContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/UnitListContainer.js deleted file mode 100644 index f382ff74..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/UnitListContainer.js +++ /dev/null @@ -1,17 +0,0 @@ -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].rackId].machineIds[ - state.interactionLevel.position - 1 - ] - ][ownProps.unitType + 'Ids'], - } -} - -const UnitListContainer = connect(mapStateToProps)(UnitListComponent) - -export default UnitListContainer diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/UnitTabsContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/UnitTabsContainer.js deleted file mode 100644 index 00fe4067..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/UnitTabsContainer.js +++ /dev/null @@ -1,5 +0,0 @@ -import UnitTabsComponent from '../../../../../components/app/sidebars/topology/machine/UnitTabsComponent' - -const UnitTabsContainer = UnitTabsComponent - -export default UnitTabsContainer diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/AddPrefabContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/AddPrefabContainer.js deleted file mode 100644 index c941e745..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/AddPrefabContainer.js +++ /dev/null @@ -1,13 +0,0 @@ -import { connect } from 'react-redux' -import { addPrefab } from '../../../../../actions/prefabs' -import AddPrefabComponent from '../../../../../components/app/sidebars/topology/rack/AddPrefabComponent' - -const mapDispatchToProps = (dispatch) => { - return { - onClick: () => dispatch(addPrefab('name')), - } -} - -const AddPrefabContainer = connect(undefined, mapDispatchToProps)(AddPrefabComponent) - -export default AddPrefabContainer diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/BackToRoomContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/BackToRoomContainer.js deleted file mode 100644 index 58c3b082..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/BackToRoomContainer.js +++ /dev/null @@ -1,13 +0,0 @@ -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/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/DeleteRackContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/DeleteRackContainer.js deleted file mode 100644 index 8229a359..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/DeleteRackContainer.js +++ /dev/null @@ -1,13 +0,0 @@ -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/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/EmptySlotContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/EmptySlotContainer.js deleted file mode 100644 index cf341da9..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/EmptySlotContainer.js +++ /dev/null @@ -1,13 +0,0 @@ -import { connect } from 'react-redux' -import { addMachine } from '../../../../../actions/topology/rack' -import EmptySlotComponent from '../../../../../components/app/sidebars/topology/rack/EmptySlotComponent' - -const mapDispatchToProps = (dispatch, ownProps) => { - return { - onAdd: () => dispatch(addMachine(ownProps.position)), - } -} - -const EmptySlotContainer = connect(undefined, mapDispatchToProps)(EmptySlotComponent) - -export default EmptySlotContainer diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/MachineContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/MachineContainer.js deleted file mode 100644 index fe12827d..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/MachineContainer.js +++ /dev/null @@ -1,19 +0,0 @@ -import { connect } from 'react-redux' -import { goFromRackToMachine } from '../../../../../actions/interaction-level' -import MachineComponent from '../../../../../components/app/sidebars/topology/rack/MachineComponent' - -const mapStateToProps = (state, ownProps) => { - return { - machine: state.objects.machine[ownProps.machineId], - } -} - -const mapDispatchToProps = (dispatch, ownProps) => { - return { - onClick: () => dispatch(goFromRackToMachine(ownProps.position)), - } -} - -const MachineContainer = connect(mapStateToProps, mapDispatchToProps)(MachineComponent) - -export default MachineContainer diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/MachineListContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/MachineListContainer.js deleted file mode 100644 index bc5a285a..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/MachineListContainer.js +++ /dev/null @@ -1,12 +0,0 @@ -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].rackId].machineIds, - } -} - -const MachineListContainer = connect(mapStateToProps)(MachineListComponent) - -export default MachineListContainer diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/RackNameContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/RackNameContainer.js deleted file mode 100644 index 504dbc61..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/RackNameContainer.js +++ /dev/null @@ -1,19 +0,0 @@ -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].rackId].name, - } -} - -const mapDispatchToProps = (dispatch) => { - return { - onEdit: () => dispatch(openEditRackNameModal()), - } -} - -const RackNameContainer = connect(mapStateToProps, mapDispatchToProps)(RackNameComponent) - -export default RackNameContainer diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/RackSidebarContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/RackSidebarContainer.js deleted file mode 100644 index 453d7e41..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/RackSidebarContainer.js +++ /dev/null @@ -1,12 +0,0 @@ -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].rackId, - } -} - -const RackSidebarContainer = connect(mapStateToProps)(RackSidebarComponent) - -export default RackSidebarContainer diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/BackToBuildingContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/BackToBuildingContainer.js deleted file mode 100644 index 4c1ab99d..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/BackToBuildingContainer.js +++ /dev/null @@ -1,13 +0,0 @@ -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/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/DeleteRoomContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/DeleteRoomContainer.js deleted file mode 100644 index 636fa5c5..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/DeleteRoomContainer.js +++ /dev/null @@ -1,13 +0,0 @@ -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/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/EditRoomContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/EditRoomContainer.js deleted file mode 100644 index d17a45d1..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/EditRoomContainer.js +++ /dev/null @@ -1,21 +0,0 @@ -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/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/RackConstructionContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/RackConstructionContainer.js deleted file mode 100644 index cd8319de..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/RackConstructionContainer.js +++ /dev/null @@ -1,21 +0,0 @@ -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/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/RoomNameContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/RoomNameContainer.js deleted file mode 100644 index cab16016..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/RoomNameContainer.js +++ /dev/null @@ -1,19 +0,0 @@ -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/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/RoomSidebarContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/RoomSidebarContainer.js deleted file mode 100644 index 8c3ca8ab..00000000 --- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/RoomSidebarContainer.js +++ /dev/null @@ -1,12 +0,0 @@ -import { connect } from 'react-redux' -import RoomSidebarComponent from '../../../../../components/app/sidebars/topology/room/RoomSidebarComponent' - -const mapStateToProps = (state) => { - return { - roomId: state.interactionLevel.roomId, - } -} - -const RoomSidebarContainer = connect(mapStateToProps)(RoomSidebarComponent) - -export default RoomSidebarContainer diff --git a/opendc-web/opendc-web-ui/src/containers/auth/Login.js b/opendc-web/opendc-web-ui/src/containers/auth/Login.js deleted file mode 100644 index bebe015c..00000000 --- a/opendc-web/opendc-web-ui/src/containers/auth/Login.js +++ /dev/null @@ -1,63 +0,0 @@ -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' -import config from '../../config' - -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={config['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/opendc-web/opendc-web-ui/src/containers/auth/Logout.js b/opendc-web/opendc-web-ui/src/containers/auth/Logout.js deleted file mode 100644 index 22400381..00000000 --- a/opendc-web/opendc-web-ui/src/containers/auth/Logout.js +++ /dev/null @@ -1,13 +0,0 @@ -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/opendc-web/opendc-web-ui/src/containers/auth/ProfileName.js b/opendc-web/opendc-web-ui/src/containers/auth/ProfileName.js deleted file mode 100644 index 06da75ab..00000000 --- a/opendc-web/opendc-web-ui/src/containers/auth/ProfileName.js +++ /dev/null @@ -1,14 +0,0 @@ -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/opendc-web/opendc-web-ui/src/containers/modals/DeleteMachineModal.js b/opendc-web/opendc-web-ui/src/containers/modals/DeleteMachineModal.js deleted file mode 100644 index f30febdb..00000000 --- a/opendc-web/opendc-web-ui/src/containers/modals/DeleteMachineModal.js +++ /dev/null @@ -1,35 +0,0 @@ -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/opendc-web/opendc-web-ui/src/containers/modals/DeleteProfileModal.js b/opendc-web/opendc-web-ui/src/containers/modals/DeleteProfileModal.js deleted file mode 100644 index e7c4014d..00000000 --- a/opendc-web/opendc-web-ui/src/containers/modals/DeleteProfileModal.js +++ /dev/null @@ -1,35 +0,0 @@ -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/opendc-web/opendc-web-ui/src/containers/modals/DeleteRackModal.js b/opendc-web/opendc-web-ui/src/containers/modals/DeleteRackModal.js deleted file mode 100644 index 0cb22a7e..00000000 --- a/opendc-web/opendc-web-ui/src/containers/modals/DeleteRackModal.js +++ /dev/null @@ -1,35 +0,0 @@ -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/opendc-web/opendc-web-ui/src/containers/modals/DeleteRoomModal.js b/opendc-web/opendc-web-ui/src/containers/modals/DeleteRoomModal.js deleted file mode 100644 index 1f6eef92..00000000 --- a/opendc-web/opendc-web-ui/src/containers/modals/DeleteRoomModal.js +++ /dev/null @@ -1,35 +0,0 @@ -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/opendc-web/opendc-web-ui/src/containers/modals/EditRackNameModal.js b/opendc-web/opendc-web-ui/src/containers/modals/EditRackNameModal.js deleted file mode 100644 index 9128f449..00000000 --- a/opendc-web/opendc-web-ui/src/containers/modals/EditRackNameModal.js +++ /dev/null @@ -1,40 +0,0 @@ -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].rackId].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/opendc-web/opendc-web-ui/src/containers/modals/EditRoomNameModal.js b/opendc-web/opendc-web-ui/src/containers/modals/EditRoomNameModal.js deleted file mode 100644 index 8032a5d1..00000000 --- a/opendc-web/opendc-web-ui/src/containers/modals/EditRoomNameModal.js +++ /dev/null @@ -1,38 +0,0 @@ -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/opendc-web/opendc-web-ui/src/containers/modals/NewPortfolioModal.js b/opendc-web/opendc-web-ui/src/containers/modals/NewPortfolioModal.js deleted file mode 100644 index 6cf12d8e..00000000 --- a/opendc-web/opendc-web-ui/src/containers/modals/NewPortfolioModal.js +++ /dev/null @@ -1,30 +0,0 @@ -import { connect } from 'react-redux' -import NewPortfolioModalComponent from '../../components/modals/custom-components/NewPortfolioModalComponent' -import { addPortfolio } from '../../actions/portfolios' -import { closeNewPortfolioModal } from '../../actions/modals/portfolios' - -const mapStateToProps = (state) => { - return { - show: state.modals.newPortfolioModalVisible, - } -} - -const mapDispatchToProps = (dispatch) => { - return { - callback: (name, targets) => { - if (name) { - dispatch( - addPortfolio({ - name, - targets, - }) - ) - } - dispatch(closeNewPortfolioModal()) - }, - } -} - -const NewPortfolioModal = connect(mapStateToProps, mapDispatchToProps)(NewPortfolioModalComponent) - -export default NewPortfolioModal diff --git a/opendc-web/opendc-web-ui/src/containers/modals/NewProjectModal.js b/opendc-web/opendc-web-ui/src/containers/modals/NewProjectModal.js deleted file mode 100644 index d306dc45..00000000 --- a/opendc-web/opendc-web-ui/src/containers/modals/NewProjectModal.js +++ /dev/null @@ -1,30 +0,0 @@ -import React from 'react' -import { connect } from 'react-redux' -import { closeNewProjectModal } from '../../actions/modals/projects' -import { addProject } from '../../actions/projects' -import TextInputModal from '../../components/modals/TextInputModal' - -const NewProjectModalComponent = ({ visible, callback }) => ( - <TextInputModal title="New Project" label="Project title" show={visible} callback={callback} /> -) - -const mapStateToProps = (state) => { - return { - visible: state.modals.newProjectModalVisible, - } -} - -const mapDispatchToProps = (dispatch) => { - return { - callback: (text) => { - if (text) { - dispatch(addProject(text)) - } - dispatch(closeNewProjectModal()) - }, - } -} - -const NewProjectModal = connect(mapStateToProps, mapDispatchToProps)(NewProjectModalComponent) - -export default NewProjectModal diff --git a/opendc-web/opendc-web-ui/src/containers/modals/NewScenarioModal.js b/opendc-web/opendc-web-ui/src/containers/modals/NewScenarioModal.js deleted file mode 100644 index 7d774fa4..00000000 --- a/opendc-web/opendc-web-ui/src/containers/modals/NewScenarioModal.js +++ /dev/null @@ -1,50 +0,0 @@ -import { connect } from 'react-redux' -import NewScenarioModalComponent from '../../components/modals/custom-components/NewScenarioModalComponent' -import { addScenario } from '../../actions/scenarios' -import { closeNewScenarioModal } from '../../actions/modals/scenarios' - -const mapStateToProps = (state) => { - let topologies = - state.currentProjectId !== '-1' - ? state.objects.project[state.currentProjectId].topologyIds.map((t) => state.objects.topology[t]) - : [] - if (topologies.filter((t) => !t).length > 0) { - topologies = [] - } - - return { - show: state.modals.newScenarioModalVisible, - currentPortfolioId: state.currentPortfolioId, - currentPortfolioScenarioIds: - state.currentPortfolioId !== '-1' && state.objects.portfolio[state.currentPortfolioId] - ? state.objects.portfolio[state.currentPortfolioId].scenarioIds - : [], - traces: Object.values(state.objects.trace), - topologies, - schedulers: Object.values(state.objects.scheduler), - } -} - -const mapDispatchToProps = (dispatch) => { - return { - callback: (name, portfolioId, trace, topology, operational) => { - if (name) { - dispatch( - addScenario({ - portfolioId, - name, - trace, - topology, - operational, - }) - ) - } - - dispatch(closeNewScenarioModal()) - }, - } -} - -const NewScenarioModal = connect(mapStateToProps, mapDispatchToProps)(NewScenarioModalComponent) - -export default NewScenarioModal diff --git a/opendc-web/opendc-web-ui/src/containers/modals/NewTopologyModal.js b/opendc-web/opendc-web-ui/src/containers/modals/NewTopologyModal.js deleted file mode 100644 index 0acf6cf2..00000000 --- a/opendc-web/opendc-web-ui/src/containers/modals/NewTopologyModal.js +++ /dev/null @@ -1,42 +0,0 @@ -import { connect } from 'react-redux' -import NewTopologyModalComponent from '../../components/modals/custom-components/NewTopologyModalComponent' -import { closeNewTopologyModal } from '../../actions/modals/topology' -import { addTopology } from '../../actions/topologies' - -const mapStateToProps = (state) => { - let topologies = state.objects.project[state.currentProjectId] - ? state.objects.project[state.currentProjectId].topologyIds.map((t) => state.objects.topology[t]) - : [] - if (topologies.filter((t) => !t).length > 0) { - topologies = [] - } - - return { - show: state.modals.changeTopologyModalVisible, - topologies, - } -} - -const mapDispatchToProps = (dispatch) => { - return { - onCreateTopology: (name) => { - if (name) { - dispatch(addTopology(name, undefined)) - } - dispatch(closeNewTopologyModal()) - }, - onDuplicateTopology: (name, id) => { - if (name) { - dispatch(addTopology(name, id)) - } - dispatch(closeNewTopologyModal()) - }, - onCancel: () => { - dispatch(closeNewTopologyModal()) - }, - } -} - -const NewTopologyModal = connect(mapStateToProps, mapDispatchToProps)(NewTopologyModalComponent) - -export default NewTopologyModal diff --git a/opendc-web/opendc-web-ui/src/containers/navigation/AppNavbarContainer.js b/opendc-web/opendc-web-ui/src/containers/navigation/AppNavbarContainer.js deleted file mode 100644 index 845d54e1..00000000 --- a/opendc-web/opendc-web-ui/src/containers/navigation/AppNavbarContainer.js +++ /dev/null @@ -1,12 +0,0 @@ -import { connect } from 'react-redux' -import AppNavbarComponent from '../../components/navigation/AppNavbarComponent' - -const mapStateToProps = (state) => { - return { - project: state.currentProjectId !== '-1' ? state.objects.project[state.currentProjectId] : undefined, - } -} - -const AppNavbarContainer = connect(mapStateToProps)(AppNavbarComponent) - -export default AppNavbarContainer diff --git a/opendc-web/opendc-web-ui/src/containers/projects/FilterLink.js b/opendc-web/opendc-web-ui/src/containers/projects/FilterLink.js deleted file mode 100644 index dfd6affe..00000000 --- a/opendc-web/opendc-web-ui/src/containers/projects/FilterLink.js +++ /dev/null @@ -1,19 +0,0 @@ -import { connect } from 'react-redux' -import { setAuthVisibilityFilter } from '../../actions/projects' -import FilterButton from '../../components/projects/FilterButton' - -const mapStateToProps = (state, ownProps) => { - return { - active: state.projectList.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/opendc-web/opendc-web-ui/src/containers/projects/NewProjectButtonContainer.js b/opendc-web/opendc-web-ui/src/containers/projects/NewProjectButtonContainer.js deleted file mode 100644 index ffd4a4a3..00000000 --- a/opendc-web/opendc-web-ui/src/containers/projects/NewProjectButtonContainer.js +++ /dev/null @@ -1,13 +0,0 @@ -import { connect } from 'react-redux' -import { openNewProjectModal } from '../../actions/modals/projects' -import NewProjectButtonComponent from '../../components/projects/NewProjectButtonComponent' - -const mapDispatchToProps = (dispatch) => { - return { - onClick: () => dispatch(openNewProjectModal()), - } -} - -const NewProjectButtonContainer = connect(undefined, mapDispatchToProps)(NewProjectButtonComponent) - -export default NewProjectButtonContainer diff --git a/opendc-web/opendc-web-ui/src/containers/projects/ProjectActions.js b/opendc-web/opendc-web-ui/src/containers/projects/ProjectActions.js deleted file mode 100644 index 8bcbb7fd..00000000 --- a/opendc-web/opendc-web-ui/src/containers/projects/ProjectActions.js +++ /dev/null @@ -1,20 +0,0 @@ -import { connect } from 'react-redux' -import { deleteProject } from '../../actions/projects' -import ProjectActionButtons from '../../components/projects/ProjectActionButtons' - -const mapStateToProps = (state, ownProps) => { - return { - projectId: ownProps.projectId, - } -} - -const mapDispatchToProps = (dispatch) => { - return { - onViewUsers: (id) => {}, // TODO implement user viewing - onDelete: (id) => dispatch(deleteProject(id)), - } -} - -const ProjectActions = connect(mapStateToProps, mapDispatchToProps)(ProjectActionButtons) - -export default ProjectActions diff --git a/opendc-web/opendc-web-ui/src/containers/projects/VisibleProjectAuthList.js b/opendc-web/opendc-web-ui/src/containers/projects/VisibleProjectAuthList.js deleted file mode 100644 index f0010540..00000000 --- a/opendc-web/opendc-web-ui/src/containers/projects/VisibleProjectAuthList.js +++ /dev/null @@ -1,32 +0,0 @@ -import { connect } from 'react-redux' -import ProjectList from '../../components/projects/ProjectAuthList' - -const getVisibleProjectAuths = (projectAuths, filter) => { - switch (filter) { - case 'SHOW_ALL': - return projectAuths - case 'SHOW_OWN': - return projectAuths.filter((projectAuth) => projectAuth.authorizationLevel === 'OWN') - case 'SHOW_SHARED': - return projectAuths.filter((projectAuth) => projectAuth.authorizationLevel !== 'OWN') - default: - return projectAuths - } -} - -const mapStateToProps = (state) => { - const denormalizedAuthorizations = state.projectList.authorizationsOfCurrentUser.map((authorizationIds) => { - const authorization = state.objects.authorization[authorizationIds] - authorization.user = state.objects.user[authorization.userId] - authorization.project = state.objects.project[authorization.projectId] - return authorization - }) - - return { - authorizations: getVisibleProjectAuths(denormalizedAuthorizations, state.projectList.authVisibilityFilter), - } -} - -const VisibleProjectAuthList = connect(mapStateToProps)(ProjectList) - -export default VisibleProjectAuthList diff --git a/opendc-web/opendc-web-ui/src/data/experiments.js b/opendc-web/opendc-web-ui/src/data/experiments.js new file mode 100644 index 00000000..a76ea53f --- /dev/null +++ b/opendc-web/opendc-web-ui/src/data/experiments.js @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2021 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. + */ + +import { useQuery } from 'react-query' +import { fetchTraces } from '../api/traces' +import { fetchSchedulers } from '../api/schedulers' + +/** + * Configure the query defaults for the experiment endpoints. + */ +export function configureExperimentClient(queryClient, auth) { + queryClient.setQueryDefaults('traces', { queryFn: () => fetchTraces(auth) }) + queryClient.setQueryDefaults('schedulers', { queryFn: () => fetchSchedulers(auth) }) +} + +/** + * Return the available traces to experiment with. + */ +export function useTraces() { + return useQuery('traces') +} + +/** + * Return the available schedulers to experiment with. + */ +export function useSchedulers() { + return useQuery('schedulers') +} diff --git a/opendc-web/opendc-web-ui/src/data/project.js b/opendc-web/opendc-web-ui/src/data/project.js new file mode 100644 index 00000000..9dcd8532 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/data/project.js @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2021 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. + */ + +import { useQuery } from 'react-query' +import { addProject, deleteProject, fetchProject, fetchProjects } from '../api/projects' +import { addPortfolio, deletePortfolio, fetchPortfolio, fetchPortfoliosOfProject } from '../api/portfolios' +import { addScenario, deleteScenario, fetchScenario, fetchScenariosOfPortfolio } from '../api/scenarios' + +/** + * Configure the query defaults for the project endpoints. + */ +export function configureProjectClient(queryClient, auth) { + queryClient.setQueryDefaults('projects', { + queryFn: ({ queryKey }) => (queryKey.length === 1 ? fetchProjects(auth) : fetchProject(auth, queryKey[1])), + }) + + queryClient.setMutationDefaults('addProject', { + mutationFn: (data) => addProject(auth, data), + onSuccess: async (result) => { + queryClient.setQueryData('projects', (old = []) => [...old, result]) + queryClient.setQueryData(['projects', result._id], result) + }, + }) + queryClient.setMutationDefaults('deleteProject', { + mutationFn: (id) => deleteProject(auth, id), + onSuccess: async (result) => { + queryClient.setQueryData('projects', (old = []) => old.filter((project) => project._id !== result._id)) + queryClient.removeQueries(['projects', result._id]) + }, + }) + + queryClient.setQueryDefaults('portfolios', { + queryFn: ({ queryKey }) => fetchPortfolio(auth, queryKey[1]), + }) + queryClient.setQueryDefaults('project-portfolios', { + queryFn: ({ queryKey }) => fetchPortfoliosOfProject(auth, queryKey[1]), + }) + queryClient.setMutationDefaults('addPortfolio', { + mutationFn: (data) => addPortfolio(auth, data), + onSuccess: async (result) => { + queryClient.setQueryData(['projects', result.projectId], (old) => ({ + ...old, + portfolioIds: [...old.portfolioIds, result._id], + })) + queryClient.setQueryData(['project-portfolios', result.projectId], (old = []) => [...old, result]) + queryClient.setQueryData(['portfolios', result._id], result) + }, + }) + queryClient.setMutationDefaults('deletePortfolio', { + mutationFn: (id) => deletePortfolio(auth, id), + onSuccess: async (result) => { + queryClient.setQueryData(['projects', result.projectId], (old) => ({ + ...old, + portfolioIds: old.portfolioIds.filter((id) => id !== result._id), + })) + queryClient.setQueryData(['project-portfolios', result.projectId], (old = []) => + old.filter((portfolio) => portfolio._id !== result._id) + ) + queryClient.removeQueries(['portfolios', result._id]) + }, + }) + + queryClient.setQueryDefaults('scenarios', { + queryFn: ({ queryKey }) => fetchScenario(auth, queryKey[1]), + }) + queryClient.setQueryDefaults('portfolio-scenarios', { + queryFn: ({ queryKey }) => fetchScenariosOfPortfolio(auth, queryKey[1]), + }) + queryClient.setMutationDefaults('addScenario', { + mutationFn: (data) => addScenario(auth, data), + onSuccess: async (result) => { + // Register updated scenario in cache + queryClient.setQueryData(['scenarios', result._id], result) + queryClient.setQueryData(['portfolio-scenarios', result.portfolioId], (old = []) => [...old, result]) + + // Add scenario id to portfolio + queryClient.setQueryData(['portfolios', result.portfolioId], (old) => ({ + ...old, + scenarioIds: [...old.scenarioIds, result._id], + })) + }, + }) + queryClient.setMutationDefaults('deleteScenario', { + mutationFn: (id) => deleteScenario(auth, id), + onSuccess: async (result) => { + queryClient.setQueryData(['portfolios', result.portfolioId], (old) => ({ + ...old, + scenarioIds: old.scenarioIds.filter((id) => id !== result._id), + })) + queryClient.setQueryData(['portfolio-scenarios', result.portfolioId], (old = []) => + old.filter((scenario) => scenario._id !== result._id) + ) + queryClient.removeQueries(['scenarios', result._id]) + }, + }) +} + +/** + * Return the available projects. + */ +export function useProjects(options = {}) { + return useQuery('projects', options) +} + +/** + * Return the project with the specified identifier. + */ +export function useProject(projectId, options = {}) { + return useQuery(['projects', projectId], { enabled: !!projectId, ...options }) +} + +/** + * Return the portfolio with the specified identifier. + */ +export function usePortfolio(portfolioId, options = {}) { + return useQuery(['portfolios', portfolioId], { enabled: !!portfolioId, ...options }) +} + +/** + * Return the portfolios of the specified project. + */ +export function useProjectPortfolios(projectId, options = {}) { + return useQuery(['project-portfolios', projectId], { enabled: !!projectId, ...options }) +} + +/** + * Return the scenarios of the specified portfolio. + */ +export function usePortfolioScenarios(portfolioId, options = {}) { + return useQuery(['portfolio-scenarios', portfolioId], { enabled: !!portfolioId, ...options }) +} diff --git a/opendc-web/opendc-web-ui/src/data/query.js b/opendc-web/opendc-web-ui/src/data/query.js new file mode 100644 index 00000000..59eaa684 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/data/query.js @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2021 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. + */ + +import { useMemo } from 'react' +import { QueryClient } from 'react-query' +import { useAuth } from '../auth' +import { configureExperimentClient } from './experiments' +import { configureProjectClient } from './project' +import { configureTopologyClient } from './topology' + +let queryClient + +function createQueryClient(auth) { + const client = new QueryClient() + configureProjectClient(client, auth) + configureExperimentClient(client, auth) + configureTopologyClient(client, auth) + return client +} + +function initializeQueryClient(auth) { + const _queryClient = queryClient ?? createQueryClient(auth) + + // For SSG and SSR always create a new query client + if (typeof window === 'undefined') return _queryClient + // Create the query client once in the client + if (!queryClient) queryClient = _queryClient + + return _queryClient +} + +/** + * Obtain a cached query client. + */ +export function useNewQueryClient() { + const auth = useAuth() + return useMemo(() => initializeQueryClient(auth), []) // eslint-disable-line react-hooks/exhaustive-deps +} diff --git a/opendc-web/opendc-web-ui/src/data/topology.js b/opendc-web/opendc-web-ui/src/data/topology.js new file mode 100644 index 00000000..e068ed8e --- /dev/null +++ b/opendc-web/opendc-web-ui/src/data/topology.js @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2021 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. + */ + +import { useQuery } from 'react-query' +import { addTopology, deleteTopology, fetchTopologiesOfProject, fetchTopology, updateTopology } from '../api/topologies' + +/** + * Configure the query defaults for the topology endpoints. + */ +export function configureTopologyClient(queryClient, auth) { + queryClient.setQueryDefaults('topologies', { queryFn: ({ queryKey }) => fetchTopology(auth, queryKey[1]) }) + queryClient.setQueryDefaults('project-topologies', { + queryFn: ({ queryKey }) => fetchTopologiesOfProject(auth, queryKey[1]), + }) + + queryClient.setMutationDefaults('addTopology', { + mutationFn: (data) => addTopology(auth, data), + onSuccess: async (result) => { + queryClient.setQueryData(['projects', result.projectId], (old) => ({ + ...old, + topologyIds: [...old.topologyIds, result._id], + })) + queryClient.setQueryData(['project-topologies', result.projectId], (old = []) => [...old, result]) + queryClient.setQueryData(['topologies', result._id], result) + }, + }) + queryClient.setMutationDefaults('updateTopology', { + mutationFn: (data) => updateTopology(auth, data), + onSuccess: (result) => queryClient.setQueryData(['topologies', result._id], result), + }) + queryClient.setMutationDefaults('deleteTopology', { + mutationFn: (id) => deleteTopology(auth, id), + onSuccess: async (result) => { + queryClient.setQueryData(['projects', result.projectId], (old) => ({ + ...old, + topologyIds: old.topologyIds.filter((id) => id !== result._id), + })) + queryClient.setQueryData(['project-topologies', result.projectId], (old = []) => + old.filter((topology) => topology._id !== result._id) + ) + queryClient.removeQueries(['topologies', result._id]) + }, + }) +} + +/** + * Return the current active topology. + */ +export function useTopology(topologyId, options = {}) { + return useQuery(['topologies', topologyId], { enabled: !!topologyId, ...options }) +} + +/** + * Return the topologies of the specified project. + */ +export function useProjectTopologies(projectId, options = {}) { + return useQuery(['project-topologies', projectId], { enabled: !!projectId, ...options }) +} diff --git a/opendc-web/opendc-web-ui/src/hotkeys.js b/opendc-web/opendc-web-ui/src/hotkeys.js new file mode 100644 index 00000000..1c4d2621 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/hotkeys.js @@ -0,0 +1,6 @@ +export const KeymapConfiguration = { + MOVE_LEFT: ['a', 'left'], + MOVE_RIGHT: ['d', 'right'], + MOVE_UP: ['w', 'up'], + MOVE_DOWN: ['s', 'down'], +} diff --git a/opendc-web/opendc-web-ui/src/index.js b/opendc-web/opendc-web-ui/src/index.js deleted file mode 100644 index ae3a5ddc..00000000 --- a/opendc-web/opendc-web-ui/src/index.js +++ /dev/null @@ -1,32 +0,0 @@ -import React from 'react' -import ReactDOM from 'react-dom' -import * as Sentry from '@sentry/react' -import { Integrations } from '@sentry/tracing' -import { Provider } from 'react-redux' -import { setupSocketConnection } from './api/socket' -import './index.sass' -import Routes from './routes' -import config from './config' -import configureStore from './store/configure-store' - -setupSocketConnection(() => { - const store = configureStore() - - // Initialize Sentry if the user has configured a DSN - const dsn = config['SENTRY_DSN'] - if (dsn) { - Sentry.init({ - environment: process.env.NODE_ENV, - dsn: dsn, - integrations: [new Integrations.BrowserTracing()], - tracesSampleRate: 0.1, - }) - } - - ReactDOM.render( - <Provider store={store}> - <Routes /> - </Provider>, - document.getElementById('root') - ) -}) diff --git a/opendc-web/opendc-web-ui/src/index.sass b/opendc-web/opendc-web-ui/src/index.sass deleted file mode 100644 index a78f7a19..00000000 --- a/opendc-web/opendc-web-ui/src/index.sass +++ /dev/null @@ -1,52 +0,0 @@ -@import "~bootstrap/scss/bootstrap" - -@import ./style-globals/_mixins.sass -@import ./style-globals/_variables.sass - -html, body, #root - margin: 0 - padding: 0 - width: 100% - height: 100% - - font-family: Roboto, Helvetica, Verdana, sans-serif - background: #eee - - // Scroll padding for top navbar - scroll-padding-top: 60px - -.full-height - position: relative - height: 100% !important - -.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 - +clickable - -.btn-circle - +border-radius(50%) - -a, a:hover - text-decoration: none - -.app-page-container - padding-left: $side-bar-width - padding-top: 15px - -.w-70 - width: 70% !important diff --git a/opendc-web/opendc-web-ui/src/pages/404.js b/opendc-web/opendc-web-ui/src/pages/404.js new file mode 100644 index 00000000..0939bc56 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/pages/404.js @@ -0,0 +1,38 @@ +import React from 'react' +import Head from 'next/head' +import { AppPage } from '../components/AppPage' +import { + Bullseye, + EmptyState, + EmptyStateBody, + EmptyStateIcon, + PageSection, + PageSectionVariants, + Title, +} from '@patternfly/react-core' +import { UnknownIcon } from '@patternfly/react-icons' + +const NotFound = () => { + return ( + <AppPage> + <Head> + <title>Page Not Found - OpenDC</title> + </Head> + <PageSection variant={PageSectionVariants.light}> + <Bullseye> + <EmptyState> + <EmptyStateIcon variant="container" component={UnknownIcon} /> + <Title size="lg" headingLevel="h4"> + 404: That page does not exist + </Title> + <EmptyStateBody> + The requested page is not found. Try refreshing the page if it was recently added. + </EmptyStateBody> + </EmptyState> + </Bullseye> + </PageSection> + </AppPage> + ) +} + +export default NotFound diff --git a/opendc-web/opendc-web-ui/src/pages/App.js b/opendc-web/opendc-web-ui/src/pages/App.js deleted file mode 100644 index cbc805b8..00000000 --- a/opendc-web/opendc-web-ui/src/pages/App.js +++ /dev/null @@ -1,137 +0,0 @@ -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 { openPortfolioSucceeded } from '../actions/portfolios' -import { openProjectSucceeded } from '../actions/projects' -import ToolPanelComponent from '../components/app/map/controls/ToolPanelComponent' -import LoadingScreen from '../components/app/map/LoadingScreen' -import ScaleIndicatorContainer from '../containers/app/map/controls/ScaleIndicatorContainer' -import MapStage from '../containers/app/map/MapStage' -import TopologySidebarContainer from '../containers/app/sidebars/topology/TopologySidebarContainer' -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' -import NewTopologyModal from '../containers/modals/NewTopologyModal' -import AppNavbarContainer from '../containers/navigation/AppNavbarContainer' -import ProjectSidebarContainer from '../containers/app/sidebars/project/ProjectSidebarContainer' -import { openScenarioSucceeded } from '../actions/scenarios' -import NewPortfolioModal from '../containers/modals/NewPortfolioModal' -import NewScenarioModal from '../containers/modals/NewScenarioModal' -import PortfolioResultsContainer from '../containers/app/results/PortfolioResultsContainer' - -const shortcutManager = new ShortcutManager(KeymapConfiguration) - -class AppComponent extends React.Component { - static propTypes = { - projectId: PropTypes.string.isRequired, - portfolioId: PropTypes.string, - scenarioId: PropTypes.string, - projectName: PropTypes.string, - } - static childContextTypes = { - shortcuts: PropTypes.object.isRequired, - } - - componentDidMount() { - if (this.props.scenarioId) { - this.props.openScenarioSucceeded(this.props.projectId, this.props.portfolioId, this.props.scenarioId) - } else if (this.props.portfolioId) { - this.props.openPortfolioSucceeded(this.props.projectId, this.props.portfolioId) - } else { - this.props.openProjectSucceeded(this.props.projectId) - } - } - - getChildContext() { - return { - shortcuts: shortcutManager, - } - } - - render() { - const constructionElements = this.props.topologyIsLoading ? ( - <div className="full-height d-flex align-items-center justify-content-center"> - <LoadingScreen /> - </div> - ) : ( - <div className="full-height"> - <MapStage /> - <ScaleIndicatorContainer /> - <ToolPanelComponent /> - <ProjectSidebarContainer /> - <TopologySidebarContainer /> - </div> - ) - - const portfolioElements = ( - <div className="full-height app-page-container"> - <ProjectSidebarContainer /> - <div className="container-fluid full-height"> - <PortfolioResultsContainer /> - </div> - </div> - ) - - const scenarioElements = ( - <div className="full-height app-page-container"> - <ProjectSidebarContainer /> - <div className="container-fluid full-height"> - <h2>Scenario loading</h2> - </div> - </div> - ) - - return ( - <DocumentTitle - title={this.props.projectName ? this.props.projectName + ' - OpenDC' : 'Simulation - OpenDC'} - > - <div className="page-container full-height"> - <AppNavbarContainer fullWidth={true} /> - {this.props.scenarioId - ? scenarioElements - : this.props.portfolioId - ? portfolioElements - : constructionElements} - <NewTopologyModal /> - <NewPortfolioModal /> - <NewScenarioModal /> - <EditRoomNameModal /> - <DeleteRoomModal /> - <EditRackNameModal /> - <DeleteRackModal /> - <DeleteMachineModal /> - </div> - </DocumentTitle> - ) - } -} - -const mapStateToProps = (state) => { - let projectName = undefined - if (state.currentProjectId !== '-1' && state.objects.project[state.currentProjectId]) { - projectName = state.objects.project[state.currentProjectId].name - } - - return { - topologyIsLoading: state.currentTopologyId === '-1', - projectName, - } -} - -const mapDispatchToProps = (dispatch) => { - return { - openProjectSucceeded: (projectId) => dispatch(openProjectSucceeded(projectId)), - openPortfolioSucceeded: (projectId, portfolioId) => dispatch(openPortfolioSucceeded(projectId, portfolioId)), - openScenarioSucceeded: (projectId, portfolioId, scenarioId) => - dispatch(openScenarioSucceeded(projectId, portfolioId, scenarioId)), - } -} - -const App = connect(mapStateToProps, mapDispatchToProps)(AppComponent) - -export default App diff --git a/opendc-web/opendc-web-ui/src/pages/Home.js b/opendc-web/opendc-web-ui/src/pages/Home.js deleted file mode 100644 index 6fc940c0..00000000 --- a/opendc-web/opendc-web-ui/src/pages/Home.js +++ /dev/null @@ -1,33 +0,0 @@ -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 './Home.sass' - -function Home() { - 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/opendc-web/opendc-web-ui/src/pages/Home.sass b/opendc-web/opendc-web-ui/src/pages/Home.sass deleted file mode 100644 index 79cb9698..00000000 --- a/opendc-web/opendc-web-ui/src/pages/Home.sass +++ /dev/null @@ -1,9 +0,0 @@ -.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/opendc-web/opendc-web-ui/src/pages/NotFound.js b/opendc-web/opendc-web-ui/src/pages/NotFound.js deleted file mode 100644 index 72be7342..00000000 --- a/opendc-web/opendc-web-ui/src/pages/NotFound.js +++ /dev/null @@ -1,14 +0,0 @@ -import React from 'react' -import DocumentTitle from 'react-document-title' -import TerminalWindow from '../components/not-found/TerminalWindow' -import './NotFound.sass' - -const NotFound = () => ( - <DocumentTitle title="Page Not Found - OpenDC"> - <div className="not-found-backdrop"> - <TerminalWindow /> - </div> - </DocumentTitle> -) - -export default NotFound diff --git a/opendc-web/opendc-web-ui/src/pages/NotFound.sass b/opendc-web/opendc-web-ui/src/pages/NotFound.sass deleted file mode 100644 index 59231f7a..00000000 --- a/opendc-web/opendc-web-ui/src/pages/NotFound.sass +++ /dev/null @@ -1,11 +0,0 @@ -.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/opendc-web/opendc-web-ui/src/pages/Profile.js b/opendc-web/opendc-web-ui/src/pages/Profile.js deleted file mode 100644 index 0d94b519..00000000 --- a/opendc-web/opendc-web-ui/src/pages/Profile.js +++ /dev/null @@ -1,35 +0,0 @@ -import React from 'react' -import DocumentTitle from 'react-document-title' -import { connect } from 'react-redux' -import { openDeleteProfileModal } from '../actions/modals/profile' -import DeleteProfileModal from '../containers/modals/DeleteProfileModal' -import AppNavbarContainer from '../containers/navigation/AppNavbarContainer' - -const ProfileContainer = ({ onDelete }) => ( - <DocumentTitle title="My Profile - OpenDC"> - <div className="full-height"> - <AppNavbarContainer 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 project info that is associated with you (projects 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/opendc-web/opendc-web-ui/src/pages/Projects.js b/opendc-web/opendc-web-ui/src/pages/Projects.js deleted file mode 100644 index bb54aaa5..00000000 --- a/opendc-web/opendc-web-ui/src/pages/Projects.js +++ /dev/null @@ -1,43 +0,0 @@ -import React from 'react' -import DocumentTitle from 'react-document-title' -import { connect } from 'react-redux' -import { openNewProjectModal } from '../actions/modals/projects' -import { fetchAuthorizationsOfCurrentUser } from '../actions/users' -import ProjectFilterPanel from '../components/projects/FilterPanel' -import NewProjectModal from '../containers/modals/NewProjectModal' -import NewProjectButtonContainer from '../containers/projects/NewProjectButtonContainer' -import VisibleProjectList from '../containers/projects/VisibleProjectAuthList' -import AppNavbarContainer from '../containers/navigation/AppNavbarContainer' - -class ProjectsContainer extends React.Component { - componentDidMount() { - this.props.fetchAuthorizationsOfCurrentUser() - } - - render() { - return ( - <DocumentTitle title="My Projects - OpenDC"> - <div className="full-height"> - <AppNavbarContainer fullWidth={false} /> - <div className="container text-page-container full-height"> - <ProjectFilterPanel /> - <VisibleProjectList /> - <NewProjectButtonContainer /> - </div> - <NewProjectModal /> - </div> - </DocumentTitle> - ) - } -} - -const mapDispatchToProps = (dispatch) => { - return { - fetchAuthorizationsOfCurrentUser: () => dispatch(fetchAuthorizationsOfCurrentUser()), - openNewProjectModal: () => dispatch(openNewProjectModal()), - } -} - -const Projects = connect(undefined, mapDispatchToProps)(ProjectsContainer) - -export default Projects diff --git a/opendc-web/opendc-web-ui/src/pages/_app.js b/opendc-web/opendc-web-ui/src/pages/_app.js new file mode 100644 index 00000000..900ff405 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/pages/_app.js @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2021 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. + */ + +import PropTypes from 'prop-types' +import Head from 'next/head' +import { Provider } from 'react-redux' +import { useNewQueryClient } from '../data/query' +import { useStore } from '../redux' +import { AuthProvider, useRequireAuth } from '../auth' +import * as Sentry from '@sentry/react' +import { Integrations } from '@sentry/tracing' +import { QueryClientProvider } from 'react-query' + +import '@patternfly/react-core/dist/styles/base.css' +import '@patternfly/react-styles/css/utilities/Alignment/alignment.css' +import '@patternfly/react-styles/css/utilities/BackgroundColor/BackgroundColor.css' +import '@patternfly/react-styles/css/utilities/BoxShadow/box-shadow.css' +import '@patternfly/react-styles/css/utilities/Display/display.css' +import '@patternfly/react-styles/css/utilities/Flex/flex.css' +import '@patternfly/react-styles/css/utilities/Float/float.css' +import '@patternfly/react-styles/css/utilities/Sizing/sizing.css' +import '@patternfly/react-styles/css/utilities/Spacing/spacing.css' +import '@patternfly/react-styles/css/utilities/Text/text.css' +import '@patternfly/react-styles/css/components/InlineEdit/inline-edit.css' +import '../style/index.scss' + +// This setup is necessary to forward the Auth0 context to the Redux context +function Inner({ Component, pageProps }) { + // Force user to be authorized + useRequireAuth() + + const queryClient = useNewQueryClient() + const store = useStore(pageProps.initialReduxState, { queryClient }) + return ( + <QueryClientProvider client={queryClient}> + <Provider store={store}> + <Component {...pageProps} /> + </Provider> + </QueryClientProvider> + ) +} + +Inner.propTypes = { + Component: PropTypes.func, + pageProps: PropTypes.shape({ + initialReduxState: PropTypes.object, + }).isRequired, +} + +const dsn = process.env.NEXT_PUBLIC_SENTRY_DSN +// Initialize Sentry if the user has configured a DSN +if (process.browser && dsn) { + if (dsn) { + Sentry.init({ + environment: process.env.NODE_ENV, + dsn: dsn, + integrations: [new Integrations.BrowserTracing()], + tracesSampleRate: 0.1, + }) + } +} + +export default function App(props) { + return ( + <> + <Head> + <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" /> + <meta name="theme-color" content="#00A6D6" /> + </Head> + <Sentry.ErrorBoundary fallback={'An error has occurred'}> + <AuthProvider> + <Inner {...props} /> + </AuthProvider> + </Sentry.ErrorBoundary> + </> + ) +} diff --git a/opendc-web/opendc-web-ui/src/pages/_document.js b/opendc-web/opendc-web-ui/src/pages/_document.js new file mode 100644 index 00000000..51d8d3e0 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/pages/_document.js @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2021 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. + */ + +import Document, { Html, Head, Main, NextScript } from 'next/document' + +class OpenDCDocument extends Document { + render() { + return ( + <Html lang="en"> + <Head> + <meta charSet="utf-8" /> + <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" /> + + {/* CDN Dependencies */} + <link + href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap" + rel="stylesheet" + /> + + {/* Google Analytics */} + <script async src="https://www.googletagmanager.com/gtag/js?id=UA-84285092-3" /> + <script + dangerouslySetInnerHTML={{ + __html: ` + window.dataLayer = window.dataLayer || []; + function gtag(){dataLayer.push(arguments);} + gtag('js', new Date()); + gtag('config', 'UA-84285092-3'); + `, + }} + /> + </Head> + <body> + <Main /> + <NextScript /> + </body> + </Html> + ) + } +} + +export default OpenDCDocument diff --git a/opendc-web/opendc-web-ui/src/pages/logout.js b/opendc-web/opendc-web-ui/src/pages/logout.js new file mode 100644 index 00000000..38d5968e --- /dev/null +++ b/opendc-web/opendc-web-ui/src/pages/logout.js @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2021 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. + */ + +import React from 'react' +import Head from 'next/head' +import { AppPage } from '../components/AppPage' +import { PageSection, PageSectionVariants } from '@patternfly/react-core' + +function Logout() { + return ( + <AppPage> + <Head> + <title>Logged Out - OpenDC</title> + </Head> + <PageSection variant={PageSectionVariants.light}>Logged out successfully</PageSection> + </AppPage> + ) +} + +export default Logout diff --git a/opendc-web/opendc-web-ui/src/pages/projects/[project]/index.js b/opendc-web/opendc-web-ui/src/pages/projects/[project]/index.js new file mode 100644 index 00000000..c07a2c31 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/pages/projects/[project]/index.js @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2021 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. + */ + +import { useRouter } from 'next/router' +import ContextSelectionSection from '../../../components/context/ContextSelectionSection' +import ProjectOverview from '../../../components/projects/ProjectOverview' +import ProjectSelector from '../../../components/context/ProjectSelector' +import { useProject } from '../../../data/project' +import { AppPage } from '../../../components/AppPage' +import Head from 'next/head' +import { + Breadcrumb, + BreadcrumbItem, + PageSection, + PageSectionVariants, + Skeleton, + Text, + TextContent, +} from '@patternfly/react-core' +import BreadcrumbLink from '../../../components/util/BreadcrumbLink' + +function Project() { + const router = useRouter() + const { project: projectId } = router.query + + const { data: project } = useProject(projectId) + + const breadcrumb = ( + <Breadcrumb> + <BreadcrumbItem to="/projects" component={BreadcrumbLink}> + Projects + </BreadcrumbItem> + <BreadcrumbItem to={`/projects/${projectId}`} component={BreadcrumbLink} isActive> + Project details + </BreadcrumbItem> + </Breadcrumb> + ) + + const contextSelectors = ( + <ContextSelectionSection> + <ProjectSelector projectId={projectId} /> + </ContextSelectionSection> + ) + + return ( + <AppPage breadcrumb={breadcrumb} contextSelectors={contextSelectors}> + <Head> + <title>{project?.name ?? 'Project'} - OpenDC</title> + </Head> + <PageSection variant={PageSectionVariants.light}> + <TextContent> + <Text component="h1"> + {project?.name ?? <Skeleton width="15%" screenreaderText="Loading project" />} + </Text> + </TextContent> + </PageSection> + <PageSection isFilled> + <ProjectOverview projectId={projectId} /> + </PageSection> + </AppPage> + ) +} + +export default Project diff --git a/opendc-web/opendc-web-ui/src/pages/projects/[project]/portfolios/[portfolio].js b/opendc-web/opendc-web-ui/src/pages/projects/[project]/portfolios/[portfolio].js new file mode 100644 index 00000000..d1533d98 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/pages/projects/[project]/portfolios/[portfolio].js @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2021 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. + */ + +import { useRouter } from 'next/router' +import Head from 'next/head' +import React, { useRef } from 'react' +import { + Breadcrumb, + BreadcrumbItem, + Divider, + PageSection, + PageSectionVariants, + Tab, + TabContent, + Tabs, + TabTitleText, + Text, + TextContent, +} from '@patternfly/react-core' +import { AppPage } from '../../../../components/AppPage' +import ContextSelectionSection from '../../../../components/context/ContextSelectionSection' +import PortfolioSelector from '../../../../components/context/PortfolioSelector' +import ProjectSelector from '../../../../components/context/ProjectSelector' +import BreadcrumbLink from '../../../../components/util/BreadcrumbLink' +import PortfolioOverview from '../../../../components/portfolios/PortfolioOverview' +import PortfolioResults from '../../../../components/portfolios/PortfolioResults' + +/** + * Page that displays the results in a portfolio. + */ +function Portfolio() { + const router = useRouter() + const { project: projectId, portfolio: portfolioId } = router.query + + const overviewRef = useRef(null) + const resultsRef = useRef(null) + + const breadcrumb = ( + <Breadcrumb> + <BreadcrumbItem to="/projects" component={BreadcrumbLink}> + Projects + </BreadcrumbItem> + <BreadcrumbItem to={`/projects/${projectId}`} component={BreadcrumbLink}> + Project details + </BreadcrumbItem> + <BreadcrumbItem to={`/projects/${projectId}/portfolios/${portfolioId}`} component={BreadcrumbLink} isActive> + Portfolio + </BreadcrumbItem> + </Breadcrumb> + ) + + const contextSelectors = ( + <ContextSelectionSection> + <ProjectSelector projectId={projectId} /> + <PortfolioSelector projectId={projectId} portfolioId={portfolioId} /> + </ContextSelectionSection> + ) + + return ( + <AppPage breadcrumb={breadcrumb} contextSelectors={contextSelectors}> + <Head> + <title>Portfolio - OpenDC</title> + </Head> + <PageSection variant={PageSectionVariants.light}> + <TextContent> + <Text component="h1">Portfolio</Text> + </TextContent> + </PageSection> + <PageSection type="none" variant={PageSectionVariants.light} className="pf-c-page__main-tabs" sticky="top"> + <Divider component="div" /> + <Tabs defaultActiveKey={0} className="pf-m-page-insets"> + <Tab + eventKey={0} + title={<TabTitleText>Overview</TabTitleText>} + tabContentId="overview" + tabContentRef={overviewRef} + /> + <Tab + eventKey={1} + title={<TabTitleText>Results</TabTitleText>} + tabContentId="results" + tabContentRef={resultsRef} + /> + </Tabs> + </PageSection> + <PageSection isFilled> + <TabContent eventKey={0} id="overview" ref={overviewRef} aria-label="Overview tab"> + <PortfolioOverview portfolioId={portfolioId} /> + </TabContent> + <TabContent eventKey={1} id="results" ref={resultsRef} aria-label="Results tab" hidden> + <PortfolioResults portfolioId={portfolioId} /> + </TabContent> + </PageSection> + </AppPage> + ) +} + +export default Portfolio diff --git a/opendc-web/opendc-web-ui/src/pages/projects/[project]/topologies/[topology].js b/opendc-web/opendc-web-ui/src/pages/projects/[project]/topologies/[topology].js new file mode 100644 index 00000000..858f9b16 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/pages/projects/[project]/topologies/[topology].js @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2021 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. + */ + +import { useRouter } from 'next/router' +import ContextSelectionSection from '../../../../components/context/ContextSelectionSection' +import ProjectSelector from '../../../../components/context/ProjectSelector' +import TopologySelector from '../../../../components/context/TopologySelector' +import TopologyOverview from '../../../../components/topologies/TopologyOverview' +import { useProject } from '../../../../data/project' +import { useDispatch } from 'react-redux' +import React, { useEffect, useState } from 'react' +import Head from 'next/head' +import { AppPage } from '../../../../components/AppPage' +import { + Breadcrumb, + BreadcrumbItem, + Divider, + PageSection, + PageSectionVariants, + Tab, + TabContent, + Tabs, + TabTitleText, + Text, + TextContent, +} from '@patternfly/react-core' +import BreadcrumbLink from '../../../../components/util/BreadcrumbLink' +import TopologyMap from '../../../../components/topologies/TopologyMap' +import { goToRoom } from '../../../../redux/actions/interaction-level' +import { openTopology } from '../../../../redux/actions/topologies' + +/** + * Page that displays a datacenter topology. + */ +function Topology() { + const router = useRouter() + const { project: projectId, topology: topologyId } = router.query + + const { data: project } = useProject(projectId) + + const dispatch = useDispatch() + useEffect(() => { + if (topologyId) { + dispatch(openTopology(topologyId)) + } + }, [topologyId, dispatch]) + + const [activeTab, setActiveTab] = useState('overview') + + const breadcrumb = ( + <Breadcrumb> + <BreadcrumbItem to="/projects" component={BreadcrumbLink}> + Projects + </BreadcrumbItem> + <BreadcrumbItem to={`/projects/${projectId}`} component={BreadcrumbLink}> + Project details + </BreadcrumbItem> + <BreadcrumbItem to={`/projects/${projectId}/topologies/${topologyId}`} component={BreadcrumbLink} isActive> + Topology + </BreadcrumbItem> + </Breadcrumb> + ) + + const contextSelectors = ( + <ContextSelectionSection> + <ProjectSelector projectId={projectId} /> + <TopologySelector projectId={projectId} topologyId={topologyId} /> + </ContextSelectionSection> + ) + + return ( + <AppPage breadcrumb={breadcrumb} contextSelectors={contextSelectors}> + <Head> + <title>{project?.name ?? 'Topologies'} - OpenDC</title> + </Head> + <PageSection variant={PageSectionVariants.light}> + <TextContent> + <Text component="h1">Topology</Text> + </TextContent> + </PageSection> + <PageSection type="none" variant={PageSectionVariants.light} className="pf-c-page__main-tabs" sticky="top"> + <Divider component="div" /> + <Tabs + activeKey={activeTab} + onSelect={(_, tabIndex) => setActiveTab(tabIndex)} + className="pf-m-page-insets" + > + <Tab eventKey="overview" title={<TabTitleText>Overview</TabTitleText>} tabContentId="overview" /> + <Tab + eventKey="floor-plan" + title={<TabTitleText>Floor Plan</TabTitleText>} + tabContentId="floor-plan" + /> + </Tabs> + </PageSection> + <PageSection padding={activeTab === 'floor-plan' && { default: 'noPadding' }} isFilled> + <TabContent id="overview" aria-label="Overview tab" hidden={activeTab !== 'overview'}> + <TopologyOverview + topologyId={topologyId} + onSelect={(type, obj) => { + if (type === 'room') { + dispatch(goToRoom(obj._id)) + setActiveTab('floor-plan') + } + }} + /> + </TabContent> + <TabContent + id="floor-plan" + aria-label="Floor Plan tab" + className="pf-u-h-100" + hidden={activeTab !== 'floor-plan'} + > + <TopologyMap /> + </TabContent> + </PageSection> + </AppPage> + ) +} + +export default Topology diff --git a/opendc-web/opendc-web-ui/src/pages/projects/index.js b/opendc-web/opendc-web-ui/src/pages/projects/index.js new file mode 100644 index 00000000..eb77701e --- /dev/null +++ b/opendc-web/opendc-web-ui/src/pages/projects/index.js @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2021 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. + */ + +import React, { useMemo, useState } from 'react' +import Head from 'next/head' +import ProjectFilterPanel from '../../components/projects/FilterPanel' +import { useAuth } from '../../auth' +import { AppPage } from '../../components/AppPage' +import { PageSection, PageSectionVariants, Text, TextContent } from '@patternfly/react-core' +import { useProjects } from '../../data/project' +import ProjectTable from '../../components/projects/ProjectTable' +import { useMutation } from 'react-query' +import NewProject from '../../components/projects/NewProject' + +const getVisibleProjects = (projects, filter, userId) => { + switch (filter) { + case 'SHOW_ALL': + return projects + case 'SHOW_OWN': + return projects.filter((project) => + project.authorizations.some((a) => a.userId === userId && a.level === 'OWN') + ) + case 'SHOW_SHARED': + return projects.filter((project) => + project.authorizations.some((a) => a.userId === userId && a.level !== 'OWN') + ) + default: + return projects + } +} + +function Projects() { + const { user } = useAuth() + const { status, data: projects } = useProjects() + const [filter, setFilter] = useState('SHOW_ALL') + const visibleProjects = useMemo(() => getVisibleProjects(projects ?? [], filter, user?.sub), [ + projects, + filter, + user?.sub, + ]) + + const { mutate: deleteProject } = useMutation('deleteProject') + + return ( + <AppPage> + <Head> + <title>My Projects - OpenDC</title> + </Head> + <PageSection variant={PageSectionVariants.light}> + <TextContent> + <Text component="h1">My Projects</Text> + </TextContent> + </PageSection> + <PageSection variant={PageSectionVariants.light} isFilled> + <ProjectFilterPanel onSelect={setFilter} activeFilter={filter} /> + <ProjectTable + status={status} + isFiltering={filter !== 'SHOW_ALL'} + projects={visibleProjects} + onDelete={(project) => deleteProject(project._id)} + /> + <NewProject /> + </PageSection> + </AppPage> + ) +} + +export default Projects diff --git a/opendc-web/opendc-web-ui/src/reducers/auth.js b/opendc-web/opendc-web-ui/src/reducers/auth.js deleted file mode 100644 index 399a4b10..00000000 --- a/opendc-web/opendc-web-ui/src/reducers/auth.js +++ /dev/null @@ -1,12 +0,0 @@ -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/opendc-web/opendc-web-ui/src/reducers/current-ids.js b/opendc-web/opendc-web-ui/src/reducers/current-ids.js deleted file mode 100644 index 9b46aa60..00000000 --- a/opendc-web/opendc-web-ui/src/reducers/current-ids.js +++ /dev/null @@ -1,54 +0,0 @@ -import { OPEN_PORTFOLIO_SUCCEEDED, SET_CURRENT_PORTFOLIO } from '../actions/portfolios' -import { OPEN_PROJECT_SUCCEEDED } from '../actions/projects' -import { SET_CURRENT_TOPOLOGY } from '../actions/topology/building' -import { OPEN_SCENARIO_SUCCEEDED, SET_CURRENT_SCENARIO } from '../actions/scenarios' - -export function currentTopologyId(state = '-1', action) { - switch (action.type) { - case SET_CURRENT_TOPOLOGY: - return action.topologyId - default: - return state - } -} - -export function currentProjectId(state = '-1', action) { - switch (action.type) { - case OPEN_PROJECT_SUCCEEDED: - return action.id - case OPEN_PORTFOLIO_SUCCEEDED: - case OPEN_SCENARIO_SUCCEEDED: - return action.projectId - default: - return state - } -} - -export function currentPortfolioId(state = '-1', action) { - switch (action.type) { - case OPEN_PORTFOLIO_SUCCEEDED: - case SET_CURRENT_PORTFOLIO: - case SET_CURRENT_SCENARIO: - return action.portfolioId - case OPEN_SCENARIO_SUCCEEDED: - return action.portfolioId - case OPEN_PROJECT_SUCCEEDED: - case SET_CURRENT_TOPOLOGY: - return '-1' - default: - return state - } -} -export function currentScenarioId(state = '-1', action) { - switch (action.type) { - case OPEN_SCENARIO_SUCCEEDED: - case SET_CURRENT_SCENARIO: - return action.scenarioId - case OPEN_PORTFOLIO_SUCCEEDED: - case SET_CURRENT_TOPOLOGY: - case OPEN_PROJECT_SUCCEEDED: - return '-1' - default: - return state - } -} diff --git a/opendc-web/opendc-web-ui/src/reducers/index.js b/opendc-web/opendc-web-ui/src/reducers/index.js deleted file mode 100644 index 787d5a74..00000000 --- a/opendc-web/opendc-web-ui/src/reducers/index.js +++ /dev/null @@ -1,25 +0,0 @@ -import { combineReducers } from 'redux' -import { auth } from './auth' -import { construction } from './construction-mode' -import { currentPortfolioId, currentProjectId, currentScenarioId, currentTopologyId } from './current-ids' -import { interactionLevel } from './interaction-level' -import { map } from './map' -import { modals } from './modals' -import { objects } from './objects' -import { projectList } from './project-list' - -const rootReducer = combineReducers({ - objects, - modals, - projectList, - construction, - map, - currentProjectId, - currentTopologyId, - currentPortfolioId, - currentScenarioId, - interactionLevel, - auth, -}) - -export default rootReducer diff --git a/opendc-web/opendc-web-ui/src/reducers/map.js b/opendc-web/opendc-web-ui/src/reducers/map.js deleted file mode 100644 index de712c15..00000000 --- a/opendc-web/opendc-web-ui/src/reducers/map.js +++ /dev/null @@ -1,35 +0,0 @@ -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/opendc-web/opendc-web-ui/src/reducers/modals.js b/opendc-web/opendc-web-ui/src/reducers/modals.js deleted file mode 100644 index a7656373..00000000 --- a/opendc-web/opendc-web-ui/src/reducers/modals.js +++ /dev/null @@ -1,45 +0,0 @@ -import { combineReducers } from 'redux' -import { CLOSE_DELETE_PROFILE_MODAL, OPEN_DELETE_PROFILE_MODAL } from '../actions/modals/profile' -import { CLOSE_NEW_PROJECT_MODAL, OPEN_NEW_PROJECT_MODAL } from '../actions/modals/projects' -import { - CLOSE_NEW_TOPOLOGY_MODAL, - CLOSE_DELETE_MACHINE_MODAL, - CLOSE_DELETE_RACK_MODAL, - CLOSE_DELETE_ROOM_MODAL, - CLOSE_EDIT_RACK_NAME_MODAL, - CLOSE_EDIT_ROOM_NAME_MODAL, - OPEN_NEW_TOPOLOGY_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' -import { CLOSE_NEW_PORTFOLIO_MODAL, OPEN_NEW_PORTFOLIO_MODAL } from '../actions/modals/portfolios' -import { CLOSE_NEW_SCENARIO_MODAL, OPEN_NEW_SCENARIO_MODAL } from '../actions/modals/scenarios' - -function modal(openAction, closeAction) { - return function (state = false, action) { - switch (action.type) { - case openAction: - return true - case closeAction: - return false - default: - return state - } - } -} - -export const modals = combineReducers({ - newProjectModalVisible: modal(OPEN_NEW_PROJECT_MODAL, CLOSE_NEW_PROJECT_MODAL), - deleteProfileModalVisible: modal(OPEN_DELETE_PROFILE_MODAL, CLOSE_DELETE_PROFILE_MODAL), - changeTopologyModalVisible: modal(OPEN_NEW_TOPOLOGY_MODAL, CLOSE_NEW_TOPOLOGY_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), - newPortfolioModalVisible: modal(OPEN_NEW_PORTFOLIO_MODAL, CLOSE_NEW_PORTFOLIO_MODAL), - newScenarioModalVisible: modal(OPEN_NEW_SCENARIO_MODAL, CLOSE_NEW_SCENARIO_MODAL), -}) diff --git a/opendc-web/opendc-web-ui/src/reducers/objects.js b/opendc-web/opendc-web-ui/src/reducers/objects.js deleted file mode 100644 index 1f721b2e..00000000 --- a/opendc-web/opendc-web-ui/src/reducers/objects.js +++ /dev/null @@ -1,64 +0,0 @@ -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' -import { CPU_UNITS, GPU_UNITS, MEMORY_UNITS, STORAGE_UNITS } from '../util/unit-specifications' - -export const objects = combineReducers({ - project: object('project'), - user: object('user'), - authorization: objectWithId('authorization', (object) => [object.userId, object.projectId]), - cpu: object('cpu', CPU_UNITS), - gpu: object('gpu', GPU_UNITS), - memory: object('memory', MEMORY_UNITS), - storage: object('storage', STORAGE_UNITS), - machine: object('machine'), - rack: object('rack'), - tile: object('tile'), - room: object('room'), - topology: object('topology'), - trace: object('trace'), - scheduler: object('scheduler'), - portfolio: object('portfolio'), - scenario: object('scenario'), - prefab: object('prefab'), -}) - -function object(type, defaultState = {}) { - return objectWithId(type, (object) => object._id, defaultState) -} - -function objectWithId(type, getId, defaultState = {}) { - return (state = defaultState, 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/opendc-web/opendc-web-ui/src/reducers/project-list.js b/opendc-web/opendc-web-ui/src/reducers/project-list.js deleted file mode 100644 index 1f1aa8d0..00000000 --- a/opendc-web/opendc-web-ui/src/reducers/project-list.js +++ /dev/null @@ -1,30 +0,0 @@ -import { combineReducers } from 'redux' -import { ADD_PROJECT_SUCCEEDED, DELETE_PROJECT_SUCCEEDED, SET_AUTH_VISIBILITY_FILTER } from '../actions/projects' -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_PROJECT_SUCCEEDED: - return [...state, action.authorization] - case DELETE_PROJECT_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 projectList = combineReducers({ - authorizationsOfCurrentUser, - authVisibilityFilter, -}) diff --git a/opendc-web/opendc-web-ui/src/actions/interaction-level.js b/opendc-web/opendc-web-ui/src/redux/actions/interaction-level.js index ff6b1fa3..8381eeef 100644 --- a/opendc-web/opendc-web-ui/src/actions/interaction-level.js +++ b/opendc-web/opendc-web-ui/src/redux/actions/interaction-level.js @@ -3,6 +3,13 @@ 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 goToRoom(roomId) { + return { + type: GO_FROM_BUILDING_TO_ROOM, + roomId, + } +} + export function goFromBuildingToRoom(roomId) { return (dispatch, getState) => { const { interactionLevel } = getState() diff --git a/opendc-web/opendc-web-ui/src/redux/actions/topologies.js b/opendc-web/opendc-web-ui/src/redux/actions/topologies.js new file mode 100644 index 00000000..fc697cc2 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/actions/topologies.js @@ -0,0 +1,27 @@ +export const OPEN_TOPOLOGY = 'OPEN_TOPOLOGY' +export const ADD_TOPOLOGY = 'ADD_TOPOLOGY' +export const STORE_TOPOLOGY = 'STORE_TOPOLOGY' + +export function openTopology(id) { + return { + type: OPEN_TOPOLOGY, + id, + } +} + +export function addTopology(projectId, name, duplicateId) { + return { + type: ADD_TOPOLOGY, + projectId, + name, + duplicateId, + } +} + +export function storeTopology(topology, entities) { + return { + type: STORE_TOPOLOGY, + topology, + entities, + } +} diff --git a/opendc-web/opendc-web-ui/src/actions/topology/building.js b/opendc-web/opendc-web-ui/src/redux/actions/topology/building.js index 72deda6f..939c24a4 100644 --- a/opendc-web/opendc-web-ui/src/actions/topology/building.js +++ b/opendc-web/opendc-web-ui/src/redux/actions/topology/building.js @@ -1,4 +1,6 @@ -export const SET_CURRENT_TOPOLOGY = 'SET_CURRENT_TOPOLOGY' +import { uuid } from 'uuidv4' +import { addRoom, deleteRoom } from './room' + 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' @@ -9,16 +11,19 @@ export const FINISH_ROOM_EDIT = 'FINISH_ROOM_EDIT' export const ADD_TILE = 'ADD_TILE' export const DELETE_TILE = 'DELETE_TILE' -export function setCurrentTopology(topologyId) { - return { - type: SET_CURRENT_TOPOLOGY, - topologyId, - } -} - export function startNewRoomConstruction() { - return { - type: START_NEW_ROOM_CONSTRUCTION, + return (dispatch, getState) => { + const { topology } = getState() + const topologyId = topology.root._id + const room = { + _id: uuid(), + name: 'Room', + topologyId, + tiles: [], + } + + dispatch(addRoom(topologyId, room)) + dispatch(startNewRoomConstructionSucceeded(room._id)) } } @@ -31,8 +36,8 @@ export function startNewRoomConstructionSucceeded(roomId) { export function finishNewRoomConstruction() { return (dispatch, getState) => { - const { objects, construction } = getState() - if (objects.room[construction.currentRoomInConstruction].tileIds.length === 0) { + const { topology, construction } = getState() + if (topology.rooms[construction.currentRoomInConstruction].tiles.length === 0) { dispatch(cancelNewRoomConstruction()) return } @@ -44,8 +49,11 @@ export function finishNewRoomConstruction() { } export function cancelNewRoomConstruction() { - return { - type: CANCEL_NEW_ROOM_CONSTRUCTION, + return (dispatch, getState) => { + const { construction } = getState() + const roomId = construction.currentRoomInConstruction + dispatch(deleteRoom(roomId)) + dispatch(cancelNewRoomConstructionSucceeded()) } } @@ -55,13 +63,10 @@ export function cancelNewRoomConstructionSucceeded() { } } -export function startRoomEdit() { - return (dispatch, getState) => { - const { interactionLevel } = getState() - dispatch({ - type: START_ROOM_EDIT, - roomId: interactionLevel.roomId, - }) +export function startRoomEdit(roomId) { + return { + type: START_ROOM_EDIT, + roomId: roomId, } } @@ -73,27 +78,30 @@ export function finishRoomEdit() { export function toggleTileAtLocation(positionX, positionY) { return (dispatch, getState) => { - const { objects, construction } = getState() + const { topology, 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])) + const roomId = construction.currentRoomInConstruction + const tileIds = topology.rooms[roomId].tiles + for (const tileId of tileIds) { + if (topology.tiles[tileId].positionX === positionX && topology.tiles[tileId].positionY === positionY) { + dispatch(deleteTile(tileId)) return } } - dispatch(addTile(positionX, positionY)) + + dispatch(addTile(roomId, positionX, positionY)) } } -export function addTile(positionX, positionY) { +export function addTile(roomId, positionX, positionY) { return { type: ADD_TILE, - positionX, - positionY, + tile: { + _id: uuid(), + roomId, + positionX, + positionY, + }, } } diff --git a/opendc-web/opendc-web-ui/src/actions/topology/machine.js b/opendc-web/opendc-web-ui/src/redux/actions/topology/machine.js index 17ccce5d..93320884 100644 --- a/opendc-web/opendc-web-ui/src/actions/topology/machine.js +++ b/opendc-web/opendc-web-ui/src/redux/actions/topology/machine.js @@ -2,24 +2,27 @@ export const DELETE_MACHINE = 'DELETE_MACHINE' export const ADD_UNIT = 'ADD_UNIT' export const DELETE_UNIT = 'DELETE_UNIT' -export function deleteMachine() { +export function deleteMachine(machineId) { return { type: DELETE_MACHINE, + machineId, } } -export function addUnit(unitType, id) { +export function addUnit(machineId, unitType, unitId) { return { type: ADD_UNIT, + machineId, unitType, - id, + unitId, } } -export function deleteUnit(unitType, index) { +export function deleteUnit(machineId, unitType, unitId) { return { type: DELETE_UNIT, + machineId, unitType, - index, + unitId, } } diff --git a/opendc-web/opendc-web-ui/src/redux/actions/topology/rack.js b/opendc-web/opendc-web-ui/src/redux/actions/topology/rack.js new file mode 100644 index 00000000..c319d966 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/actions/topology/rack.js @@ -0,0 +1,36 @@ +import { uuid } from 'uuidv4' + +export const EDIT_RACK_NAME = 'EDIT_RACK_NAME' +export const DELETE_RACK = 'DELETE_RACK' +export const ADD_MACHINE = 'ADD_MACHINE' + +export function editRackName(rackId, name) { + return { + type: EDIT_RACK_NAME, + name, + rackId, + } +} + +export function deleteRack(tileId, rackId) { + return { + type: DELETE_RACK, + rackId, + tileId, + } +} + +export function addMachine(rackId, position) { + return { + type: ADD_MACHINE, + machine: { + _id: uuid(), + rackId, + position, + cpus: [], + gpus: [], + memories: [], + storages: [], + }, + } +} diff --git a/opendc-web/opendc-web-ui/src/redux/actions/topology/room.js b/opendc-web/opendc-web-ui/src/redux/actions/topology/room.js new file mode 100644 index 00000000..bd447db5 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/actions/topology/room.js @@ -0,0 +1,74 @@ +import { uuid } from 'uuidv4' +import { + DEFAULT_RACK_SLOT_CAPACITY, + DEFAULT_RACK_POWER_CAPACITY, +} from '../../../components/topologies/map/MapConstants' +import { findTileWithPosition } from '../../../util/tile-calculations' + +export const ADD_ROOM = 'ADD_ROOM' +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 addRoom(topologyId, room) { + return { + type: ADD_ROOM, + room: { + _id: uuid(), + topologyId, + ...room, + }, + } +} + +export function editRoomName(roomId, name) { + return { + type: EDIT_ROOM_NAME, + name, + roomId, + } +} + +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 { topology, interactionLevel } = getState() + const currentRoom = topology.rooms[interactionLevel.roomId] + const tiles = currentRoom.tiles.map((tileId) => topology.tiles[tileId]) + const tile = findTileWithPosition(tiles, positionX, positionY) + + if (tile !== null) { + dispatch({ + type: ADD_RACK_TO_TILE, + rack: { + _id: uuid(), + name: 'Rack', + tileId: tile._id, + capacity: DEFAULT_RACK_SLOT_CAPACITY, + powerCapacityW: DEFAULT_RACK_POWER_CAPACITY, + machines: [], + }, + }) + } + } +} + +export function deleteRoom(roomId) { + return { + type: DELETE_ROOM, + roomId, + } +} diff --git a/opendc-web/opendc-web-ui/src/redux/index.js b/opendc-web/opendc-web-ui/src/redux/index.js new file mode 100644 index 00000000..fa0c9d23 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/index.js @@ -0,0 +1,58 @@ +import { useMemo } from 'react' +import { applyMiddleware, compose, createStore } from 'redux' +import { createLogger } from 'redux-logger' +import createSagaMiddleware from 'redux-saga' +import thunk from 'redux-thunk' +import rootReducer from './reducers' +import rootSaga from './sagas' +import { createReduxEnhancer } from '@sentry/react' + +let store + +function initStore(initialState, ctx) { + const sagaMiddleware = createSagaMiddleware({ context: ctx }) + + const middlewares = [thunk, sagaMiddleware] + + if (process.env.NODE_ENV !== 'production') { + middlewares.push(createLogger()) + } + + let middleware = applyMiddleware(...middlewares) + + if (process.env.NEXT_PUBLIC_SENTRY_DSN) { + middleware = compose(middleware, createReduxEnhancer()) + } + + const configuredStore = createStore(rootReducer, initialState, middleware) + sagaMiddleware.run(rootSaga) + store = configuredStore + + return configuredStore +} + +export const initializeStore = (preloadedState, ctx) => { + let _store = store ?? initStore(preloadedState, ctx) + + // After navigating to a page with an initial Redux state, merge that state + // with the current state in the store, and create a new store + if (preloadedState && store) { + _store = initStore({ + ...store.getState(), + ...preloadedState, + }) + // Reset the current store + store = undefined + } + + // For SSG and SSR always create a new store + if (typeof window === 'undefined') return _store + // Create the store once in the client + if (!store) store = _store + + return _store +} + +export function useStore(initialState, ctx) { + return useMemo(() => initializeStore(initialState, ctx), [initialState, ctx]) +} diff --git a/opendc-web/opendc-web-ui/src/reducers/construction-mode.js b/opendc-web/opendc-web-ui/src/redux/reducers/construction-mode.js index 257dddd2..d0aac5ae 100644 --- a/opendc-web/opendc-web-ui/src/reducers/construction-mode.js +++ b/opendc-web/opendc-web-ui/src/redux/reducers/construction-mode.js @@ -4,13 +4,10 @@ import { CANCEL_NEW_ROOM_CONSTRUCTION_SUCCEEDED, FINISH_NEW_ROOM_CONSTRUCTION, FINISH_ROOM_EDIT, - SET_CURRENT_TOPOLOGY, 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' -import { OPEN_PORTFOLIO_SUCCEEDED } from '../actions/portfolios' -import { OPEN_SCENARIO_SUCCEEDED } from '../actions/scenarios' export function currentRoomInConstruction(state = '-1', action) { switch (action.type) { @@ -20,10 +17,7 @@ export function currentRoomInConstruction(state = '-1', action) { return action.roomId case CANCEL_NEW_ROOM_CONSTRUCTION_SUCCEEDED: case FINISH_NEW_ROOM_CONSTRUCTION: - case OPEN_PORTFOLIO_SUCCEEDED: - case OPEN_SCENARIO_SUCCEEDED: case FINISH_ROOM_EDIT: - case SET_CURRENT_TOPOLOGY: case DELETE_ROOM: return '-1' default: @@ -36,9 +30,6 @@ export function inRackConstructionMode(state = false, action) { case START_RACK_CONSTRUCTION: return true case STOP_RACK_CONSTRUCTION: - case OPEN_PORTFOLIO_SUCCEEDED: - case OPEN_SCENARIO_SUCCEEDED: - case SET_CURRENT_TOPOLOGY: case GO_DOWN_ONE_INTERACTION_LEVEL: return false default: diff --git a/opendc-web/opendc-web-ui/src/redux/reducers/index.js b/opendc-web/opendc-web-ui/src/redux/reducers/index.js new file mode 100644 index 00000000..7ffb1211 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/reducers/index.js @@ -0,0 +1,12 @@ +import { combineReducers } from 'redux' +import { construction } from './construction-mode' +import { interactionLevel } from './interaction-level' +import topology from './topology' + +const rootReducer = combineReducers({ + topology, + construction, + interactionLevel, +}) + +export default rootReducer diff --git a/opendc-web/opendc-web-ui/src/reducers/interaction-level.js b/opendc-web/opendc-web-ui/src/redux/reducers/interaction-level.js index eafcb269..b30c68b9 100644 --- a/opendc-web/opendc-web-ui/src/reducers/interaction-level.js +++ b/opendc-web/opendc-web-ui/src/redux/reducers/interaction-level.js @@ -1,23 +1,15 @@ -import { OPEN_PORTFOLIO_SUCCEEDED } from '../actions/portfolios' 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_PROJECT_SUCCEEDED } from '../actions/projects' -import { SET_CURRENT_TOPOLOGY } from '../actions/topology/building' -import { OPEN_SCENARIO_SUCCEEDED } from '../actions/scenarios' +import { DELETE_MACHINE } from '../actions/topology/machine' +import { DELETE_RACK } from '../actions/topology/rack' +import { DELETE_ROOM } from '../actions/topology/room' export function interactionLevel(state = { mode: 'BUILDING' }, action) { switch (action.type) { - case OPEN_PORTFOLIO_SUCCEEDED: - case OPEN_SCENARIO_SUCCEEDED: - case OPEN_PROJECT_SUCCEEDED: - case SET_CURRENT_TOPOLOGY: - return { - mode: 'BUILDING', - } case GO_FROM_BUILDING_TO_ROOM: return { mode: 'ROOM', @@ -55,6 +47,21 @@ export function interactionLevel(state = { mode: 'BUILDING' }, action) { } else { return state } + case DELETE_MACHINE: + return { + mode: 'RACK', + roomId: state.roomId, + tileId: state.tileId, + } + case DELETE_RACK: + return { + mode: 'ROOM', + roomId: state.roomId, + } + case DELETE_ROOM: + return { + mode: 'BUILDING', + } default: return state } diff --git a/opendc-web/opendc-web-ui/src/redux/reducers/topology/index.js b/opendc-web/opendc-web-ui/src/redux/reducers/topology/index.js new file mode 100644 index 00000000..b1c7d29e --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/reducers/topology/index.js @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2021 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. + */ + +import { CPU_UNITS, GPU_UNITS, MEMORY_UNITS, STORAGE_UNITS } from '../../../util/unit-specifications' +import machine from './machine' +import rack from './rack' +import room from './room' +import tile from './tile' +import topology from './topology' + +function objects(state = {}, action) { + return { + cpus: CPU_UNITS, + gpus: GPU_UNITS, + memories: MEMORY_UNITS, + storages: STORAGE_UNITS, + machines: machine(state.machines, action, state), + racks: rack(state.racks, action, state), + tiles: tile(state.tiles, action, state), + rooms: room(state.rooms, action, state), + root: topology(state.root, action, state), + } +} + +export default objects diff --git a/opendc-web/opendc-web-ui/src/redux/reducers/topology/machine.js b/opendc-web/opendc-web-ui/src/redux/reducers/topology/machine.js new file mode 100644 index 00000000..41773014 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/reducers/topology/machine.js @@ -0,0 +1,47 @@ +import produce from 'immer' +import { STORE_TOPOLOGY } from '../../actions/topologies' +import { DELETE_MACHINE, ADD_UNIT, DELETE_UNIT } from '../../actions/topology/machine' +import { ADD_MACHINE, DELETE_RACK } from '../../actions/topology/rack' + +function machine(state = {}, action, { racks }) { + switch (action.type) { + case STORE_TOPOLOGY: + return action.entities.machines || {} + case ADD_MACHINE: + return produce(state, (draft) => { + const { machine } = action + draft[machine._id] = machine + }) + case DELETE_MACHINE: + return produce(state, (draft) => { + const { machineId } = action + delete draft[machineId] + }) + case ADD_UNIT: + return produce(state, (draft) => { + const { machineId, unitType, unitId } = action + draft[machineId][unitType].push(unitId) + }) + case DELETE_UNIT: + return produce(state, (draft) => { + const { machineId, unitType, unitId } = action + const units = draft[machineId][unitType] + const index = units.indexOf(unitId) + units.splice(index, 1) + }) + case DELETE_RACK: + return produce(state, (draft) => { + const { rackId } = action + const rack = racks[rackId] + + for (const id of rack.machines) { + const machine = draft[id] + machine.rackId = undefined + } + }) + default: + return state + } +} + +export default machine diff --git a/opendc-web/opendc-web-ui/src/redux/reducers/topology/rack.js b/opendc-web/opendc-web-ui/src/redux/reducers/topology/rack.js new file mode 100644 index 00000000..9cc37124 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/reducers/topology/rack.js @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2021 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. + */ + +import produce from 'immer' +import { STORE_TOPOLOGY } from '../../actions/topologies' +import { DELETE_MACHINE } from '../../actions/topology/machine' +import { DELETE_RACK, EDIT_RACK_NAME, ADD_MACHINE } from '../../actions/topology/rack' +import { ADD_RACK_TO_TILE } from '../../actions/topology/room' + +function rack(state = {}, action, { machines }) { + switch (action.type) { + case STORE_TOPOLOGY: + return action.entities.racks || {} + case ADD_RACK_TO_TILE: + return produce(state, (draft) => { + const { rack } = action + draft[rack._id] = rack + }) + case EDIT_RACK_NAME: + return produce(state, (draft) => { + const { rackId, name } = action + draft[rackId].name = name + }) + case DELETE_RACK: + return produce(state, (draft) => { + const { rackId } = action + delete draft[rackId] + }) + case ADD_MACHINE: + return produce(state, (draft) => { + const { machine } = action + draft[machine.rackId].machines.push(machine._id) + }) + case DELETE_MACHINE: + return produce(state, (draft) => { + const { machineId } = action + const machine = machines[machineId] + const rack = draft[machine.rackId] + const index = rack.machines.indexOf(machineId) + rack.machines.splice(index, 1) + }) + default: + return state + } +} + +export default rack diff --git a/opendc-web/opendc-web-ui/src/redux/reducers/topology/room.js b/opendc-web/opendc-web-ui/src/redux/reducers/topology/room.js new file mode 100644 index 00000000..b61c9d82 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/reducers/topology/room.js @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2021 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. + */ + +import produce from 'immer' +import { STORE_TOPOLOGY } from '../../actions/topologies' +import { ADD_TILE, DELETE_TILE } from '../../actions/topology/building' +import { DELETE_ROOM, EDIT_ROOM_NAME, ADD_ROOM } from '../../actions/topology/room' + +function room(state = {}, action, { tiles }) { + switch (action.type) { + case STORE_TOPOLOGY: + return action.entities.rooms || {} + case ADD_ROOM: + return produce(state, (draft) => { + const { room } = action + draft[room._id] = room + }) + case DELETE_ROOM: + return produce(state, (draft) => { + const { roomId } = action + delete draft[roomId] + }) + case EDIT_ROOM_NAME: + return produce(state, (draft) => { + const { roomId, name } = action + draft[roomId].name = name + }) + case ADD_TILE: + return produce(state, (draft) => { + const { tile } = action + draft[tile.roomId].tiles.push(tile._id) + }) + case DELETE_TILE: + return produce(state, (draft) => { + const { tileId } = action + const tile = tiles[tileId] + const room = draft[tile.roomId] + const index = room.tiles.indexOf(tileId) + room.tiles.splice(index, 1) + }) + default: + return state + } +} + +export default room diff --git a/opendc-web/opendc-web-ui/src/redux/reducers/topology/tile.js b/opendc-web/opendc-web-ui/src/redux/reducers/topology/tile.js new file mode 100644 index 00000000..e0c5dd33 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/reducers/topology/tile.js @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2021 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. + */ + +import produce from 'immer' +import { STORE_TOPOLOGY } from '../../actions/topologies' +import { ADD_TILE, DELETE_TILE } from '../../actions/topology/building' +import { DELETE_RACK } from '../../actions/topology/rack' +import { ADD_RACK_TO_TILE } from '../../actions/topology/room' + +function tile(state = {}, action, { racks }) { + switch (action.type) { + case STORE_TOPOLOGY: + return action.entities.tiles || {} + case ADD_TILE: + return produce(state, (draft) => { + const { tile } = action + draft[tile._id] = tile + }) + case DELETE_TILE: + return produce(state, (draft) => { + const { tileId } = action + delete draft[tileId] + }) + case ADD_RACK_TO_TILE: + return produce(state, (draft) => { + const { rack } = action + draft[rack.tileId].rack = rack._id + }) + case DELETE_RACK: + return produce(state, (draft) => { + const { rackId } = action + const rack = racks[rackId] + draft[rack.tileId].rack = undefined + }) + default: + return state + } +} + +export default tile diff --git a/opendc-web/opendc-web-ui/src/redux/reducers/topology/topology.js b/opendc-web/opendc-web-ui/src/redux/reducers/topology/topology.js new file mode 100644 index 00000000..da0e6988 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/reducers/topology/topology.js @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2021 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. + */ + +import produce from 'immer' +import { STORE_TOPOLOGY } from '../../actions/topologies' +import { ADD_ROOM, DELETE_ROOM } from '../../actions/topology/room' + +function topology(state = undefined, action) { + switch (action.type) { + case STORE_TOPOLOGY: + return action.topology + case ADD_ROOM: + return produce(state, (draft) => { + const { room } = action + draft.rooms.push(room._id) + }) + case DELETE_ROOM: + return produce(state, (draft) => { + const { roomId } = action + const index = draft.rooms.indexOf(roomId) + draft.rooms.splice(index, 1) + }) + default: + return state + } +} + +export default topology diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/index.js b/opendc-web/opendc-web-ui/src/redux/sagas/index.js new file mode 100644 index 00000000..0fabdb6d --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/sagas/index.js @@ -0,0 +1,7 @@ +import { fork } from 'redux-saga/effects' +import { watchServer, updateServer } from './topology' + +export default function* rootSaga() { + yield fork(watchServer) + yield fork(updateServer) +} diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/topology.js b/opendc-web/opendc-web-ui/src/redux/sagas/topology.js new file mode 100644 index 00000000..f40cff28 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/sagas/topology.js @@ -0,0 +1,75 @@ +import { QueryObserver, MutationObserver } from 'react-query' +import { normalize, denormalize } from 'normalizr' +import { select, put, take, race, getContext, call } from 'redux-saga/effects' +import { eventChannel } from 'redux-saga' +import { Topology } from '../../util/topology-schema' +import { storeTopology, OPEN_TOPOLOGY } from '../actions/topologies' + +/** + * Update the topology on the server. + */ +export function* updateServer() { + const queryClient = yield getContext('queryClient') + const mutationObserver = new MutationObserver(queryClient, { mutationKey: 'updateTopology' }) + + while (true) { + yield take( + (action) => + action.type.startsWith('EDIT') || action.type.startsWith('ADD') || action.type.startsWith('DELETE') + ) + const topology = yield select((state) => state.topology) + + if (!topology.root) { + continue + } + + const denormalizedTopology = denormalize(topology.root, Topology, topology) + yield call([mutationObserver, mutationObserver.mutate], denormalizedTopology) + } +} + +/** + * Watch the topology on the server for changes. + */ +export function* watchServer() { + let { id } = yield take(OPEN_TOPOLOGY) + while (true) { + const channel = yield queryObserver(id) + + while (true) { + const [action, response] = yield race([take(OPEN_TOPOLOGY), take(channel)]) + + if (action) { + id = action.id + break + } + + const { isFetched, data } = response + // Only update the topology on the client-side when a new topology was fetched + if (isFetched) { + const { result: topologyId, entities } = normalize(data, Topology) + yield put(storeTopology(entities.topologies[topologyId], entities)) + } + } + } +} + +/** + * Observe changes for the topology with the specified identifier. + */ +function* queryObserver(id) { + const queryClient = yield getContext('queryClient') + const observer = new QueryObserver(queryClient, { queryKey: ['topologies', id] }) + + return eventChannel((emitter) => { + const unsubscribe = observer.subscribe((result) => { + emitter(result) + }) + + // Update result to make sure we did not miss any query updates + // between creating the observer and subscribing to it. + observer.updateResult() + + return unsubscribe + }) +} diff --git a/opendc-web/opendc-web-ui/src/routes/index.js b/opendc-web/opendc-web-ui/src/routes/index.js deleted file mode 100644 index 4291a046..00000000 --- a/opendc-web/opendc-web-ui/src/routes/index.js +++ /dev/null @@ -1,40 +0,0 @@ -import React from 'react' -import { BrowserRouter, Redirect, Route, Switch } from 'react-router-dom' -import { userIsLoggedIn } from '../auth/index' -import App from '../pages/App' -import Home from '../pages/Home' -import NotFound from '../pages/NotFound' -import Profile from '../pages/Profile' -import Projects from '../pages/Projects' - -const ProtectedComponent = (component) => () => (userIsLoggedIn() ? component : <Redirect to="/" />) -const AppComponent = ({ match }) => - userIsLoggedIn() ? ( - <App - projectId={match.params.projectId} - portfolioId={match.params.portfolioId} - scenarioId={match.params.scenarioId} - /> - ) : ( - <Redirect to="/" /> - ) - -const Routes = () => ( - <BrowserRouter> - <Switch> - <Route exact path="/" component={Home} /> - <Route exact path="/projects" render={ProtectedComponent(<Projects />)} /> - <Route exact path="/projects/:projectId" component={AppComponent} /> - <Route exact path="/projects/:projectId/portfolios/:portfolioId" component={AppComponent} /> - <Route - exact - path="/projects/:projectId/portfolios/:portfolioId/scenarios/:scenarioId" - component={AppComponent} - /> - <Route exact path="/profile" render={ProtectedComponent(<Profile />)} /> - <Route path="/*" component={NotFound} /> - </Switch> - </BrowserRouter> -) - -export default Routes diff --git a/opendc-web/opendc-web-ui/src/sagas/index.js b/opendc-web/opendc-web-ui/src/sagas/index.js deleted file mode 100644 index 6332b2fb..00000000 --- a/opendc-web/opendc-web-ui/src/sagas/index.js +++ /dev/null @@ -1,80 +0,0 @@ -import { takeEvery } from 'redux-saga/effects' -import { LOG_IN } from '../actions/auth' -import { ADD_PORTFOLIO, DELETE_PORTFOLIO, OPEN_PORTFOLIO_SUCCEEDED, UPDATE_PORTFOLIO } from '../actions/portfolios' -import { ADD_PROJECT, DELETE_PROJECT, OPEN_PROJECT_SUCCEEDED } from '../actions/projects' -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 { onAddPortfolio, onDeletePortfolio, onOpenPortfolioSucceeded, onUpdatePortfolio } from './portfolios' -import { onDeleteCurrentUser } from './profile' -import { onOpenProjectSucceeded, onProjectAdd, onProjectDelete } from './projects' -import { - onAddMachine, - onAddRackToTile, - onAddTile, - onAddTopology, - onAddUnit, - onCancelNewRoomConstruction, - onDeleteMachine, - onDeleteRack, - onDeleteRoom, - onDeleteTile, - onDeleteTopology, - onDeleteUnit, - onEditRackName, - onEditRoomName, - onStartNewRoomConstruction, -} from './topology' -import { onFetchAuthorizationsOfCurrentUser, onFetchLoggedInUser } from './users' -import { ADD_TOPOLOGY, DELETE_TOPOLOGY } from '../actions/topologies' -import { ADD_SCENARIO, DELETE_SCENARIO, OPEN_SCENARIO_SUCCEEDED, UPDATE_SCENARIO } from '../actions/scenarios' -import { onAddScenario, onDeleteScenario, onOpenScenarioSucceeded, onUpdateScenario } from './scenarios' -import { onAddPrefab } from './prefabs' -import { ADD_PREFAB } from '../actions/prefabs' - -export default function* rootSaga() { - yield takeEvery(LOG_IN, onFetchLoggedInUser) - - yield takeEvery(FETCH_AUTHORIZATIONS_OF_CURRENT_USER, onFetchAuthorizationsOfCurrentUser) - yield takeEvery(ADD_PROJECT, onProjectAdd) - yield takeEvery(DELETE_PROJECT, onProjectDelete) - - yield takeEvery(DELETE_CURRENT_USER, onDeleteCurrentUser) - - yield takeEvery(OPEN_PROJECT_SUCCEEDED, onOpenProjectSucceeded) - yield takeEvery(OPEN_PORTFOLIO_SUCCEEDED, onOpenPortfolioSucceeded) - yield takeEvery(OPEN_SCENARIO_SUCCEEDED, onOpenScenarioSucceeded) - - yield takeEvery(ADD_TOPOLOGY, onAddTopology) - yield takeEvery(DELETE_TOPOLOGY, onDeleteTopology) - 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(ADD_PORTFOLIO, onAddPortfolio) - yield takeEvery(UPDATE_PORTFOLIO, onUpdatePortfolio) - yield takeEvery(DELETE_PORTFOLIO, onDeletePortfolio) - - yield takeEvery(ADD_SCENARIO, onAddScenario) - yield takeEvery(UPDATE_SCENARIO, onUpdateScenario) - yield takeEvery(DELETE_SCENARIO, onDeleteScenario) - - yield takeEvery(ADD_PREFAB, onAddPrefab) -} diff --git a/opendc-web/opendc-web-ui/src/sagas/objects.js b/opendc-web/opendc-web-ui/src/sagas/objects.js deleted file mode 100644 index 313d9976..00000000 --- a/opendc-web/opendc-web-ui/src/sagas/objects.js +++ /dev/null @@ -1,229 +0,0 @@ -import { call, put, select } from 'redux-saga/effects' -import { addToStore } from '../actions/objects' -import { getAllSchedulers } from '../api/routes/schedulers' -import { getProject } from '../api/routes/projects' -import { getAllTraces } from '../api/routes/traces' -import { getUser } from '../api/routes/users' -import { getTopology, updateTopology } from '../api/routes/topologies' -import { uuid } from 'uuidv4' - -export const OBJECT_SELECTORS = { - project: (state) => state.objects.project, - user: (state) => state.objects.user, - authorization: (state) => state.objects.authorization, - portfolio: (state) => state.objects.portfolio, - scenario: (state) => state.objects.scenario, - 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, - tile: (state) => state.objects.tile, - room: (state) => state.objects.room, - topology: (state) => state.objects.topology, -} - -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 object of objects) { - yield put(addToStore(objectType, object)) - } - return objects -} - -export const fetchAndStoreProject = (id) => fetchAndStoreObject('project', id, call(getProject, id)) - -export const fetchAndStoreUser = (id) => fetchAndStoreObject('user', id, call(getUser, id)) - -export const fetchAndStoreTopology = function* (id) { - const topologyStore = yield select(OBJECT_SELECTORS['topology']) - const roomStore = yield select(OBJECT_SELECTORS['room']) - const tileStore = yield select(OBJECT_SELECTORS['tile']) - const rackStore = yield select(OBJECT_SELECTORS['rack']) - const machineStore = yield select(OBJECT_SELECTORS['machine']) - - let topology = topologyStore[id] - if (!topology) { - const fullTopology = yield call(getTopology, id) - - for (let roomIdx in fullTopology.rooms) { - const fullRoom = fullTopology.rooms[roomIdx] - - generateIdIfNotPresent(fullRoom) - - if (!roomStore[fullRoom._id]) { - for (let tileIdx in fullRoom.tiles) { - const fullTile = fullRoom.tiles[tileIdx] - - generateIdIfNotPresent(fullTile) - - if (!tileStore[fullTile._id]) { - if (fullTile.rack) { - const fullRack = fullTile.rack - - generateIdIfNotPresent(fullRack) - - if (!rackStore[fullRack._id]) { - for (let machineIdx in fullRack.machines) { - const fullMachine = fullRack.machines[machineIdx] - - generateIdIfNotPresent(fullMachine) - - if (!machineStore[fullMachine._id]) { - let machine = (({ _id, position, cpus, gpus, memories, storages }) => ({ - _id, - rackId: fullRack._id, - position, - cpuIds: cpus.map((u) => u._id), - gpuIds: gpus.map((u) => u._id), - memoryIds: memories.map((u) => u._id), - storageIds: storages.map((u) => u._id), - }))(fullMachine) - yield put(addToStore('machine', machine)) - } - } - - const filledSlots = new Array(fullRack.capacity).fill(null) - fullRack.machines.forEach( - (machine) => (filledSlots[machine.position - 1] = machine._id) - ) - let rack = (({ _id, name, capacity, powerCapacityW }) => ({ - _id, - name, - capacity, - powerCapacityW, - machineIds: filledSlots, - }))(fullRack) - yield put(addToStore('rack', rack)) - } - } - - let tile = (({ _id, positionX, positionY, rack }) => ({ - _id, - roomId: fullRoom._id, - positionX, - positionY, - rackId: rack ? rack._id : undefined, - }))(fullTile) - yield put(addToStore('tile', tile)) - } - } - - let room = (({ _id, name, tiles }) => ({ _id, name, tileIds: tiles.map((t) => t._id) }))(fullRoom) - yield put(addToStore('room', room)) - } - } - - topology = (({ _id, name, rooms }) => ({ _id, name, roomIds: rooms.map((r) => r._id) }))(fullTopology) - yield put(addToStore('topology', topology)) - - // TODO consider pushing the IDs - } - - return topology -} - -const generateIdIfNotPresent = (obj) => { - if (!obj._id) { - obj._id = uuid() - } -} - -export const updateTopologyOnServer = function* (id) { - const topology = yield getTopologyAsObject(id, true) - - yield call(updateTopology, topology) -} - -export const getTopologyAsObject = function* (id, keepIds) { - const topologyStore = yield select(OBJECT_SELECTORS['topology']) - const rooms = yield getAllRooms(topologyStore[id].roomIds, keepIds) - return { - _id: keepIds ? id : undefined, - name: topologyStore[id].name, - rooms: rooms, - } -} - -export const getAllRooms = function* (roomIds, keepIds) { - const roomStore = yield select(OBJECT_SELECTORS['room']) - - let rooms = [] - - for (let id of roomIds) { - let tiles = yield getAllRoomTiles(roomStore[id], keepIds) - rooms.push({ - _id: keepIds ? id : undefined, - name: roomStore[id].name, - tiles: tiles, - }) - } - return rooms -} - -export const getAllRoomTiles = function* (roomStore, keepIds) { - let tiles = [] - - for (let id of roomStore.tileIds) { - tiles.push(yield getTileById(id, keepIds)) - } - return tiles -} - -export const getTileById = function* (id, keepIds) { - const tileStore = yield select(OBJECT_SELECTORS['tile']) - return { - _id: keepIds ? id : undefined, - positionX: tileStore[id].positionX, - positionY: tileStore[id].positionY, - rack: !tileStore[id].rackId ? undefined : yield getRackById(tileStore[id].rackId, keepIds), - } -} - -export const getRackById = function* (id, keepIds) { - const rackStore = yield select(OBJECT_SELECTORS['rack']) - const machineStore = yield select(OBJECT_SELECTORS['machine']) - const cpuStore = yield select(OBJECT_SELECTORS['cpu']) - const gpuStore = yield select(OBJECT_SELECTORS['gpu']) - const memoryStore = yield select(OBJECT_SELECTORS['memory']) - const storageStore = yield select(OBJECT_SELECTORS['storage']) - - return { - _id: keepIds ? rackStore[id]._id : undefined, - name: rackStore[id].name, - capacity: rackStore[id].capacity, - powerCapacityW: rackStore[id].powerCapacityW, - machines: rackStore[id].machineIds - .filter((m) => m !== null) - .map((machineId) => ({ - _id: keepIds ? machineId : undefined, - position: machineStore[machineId].position, - cpus: machineStore[machineId].cpuIds.map((id) => cpuStore[id]), - gpus: machineStore[machineId].gpuIds.map((id) => gpuStore[id]), - memories: machineStore[machineId].memoryIds.map((id) => memoryStore[id]), - storages: machineStore[machineId].storageIds.map((id) => storageStore[id]), - })), - } -} - -export const fetchAndStoreAllTraces = () => fetchAndStoreObjects('trace', call(getAllTraces)) - -export const fetchAndStoreAllSchedulers = function* () { - const objects = yield call(getAllSchedulers) - for (let object of objects) { - object._id = object.name - yield put(addToStore('scheduler', object)) - } - return objects -} diff --git a/opendc-web/opendc-web-ui/src/sagas/portfolios.js b/opendc-web/opendc-web-ui/src/sagas/portfolios.js deleted file mode 100644 index ed9bfd29..00000000 --- a/opendc-web/opendc-web-ui/src/sagas/portfolios.js +++ /dev/null @@ -1,131 +0,0 @@ -import { call, put, select, delay } from 'redux-saga/effects' -import { addPropToStoreObject, addToStore } from '../actions/objects' -import { addPortfolio, deletePortfolio, getPortfolio, updatePortfolio } from '../api/routes/portfolios' -import { getProject } from '../api/routes/projects' -import { fetchAndStoreAllSchedulers, fetchAndStoreAllTraces } from './objects' -import { fetchAndStoreAllTopologiesOfProject } from './topology' -import { getScenario } from '../api/routes/scenarios' - -export function* onOpenPortfolioSucceeded(action) { - try { - const project = yield call(getProject, action.projectId) - yield put(addToStore('project', project)) - yield fetchAndStoreAllTopologiesOfProject(project._id) - yield fetchPortfoliosOfProject() - yield fetchAndStoreAllSchedulers() - yield fetchAndStoreAllTraces() - - yield watchForPortfolioResults() - } catch (error) { - console.error(error) - } -} - -export function* watchForPortfolioResults() { - try { - const currentPortfolioId = yield select((state) => state.currentPortfolioId) - let unfinishedScenarios = yield getCurrentUnfinishedScenarios() - - while (unfinishedScenarios.length > 0) { - yield delay(3000) - yield fetchPortfolioWithScenarios(currentPortfolioId) - unfinishedScenarios = yield getCurrentUnfinishedScenarios() - } - } catch (error) { - console.error(error) - } -} - -export function* getCurrentUnfinishedScenarios() { - try { - const currentPortfolioId = yield select((state) => state.currentPortfolioId) - const scenarioIds = yield select((state) => state.objects.portfolio[currentPortfolioId].scenarioIds) - const scenarioObjects = yield select((state) => state.objects.scenario) - const scenarios = scenarioIds.map((s) => scenarioObjects[s]) - return scenarios.filter((s) => !s || s.simulation.state === 'QUEUED' || s.simulation.state === 'RUNNING') - } catch (error) { - console.error(error) - } -} - -export function* fetchPortfoliosOfProject() { - try { - const currentProjectId = yield select((state) => state.currentProjectId) - const currentProject = yield select((state) => state.objects.project[currentProjectId]) - - yield fetchAndStoreAllSchedulers() - yield fetchAndStoreAllTraces() - - for (let i in currentProject.portfolioIds) { - yield fetchPortfolioWithScenarios(currentProject.portfolioIds[i]) - } - } catch (error) { - console.error(error) - } -} - -export function* fetchPortfolioWithScenarios(portfolioId) { - try { - const portfolio = yield call(getPortfolio, portfolioId) - yield put(addToStore('portfolio', portfolio)) - - for (let i in portfolio.scenarioIds) { - const scenario = yield call(getScenario, portfolio.scenarioIds[i]) - yield put(addToStore('scenario', scenario)) - } - return portfolio - } catch (error) { - console.error(error) - } -} - -export function* onAddPortfolio(action) { - try { - const currentProjectId = yield select((state) => state.currentProjectId) - - const portfolio = yield call( - addPortfolio, - currentProjectId, - Object.assign({}, action.portfolio, { - projectId: currentProjectId, - scenarioIds: [], - }) - ) - yield put(addToStore('portfolio', portfolio)) - - const portfolioIds = yield select((state) => state.objects.project[currentProjectId].portfolioIds) - yield put( - addPropToStoreObject('project', currentProjectId, { - portfolioIds: portfolioIds.concat([portfolio._id]), - }) - ) - } catch (error) { - console.error(error) - } -} - -export function* onUpdatePortfolio(action) { - try { - const portfolio = yield call(updatePortfolio, action.portfolio._id, action.portfolio) - yield put(addToStore('portfolio', portfolio)) - } catch (error) { - console.error(error) - } -} - -export function* onDeletePortfolio(action) { - try { - yield call(deletePortfolio, action.id) - - const currentProjectId = yield select((state) => state.currentProjectId) - const portfolioIds = yield select((state) => state.objects.project[currentProjectId].portfolioIds) - - yield put( - addPropToStoreObject('project', currentProjectId, { - portfolioIds: portfolioIds.filter((id) => id !== action.id), - }) - ) - } catch (error) { - console.error(error) - } -} diff --git a/opendc-web/opendc-web-ui/src/sagas/prefabs.js b/opendc-web/opendc-web-ui/src/sagas/prefabs.js deleted file mode 100644 index 16cf3d62..00000000 --- a/opendc-web/opendc-web-ui/src/sagas/prefabs.js +++ /dev/null @@ -1,15 +0,0 @@ -import { call, put, select } from 'redux-saga/effects' -import { addToStore } from '../actions/objects' -import { addPrefab } from '../api/routes/prefabs' -import { getRackById } from './objects' - -export function* onAddPrefab(action) { - try { - const currentRackId = yield select((state) => state.objects.tile[state.interactionLevel.tileId].rackId) - const currentRackJson = yield getRackById(currentRackId, false) - const prefab = yield call(addPrefab, { name: action.name, rack: currentRackJson }) - yield put(addToStore('prefab', prefab)) - } catch (error) { - console.error(error) - } -} diff --git a/opendc-web/opendc-web-ui/src/sagas/profile.js b/opendc-web/opendc-web-ui/src/sagas/profile.js deleted file mode 100644 index e914ba56..00000000 --- a/opendc-web/opendc-web-ui/src/sagas/profile.js +++ /dev/null @@ -1,12 +0,0 @@ -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/opendc-web/opendc-web-ui/src/sagas/projects.js b/opendc-web/opendc-web-ui/src/sagas/projects.js deleted file mode 100644 index fdeea132..00000000 --- a/opendc-web/opendc-web-ui/src/sagas/projects.js +++ /dev/null @@ -1,48 +0,0 @@ -import { call, put } from 'redux-saga/effects' -import { addToStore } from '../actions/objects' -import { addProjectSucceeded, deleteProjectSucceeded } from '../actions/projects' -import { addProject, deleteProject, getProject } from '../api/routes/projects' -import { fetchAndStoreAllTopologiesOfProject } from './topology' -import { fetchAndStoreAllSchedulers, fetchAndStoreAllTraces } from './objects' -import { fetchPortfoliosOfProject } from './portfolios' - -export function* onOpenProjectSucceeded(action) { - try { - const project = yield call(getProject, action.id) - yield put(addToStore('project', project)) - - yield fetchAndStoreAllTopologiesOfProject(action.id, true) - yield fetchPortfoliosOfProject() - yield fetchAndStoreAllSchedulers() - yield fetchAndStoreAllTraces() - } catch (error) { - console.error(error) - } -} - -export function* onProjectAdd(action) { - try { - const project = yield call(addProject, { name: action.name }) - yield put(addToStore('project', project)) - - const authorization = { - projectId: project._id, - userId: action.userId, - authorizationLevel: 'OWN', - project, - } - yield put(addToStore('authorization', authorization)) - yield put(addProjectSucceeded([authorization.userId, authorization.projectId])) - } catch (error) { - console.error(error) - } -} - -export function* onProjectDelete(action) { - try { - yield call(deleteProject, action.id) - yield put(deleteProjectSucceeded(action.id)) - } catch (error) { - console.error(error) - } -} diff --git a/opendc-web/opendc-web-ui/src/sagas/scenarios.js b/opendc-web/opendc-web-ui/src/sagas/scenarios.js deleted file mode 100644 index 59223610..00000000 --- a/opendc-web/opendc-web-ui/src/sagas/scenarios.js +++ /dev/null @@ -1,65 +0,0 @@ -import { call, put, select } from 'redux-saga/effects' -import { addPropToStoreObject, addToStore } from '../actions/objects' -import { getProject } from '../api/routes/projects' -import { fetchAndStoreAllSchedulers, fetchAndStoreAllTraces } from './objects' -import { fetchAndStoreAllTopologiesOfProject } from './topology' -import { addScenario, deleteScenario, updateScenario } from '../api/routes/scenarios' -import { fetchPortfolioWithScenarios, watchForPortfolioResults } from './portfolios' - -export function* onOpenScenarioSucceeded(action) { - try { - const project = yield call(getProject, action.projectId) - yield put(addToStore('project', project)) - yield fetchAndStoreAllTopologiesOfProject(project._id) - yield fetchAndStoreAllSchedulers() - yield fetchAndStoreAllTraces() - yield fetchPortfolioWithScenarios(action.portfolioId) - - // TODO Fetch scenario-specific metrics - } catch (error) { - console.error(error) - } -} - -export function* onAddScenario(action) { - try { - const scenario = yield call(addScenario, action.scenario.portfolioId, action.scenario) - yield put(addToStore('scenario', scenario)) - - const scenarioIds = yield select((state) => state.objects.portfolio[action.scenario.portfolioId].scenarioIds) - yield put( - addPropToStoreObject('portfolio', action.scenario.portfolioId, { - scenarioIds: scenarioIds.concat([scenario._id]), - }) - ) - yield watchForPortfolioResults() - } catch (error) { - console.error(error) - } -} - -export function* onUpdateScenario(action) { - try { - const scenario = yield call(updateScenario, action.scenario._id, action.scenario) - yield put(addToStore('scenario', scenario)) - } catch (error) { - console.error(error) - } -} - -export function* onDeleteScenario(action) { - try { - yield call(deleteScenario, action.id) - - const currentPortfolioId = yield select((state) => state.currentPortfolioId) - const scenarioIds = yield select((state) => state.objects.portfolio[currentPortfolioId].scenarioIds) - - yield put( - addPropToStoreObject('scenario', currentPortfolioId, { - scenarioIds: scenarioIds.filter((id) => id !== action.id), - }) - ) - } catch (error) { - console.error(error) - } -} diff --git a/opendc-web/opendc-web-ui/src/sagas/topology.js b/opendc-web/opendc-web-ui/src/sagas/topology.js deleted file mode 100644 index bba1ebb1..00000000 --- a/opendc-web/opendc-web-ui/src/sagas/topology.js +++ /dev/null @@ -1,311 +0,0 @@ -import { call, put, select } from 'redux-saga/effects' -import { goDownOneInteractionLevel } from '../actions/interaction-level' -import { - addIdToStoreObjectListProp, - addPropToStoreObject, - addToStore, - removeIdFromStoreObjectListProp, -} from '../actions/objects' -import { - cancelNewRoomConstructionSucceeded, - setCurrentTopology, - startNewRoomConstructionSucceeded, -} from '../actions/topology/building' -import { - DEFAULT_RACK_POWER_CAPACITY, - DEFAULT_RACK_SLOT_CAPACITY, - MAX_NUM_UNITS_PER_MACHINE, -} from '../components/app/map/MapConstants' -import { fetchAndStoreTopology, getTopologyAsObject, updateTopologyOnServer } from './objects' -import { uuid } from 'uuidv4' -import { addTopology, deleteTopology } from '../api/routes/topologies' - -export function* fetchAndStoreAllTopologiesOfProject(projectId, setTopology = false) { - try { - const project = yield select((state) => state.objects.project[projectId]) - - for (let i in project.topologyIds) { - yield fetchAndStoreTopology(project.topologyIds[i]) - } - - if (setTopology) { - yield put(setCurrentTopology(project.topologyIds[0])) - } - } catch (error) { - console.error(error) - } -} - -export function* onAddTopology(action) { - try { - const currentProjectId = yield select((state) => state.currentProjectId) - - let topologyToBeCreated - if (action.duplicateId) { - topologyToBeCreated = yield getTopologyAsObject(action.duplicateId, false) - topologyToBeCreated = Object.assign({}, topologyToBeCreated, { - name: action.name, - }) - } else { - topologyToBeCreated = { name: action.name, rooms: [] } - } - - const topology = yield call( - addTopology, - Object.assign({}, topologyToBeCreated, { - projectId: currentProjectId, - }) - ) - yield fetchAndStoreTopology(topology._id) - - const topologyIds = yield select((state) => state.objects.project[currentProjectId].topologyIds) - yield put( - addPropToStoreObject('project', currentProjectId, { - topologyIds: topologyIds.concat([topology._id]), - }) - ) - yield put(setCurrentTopology(topology._id)) - } catch (error) { - console.error(error) - } -} - -export function* onDeleteTopology(action) { - try { - const currentProjectId = yield select((state) => state.currentProjectId) - const topologyIds = yield select((state) => state.objects.project[currentProjectId].topologyIds) - const currentTopologyId = yield select((state) => state.currentTopologyId) - if (currentTopologyId === action.id) { - yield put(setCurrentTopology(topologyIds.filter((t) => t !== action.id)[0])) - } - - yield call(deleteTopology, action.id) - - yield put( - addPropToStoreObject('project', currentProjectId, { - topologyIds: topologyIds.filter((id) => id !== action.id), - }) - ) - } catch (error) { - console.error(error) - } -} - -export function* onStartNewRoomConstruction() { - try { - const topologyId = yield select((state) => state.currentTopologyId) - const room = { - _id: uuid(), - name: 'Room', - topologyId, - tileIds: [], - } - yield put(addToStore('room', room)) - yield put(addIdToStoreObjectListProp('topology', topologyId, 'roomIds', room._id)) - yield updateTopologyOnServer(topologyId) - yield put(startNewRoomConstructionSucceeded(room._id)) - } catch (error) { - console.error(error) - } -} - -export function* onCancelNewRoomConstruction() { - try { - const topologyId = yield select((state) => state.currentTopologyId) - const roomId = yield select((state) => state.construction.currentRoomInConstruction) - yield put(removeIdFromStoreObjectListProp('topology', topologyId, 'roomIds', roomId)) - // TODO remove room from store, too - yield updateTopologyOnServer(topologyId) - yield put(cancelNewRoomConstructionSucceeded()) - } catch (error) { - console.error(error) - } -} - -export function* onAddTile(action) { - try { - const topologyId = yield select((state) => state.currentTopologyId) - const roomId = yield select((state) => state.construction.currentRoomInConstruction) - const tile = { - _id: uuid(), - roomId, - positionX: action.positionX, - positionY: action.positionY, - } - yield put(addToStore('tile', tile)) - yield put(addIdToStoreObjectListProp('room', roomId, 'tileIds', tile._id)) - yield updateTopologyOnServer(topologyId) - } catch (error) { - console.error(error) - } -} - -export function* onDeleteTile(action) { - try { - const topologyId = yield select((state) => state.currentTopologyId) - const roomId = yield select((state) => state.construction.currentRoomInConstruction) - yield put(removeIdFromStoreObjectListProp('room', roomId, 'tileIds', action.tileId)) - yield updateTopologyOnServer(topologyId) - } catch (error) { - console.error(error) - } -} - -export function* onEditRoomName(action) { - try { - const topologyId = yield select((state) => state.currentTopologyId) - const roomId = yield select((state) => state.interactionLevel.roomId) - const room = Object.assign({}, yield select((state) => state.objects.room[roomId])) - room.name = action.name - yield put(addPropToStoreObject('room', roomId, { name: action.name })) - yield updateTopologyOnServer(topologyId) - } catch (error) { - console.error(error) - } -} - -export function* onDeleteRoom() { - try { - const topologyId = yield select((state) => state.currentTopologyId) - const roomId = yield select((state) => state.interactionLevel.roomId) - yield put(goDownOneInteractionLevel()) - yield put(removeIdFromStoreObjectListProp('topology', topologyId, 'roomIds', roomId)) - yield updateTopologyOnServer(topologyId) - } catch (error) { - console.error(error) - } -} - -export function* onEditRackName(action) { - try { - const topologyId = yield select((state) => state.currentTopologyId) - const rackId = yield select((state) => state.objects.tile[state.interactionLevel.tileId].rackId) - const rack = Object.assign({}, yield select((state) => state.objects.rack[rackId])) - rack.name = action.name - yield put(addPropToStoreObject('rack', rackId, { name: action.name })) - yield updateTopologyOnServer(topologyId) - } catch (error) { - console.error(error) - } -} - -export function* onDeleteRack() { - try { - const topologyId = yield select((state) => state.currentTopologyId) - const tileId = yield select((state) => state.interactionLevel.tileId) - yield put(goDownOneInteractionLevel()) - yield put(addPropToStoreObject('tile', tileId, { rackId: undefined })) - yield updateTopologyOnServer(topologyId) - } catch (error) { - console.error(error) - } -} - -export function* onAddRackToTile(action) { - try { - const topologyId = yield select((state) => state.currentTopologyId) - const rack = { - _id: uuid(), - 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, { rackId: rack._id })) - yield updateTopologyOnServer(topologyId) - } catch (error) { - console.error(error) - } -} - -export function* onAddMachine(action) { - try { - const topologyId = yield select((state) => state.currentTopologyId) - const rackId = yield select((state) => state.objects.tile[state.interactionLevel.tileId].rackId) - const rack = yield select((state) => state.objects.rack[rackId]) - - const machine = { - _id: uuid(), - rackId, - position: action.position, - 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 })) - yield updateTopologyOnServer(topologyId) - } catch (error) { - console.error(error) - } -} - -export function* onDeleteMachine() { - try { - const topologyId = yield select((state) => state.currentTopologyId) - 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].rackId]) - const machineIds = [...rack.machineIds] - machineIds[position - 1] = null - yield put(goDownOneInteractionLevel()) - yield put(addPropToStoreObject('rack', rack._id, { machineIds })) - yield updateTopologyOnServer(topologyId) - } catch (error) { - console.error(error) - } -} - -export function* onAddUnit(action) { - try { - const topologyId = yield select((state) => state.currentTopologyId) - 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].rackId].machineIds[position - 1]] - ) - - if (machine[action.unitType + 'Ids'].length >= MAX_NUM_UNITS_PER_MACHINE) { - return - } - - const units = [...machine[action.unitType + 'Ids'], action.id] - yield put( - addPropToStoreObject('machine', machine._id, { - [action.unitType + 'Ids']: units, - }) - ) - yield updateTopologyOnServer(topologyId) - } catch (error) { - console.error(error) - } -} - -export function* onDeleteUnit(action) { - try { - const topologyId = yield select((state) => state.currentTopologyId) - 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].rackId].machineIds[position - 1]] - ) - const unitIds = machine[action.unitType + 'Ids'].slice() - unitIds.splice(action.index, 1) - - yield put( - addPropToStoreObject('machine', machine._id, { - [action.unitType + 'Ids']: unitIds, - }) - ) - yield updateTopologyOnServer(topologyId) - } catch (error) { - console.error(error) - } -} diff --git a/opendc-web/opendc-web-ui/src/sagas/users.js b/opendc-web/opendc-web-ui/src/sagas/users.js deleted file mode 100644 index 74e652f6..00000000 --- a/opendc-web/opendc-web-ui/src/sagas/users.js +++ /dev/null @@ -1,44 +0,0 @@ -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 } from '../api/routes/users' -import { saveAuthLocalStorage } from '../auth/index' -import { fetchAndStoreProject, 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 user = yield call(fetchAndStoreUser, action.userId) - - for (const authorization of user.authorizations) { - authorization.userId = action.userId - yield put(addToStore('authorization', authorization)) - yield fetchAndStoreProject(authorization.projectId) - } - - const authorizationIds = user.authorizations.map((authorization) => [action.userId, authorization.projectId]) - - yield put(fetchAuthorizationsOfCurrentUserSucceeded(authorizationIds)) - } catch (error) { - console.error(error) - } -} diff --git a/opendc-web/opendc-web-ui/src/shapes/index.js b/opendc-web/opendc-web-ui/src/shapes.js index 9fab6f5d..abdf146e 100644 --- a/opendc-web/opendc-web-ui/src/shapes/index.js +++ b/opendc-web/opendc-web-ui/src/shapes.js @@ -1,8 +1,28 @@ -import PropTypes from 'prop-types' +/* + * Copyright (c) 2021 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. + */ -const Shapes = {} +import PropTypes from 'prop-types' -Shapes.User = PropTypes.shape({ +export const User = PropTypes.shape({ _id: PropTypes.string.isRequired, googleId: PropTypes.string.isRequired, email: PropTypes.string.isRequired, @@ -11,7 +31,7 @@ Shapes.User = PropTypes.shape({ authorizations: PropTypes.array.isRequired, }) -Shapes.Project = PropTypes.shape({ +export const Project = PropTypes.shape({ _id: PropTypes.string.isRequired, name: PropTypes.string.isRequired, datetimeCreated: PropTypes.string.isRequired, @@ -20,15 +40,7 @@ Shapes.Project = PropTypes.shape({ portfolioIds: PropTypes.array.isRequired, }) -Shapes.Authorization = PropTypes.shape({ - userId: PropTypes.string.isRequired, - user: Shapes.User, - projectId: PropTypes.string.isRequired, - project: Shapes.Project, - authorizationLevel: PropTypes.string.isRequired, -}) - -Shapes.ProcessingUnit = PropTypes.shape({ +export const ProcessingUnit = PropTypes.shape({ _id: PropTypes.string.isRequired, name: PropTypes.string.isRequired, clockRateMhz: PropTypes.number.isRequired, @@ -36,7 +48,7 @@ Shapes.ProcessingUnit = PropTypes.shape({ energyConsumptionW: PropTypes.number.isRequired, }) -Shapes.StorageUnit = PropTypes.shape({ +export const StorageUnit = PropTypes.shape({ _id: PropTypes.string.isRequired, name: PropTypes.string.isRequired, speedMbPerS: PropTypes.number.isRequired, @@ -44,59 +56,52 @@ Shapes.StorageUnit = PropTypes.shape({ energyConsumptionW: PropTypes.number.isRequired, }) -Shapes.Machine = PropTypes.shape({ +export const Machine = PropTypes.shape({ _id: PropTypes.string.isRequired, - rackId: PropTypes.string.isRequired, position: PropTypes.number.isRequired, - cpuIds: PropTypes.arrayOf(PropTypes.string.isRequired), - cpus: PropTypes.arrayOf(Shapes.ProcessingUnit), - gpuIds: PropTypes.arrayOf(PropTypes.string.isRequired), - gpus: PropTypes.arrayOf(Shapes.ProcessingUnit), - memoryIds: PropTypes.arrayOf(PropTypes.string.isRequired), - memories: PropTypes.arrayOf(Shapes.StorageUnit), - storageIds: PropTypes.arrayOf(PropTypes.string.isRequired), - storages: PropTypes.arrayOf(Shapes.StorageUnit), + cpus: PropTypes.arrayOf(PropTypes.string), + gpus: PropTypes.arrayOf(PropTypes.string), + memories: PropTypes.arrayOf(PropTypes.string), + storages: PropTypes.arrayOf(PropTypes.string), }) -Shapes.Rack = PropTypes.shape({ +export const Rack = PropTypes.shape({ _id: PropTypes.string.isRequired, capacity: PropTypes.number.isRequired, powerCapacityW: PropTypes.number.isRequired, - machines: PropTypes.arrayOf(Shapes.Machine), + machines: PropTypes.arrayOf(PropTypes.string), }) -Shapes.Tile = PropTypes.shape({ +export const Tile = PropTypes.shape({ _id: PropTypes.string.isRequired, - roomId: PropTypes.string.isRequired, positionX: PropTypes.number.isRequired, positionY: PropTypes.number.isRequired, - rackId: PropTypes.string, - rack: Shapes.Rack, + rack: PropTypes.string, }) -Shapes.Room = PropTypes.shape({ +export const Room = PropTypes.shape({ _id: PropTypes.string.isRequired, name: PropTypes.string.isRequired, - tiles: PropTypes.arrayOf(Shapes.Tile), + tiles: PropTypes.arrayOf(PropTypes.string), }) -Shapes.Topology = PropTypes.shape({ +export const Topology = PropTypes.shape({ _id: PropTypes.string.isRequired, name: PropTypes.string.isRequired, - rooms: PropTypes.arrayOf(Shapes.Room), + rooms: PropTypes.arrayOf(PropTypes.string), }) -Shapes.Scheduler = PropTypes.shape({ +export const Scheduler = PropTypes.shape({ name: PropTypes.string.isRequired, }) -Shapes.Trace = PropTypes.shape({ +export const Trace = PropTypes.shape({ _id: PropTypes.string.isRequired, name: PropTypes.string.isRequired, type: PropTypes.string.isRequired, }) -Shapes.Portfolio = PropTypes.shape({ +export const Portfolio = PropTypes.shape({ _id: PropTypes.string.isRequired, projectId: PropTypes.string.isRequired, name: PropTypes.string.isRequired, @@ -107,7 +112,7 @@ Shapes.Portfolio = PropTypes.shape({ }).isRequired, }) -Shapes.Scenario = PropTypes.shape({ +export const Scenario = PropTypes.shape({ _id: PropTypes.string.isRequired, portfolioId: PropTypes.string.isRequired, name: PropTypes.string.isRequired, @@ -116,33 +121,33 @@ Shapes.Scenario = PropTypes.shape({ }).isRequired, trace: PropTypes.shape({ traceId: PropTypes.string.isRequired, - trace: Shapes.Trace, + trace: Trace, loadSamplingFraction: PropTypes.number.isRequired, }).isRequired, topology: PropTypes.shape({ topologyId: PropTypes.string.isRequired, - topology: Shapes.Topology, + topology: Topology, }).isRequired, operational: PropTypes.shape({ failuresEnabled: PropTypes.bool.isRequired, performanceInterferenceEnabled: PropTypes.bool.isRequired, schedulerName: PropTypes.string.isRequired, - scheduler: Shapes.Scheduler, + scheduler: Scheduler, }).isRequired, results: PropTypes.object, }) -Shapes.WallSegment = PropTypes.shape({ +export const WallSegment = PropTypes.shape({ startPosX: PropTypes.number.isRequired, startPosY: PropTypes.number.isRequired, isHorizontal: PropTypes.bool.isRequired, length: PropTypes.number.isRequired, }) -Shapes.InteractionLevel = PropTypes.shape({ +export const InteractionLevel = PropTypes.shape({ mode: PropTypes.string.isRequired, roomId: PropTypes.string, rackId: PropTypes.string, }) -export default Shapes +export const Status = PropTypes.oneOf(['idle', 'loading', 'error', 'success']) diff --git a/opendc-web/opendc-web-ui/src/shortcuts/keymap.js b/opendc-web/opendc-web-ui/src/shortcuts/keymap.js deleted file mode 100644 index 797340d7..00000000 --- a/opendc-web/opendc-web-ui/src/shortcuts/keymap.js +++ /dev/null @@ -1,10 +0,0 @@ -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/opendc-web/opendc-web-ui/src/store/configure-store.js b/opendc-web/opendc-web-ui/src/store/configure-store.js deleted file mode 100644 index d8f343ed..00000000 --- a/opendc-web/opendc-web-ui/src/store/configure-store.js +++ /dev/null @@ -1,35 +0,0 @@ -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/opendc-web/opendc-web-ui/src/store/middlewares/dummy-middleware.js b/opendc-web/opendc-web-ui/src/store/middlewares/dummy-middleware.js deleted file mode 100644 index 5ba35691..00000000 --- a/opendc-web/opendc-web-ui/src/store/middlewares/dummy-middleware.js +++ /dev/null @@ -1,3 +0,0 @@ -export const dummyMiddleware = (store) => (next) => (action) => { - next(action) -} diff --git a/opendc-web/opendc-web-ui/src/store/middlewares/viewport-adjustment.js b/opendc-web/opendc-web-ui/src/store/middlewares/viewport-adjustment.js deleted file mode 100644 index b4472c54..00000000 --- a/opendc-web/opendc-web-ui/src/store/middlewares/viewport-adjustment.js +++ /dev/null @@ -1,73 +0,0 @@ -import { SET_MAP_DIMENSIONS, setMapPosition, setMapScale } from '../../actions/map' -import { SET_CURRENT_TOPOLOGY } 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 topologyId = '-1' - let mapDimensions = {} - if (action.type === SET_CURRENT_TOPOLOGY && action.topologyId !== '-1') { - topologyId = action.topologyId - mapDimensions = state.map.dimensions - } else if (action.type === SET_MAP_DIMENSIONS && state.currentTopologyId !== '-1') { - topologyId = state.currentTopologyId - mapDimensions = { width: action.width, height: action.height } - } - - if (topologyId !== '-1') { - const roomIds = state.objects.topology[topologyId].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/opendc-web/opendc-web-ui/src/style-globals/_mixins.sass b/opendc-web/opendc-web-ui/src/style-globals/_mixins.sass deleted file mode 100644 index d0a8d1ac..00000000 --- a/opendc-web/opendc-web-ui/src/style-globals/_mixins.sass +++ /dev/null @@ -1,21 +0,0 @@ -=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/opendc-web/opendc-web-ui/src/style-globals/_variables.sass b/opendc-web/opendc-web-ui/src/style-globals/_variables.sass deleted file mode 100644 index 7553caa0..00000000 --- a/opendc-web/opendc-web-ui/src/style-globals/_variables.sass +++ /dev/null @@ -1,31 +0,0 @@ -// 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: 350px -$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/opendc-web/opendc-web-ui/src/style/index.scss b/opendc-web/opendc-web-ui/src/style/index.scss new file mode 100644 index 00000000..ff84e24a --- /dev/null +++ b/opendc-web/opendc-web-ui/src/style/index.scss @@ -0,0 +1,36 @@ +/*! + * Copyright (c) 2021 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. + */ + +body, +#__next { + margin: 0; + padding: 0; + width: 100%; + height: 100%; + + background: #eee; +} + +.full-height { + position: relative; + height: 100% !important; +} diff --git a/opendc-web/opendc-web-ui/src/util/authorizations.js b/opendc-web/opendc-web-ui/src/util/authorizations.js index 4086b35d..ce5d34b6 100644 --- a/opendc-web/opendc-web-ui/src/util/authorizations.js +++ b/opendc-web/opendc-web-ui/src/util/authorizations.js @@ -1,7 +1,11 @@ +import HomeIcon from '@patternfly/react-icons/dist/js/icons/home-icon' +import EditIcon from '@patternfly/react-icons/dist/js/icons/edit-icon' +import EyeIcon from '@patternfly/react-icons/dist/js/icons/eye-icon' + export const AUTH_ICON_MAP = { - OWN: 'home', - EDIT: 'pencil', - VIEW: 'eye', + OWN: HomeIcon, + EDIT: EditIcon, + VIEW: EyeIcon, } export const AUTH_DESCRIPTION_MAP = { diff --git a/opendc-web/opendc-web-ui/src/util/available-metrics.js b/opendc-web/opendc-web-ui/src/util/available-metrics.js index 807bc0c1..b21ab150 100644 --- a/opendc-web/opendc-web-ui/src/util/available-metrics.js +++ b/opendc-web/opendc-web-ui/src/util/available-metrics.js @@ -1,12 +1,28 @@ +export const METRIC_GROUPS = { + 'Host Metrics': [ + 'total_overcommitted_burst', + 'total_power_draw', + 'total_failure_vm_slices', + 'total_granted_burst', + 'total_interfered_burst', + 'total_requested_burst', + 'mean_cpu_usage', + 'mean_cpu_demand', + 'mean_num_deployed_images', + 'max_num_deployed_images', + ], + 'Compute Service Metrics': ['total_vms_submitted', 'total_vms_queued', 'total_vms_finished', 'total_vms_failed'], +} + export const AVAILABLE_METRICS = [ + 'mean_cpu_usage', + 'mean_cpu_demand', + 'total_requested_burst', + 'total_granted_burst', 'total_overcommitted_burst', + 'total_interfered_burst', 'total_power_draw', 'total_failure_vm_slices', - 'total_granted_burst', - 'total_interfered_burst', - 'total_requested_burst', - 'mean_cpu_usage', - 'mean_cpu_demand', 'mean_num_deployed_images', 'max_num_deployed_images', 'total_vms_submitted', @@ -65,3 +81,22 @@ export const METRIC_UNITS = { total_vms_finished: 'VMs', total_vms_failed: 'VMs', } + +export const METRIC_DESCRIPTIONS = { + total_overcommitted_burst: + 'The total CPU clock cycles lost due to overcommitting of resources. This metric is an indicator for resource overload.', + total_requested_burst: 'The total CPU clock cycles that were requested by all virtual machines.', + total_granted_burst: 'The total CPU clock cycles executed by the hosts.', + total_interfered_burst: 'The total CPU clock cycles lost due to resource interference between virtual machines.', + total_power_draw: 'The average power usage in watts.', + mean_cpu_usage: 'The average amount of CPU clock cycles consumed by all virtual machines on a host.', + mean_cpu_demand: 'The average amount of CPU clock cycles requested by all powered on virtual machines on a host.', + mean_num_deployed_images: 'The average number of virtual machines deployed on a host.', + max_num_deployed_images: 'The maximum number of virtual machines deployed at any time.', + total_failure_vm_slices: 'The total amount of CPU clock cycles lost due to failure.', + total_vms_submitted: 'The total number of virtual machines scheduled by the compute service.', + total_vms_queued: + 'The maximum number of virtual machines waiting to be scheduled by the compute service at any point.', + total_vms_finished: 'The total number of virtual machines that completed successfully.', + total_vms_failed: 'The total number of virtual machines that failed during execution.', +} diff --git a/opendc-web/opendc-web-ui/src/util/date-time.js b/opendc-web/opendc-web-ui/src/util/date-time.js index 66efdf5b..7e2f6623 100644 --- a/opendc-web/opendc-web-ui/src/util/date-time.js +++ b/opendc-web/opendc-web-ui/src/util/date-time.js @@ -7,19 +7,7 @@ * @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') + return formatDateTime(new Date(dateTimeString)) } /** diff --git a/opendc-web/opendc-web-ui/src/util/date-time.test.js b/opendc-web/opendc-web-ui/src/util/date-time.test.js index 3d95eba6..431e39f7 100644 --- a/opendc-web/opendc-web-ui/src/util/date-time.test.js +++ b/opendc-web/opendc-web-ui/src/util/date-time.test.js @@ -1,18 +1,4 @@ -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) - }) -}) +import { convertSecondsToFormattedTime } from './date-time' describe('tick formatting', () => { it("returns '0s' for numbers <= 0", () => { diff --git a/opendc-web/opendc-web-ui/src/util/effect-ref.js b/opendc-web/opendc-web-ui/src/util/effect-ref.js new file mode 100644 index 00000000..cda0324b --- /dev/null +++ b/opendc-web/opendc-web-ui/src/util/effect-ref.js @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2021 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. + */ + +import { useCallback, useRef } from 'react' + +const noop = () => {} + +/** + * A hook that will invoke the specified callback when the reference returned by this function is initialized. + * The callback can return an optional clean up function. + */ +export function useEffectRef(callback) { + const disposeRef = useRef(noop) + return useCallback((element) => { + disposeRef.current() + disposeRef.current = noop + + if (element) { + disposeRef.current = callback(element) || noop + } + }, []) // eslint-disable-line react-hooks/exhaustive-deps +} diff --git a/opendc-web/opendc-web-ui/src/util/sidebar-space.js b/opendc-web/opendc-web-ui/src/util/sidebar-space.js deleted file mode 100644 index ef09d40a..00000000 --- a/opendc-web/opendc-web-ui/src/util/sidebar-space.js +++ /dev/null @@ -1,2 +0,0 @@ -export const isCollapsible = (location) => - location.pathname.indexOf('portfolios') === -1 && location.pathname.indexOf('scenarios') === -1 diff --git a/opendc-web/opendc-web-ui/src/util/state-utils.js b/opendc-web/opendc-web-ui/src/util/state-utils.js deleted file mode 100644 index e5b695c3..00000000 --- a/opendc-web/opendc-web-ui/src/util/state-utils.js +++ /dev/null @@ -1,6 +0,0 @@ -export const getState = (dispatch) => - new Promise((resolve) => { - dispatch((dispatch, getState) => { - resolve(getState()) - }) - }) diff --git a/opendc-web/opendc-web-ui/src/util/tile-calculations.js b/opendc-web/opendc-web-ui/src/util/tile-calculations.js index 764ae6ac..374ca48c 100644 --- a/opendc-web/opendc-web-ui/src/util/tile-calculations.js +++ b/opendc-web/opendc-web-ui/src/util/tile-calculations.js @@ -18,8 +18,8 @@ function getWallSegments(tiles) { } let doInsert = true - for (let tileIndex in tiles) { - if (tiles[tileIndex].positionX === x + dX && tiles[tileIndex].positionY === y + dY) { + for (const tile of tiles) { + if (tile.positionX === x + dX && tile.positionY === y + dY) { doInsert = false break } diff --git a/opendc-web/opendc-web-ui/src/util/timeline.js b/opendc-web/opendc-web-ui/src/util/timeline.js deleted file mode 100644 index 7c8a3ef0..00000000 --- a/opendc-web/opendc-web-ui/src/util/timeline.js +++ /dev/null @@ -1,9 +0,0 @@ -export function convertTickToPercentage(tick, maxTick) { - if (maxTick === 0) { - return '0%' - } else if (tick > maxTick) { - return (maxTick / (maxTick + 1)) * 100 + '%' - } - - return (tick / (maxTick + 1)) * 100 + '%' -} diff --git a/opendc-web/opendc-web-ui/src/util/topology-schema.js b/opendc-web/opendc-web-ui/src/util/topology-schema.js new file mode 100644 index 00000000..7779ccfe --- /dev/null +++ b/opendc-web/opendc-web-ui/src/util/topology-schema.js @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2021 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. + */ + +import { schema } from 'normalizr' + +const Cpu = new schema.Entity('cpus', {}, { idAttribute: '_id' }) +const Gpu = new schema.Entity('gpus', {}, { idAttribute: '_id' }) +const Memory = new schema.Entity('memories', {}, { idAttribute: '_id' }) +const Storage = new schema.Entity('storages', {}, { idAttribute: '_id' }) + +export const Machine = new schema.Entity( + 'machines', + { + cpus: [Cpu], + gpus: [Gpu], + memories: [Memory], + storages: [Storage], + }, + { idAttribute: '_id' } +) + +export const Rack = new schema.Entity('racks', { machines: [Machine] }, { idAttribute: '_id' }) + +export const Tile = new schema.Entity('tiles', { rack: Rack }, { idAttribute: '_id' }) + +export const Room = new schema.Entity('rooms', { tiles: [Tile] }, { idAttribute: '_id' }) + +export const Topology = new schema.Entity('topologies', { rooms: [Room] }, { idAttribute: '_id' }) diff --git a/opendc-web/opendc-web-ui/yarn.lock b/opendc-web/opendc-web-ui/yarn.lock index 10f974f1..1c898dd9 100644 --- a/opendc-web/opendc-web-ui/yarn.lock +++ b/opendc-web/opendc-web-ui/yarn.lock @@ -2,1330 +2,314 @@ # 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.3", "@babel/code-frame@^7.8.3": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.3.tgz#324bcfd8d35cd3d47dae18cde63d752086435e9a" - integrity sha512-fDx9eNW0qz0WkUeqL6tXEXzVlPh6Y5aCDEZesl0xBGA8ndRukX91Uk44ZqnkECp01NAZUdCAl+aiQNGi0k88Eg== - dependencies: - "@babel/highlight" "^7.10.3" - -"@babel/compat-data@^7.10.1", "@babel/compat-data@^7.10.3", "@babel/compat-data@^7.9.0": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.10.3.tgz#9af3e033f36e8e2d6e47570db91e64a846f5d382" - integrity sha512-BDIfJ9uNZuI0LajPfoYV28lX8kyCPMHY6uY4WH1lJdcicmAfxCK5ASzaeV0D/wsUaRH/cLk+amuxtC37sZ8TUg== - 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.3" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.10.3.tgz#73b0e8ddeec1e3fdd7a2de587a60e17c440ec77e" - integrity sha512-5YqWxYE3pyhIi84L84YcwjeEgS+fa7ZjK6IBVGTjDVfm64njkR2lfDhVR5OudLk8x2GK59YoSyVv+L/03k1q9w== - dependencies: - "@babel/code-frame" "^7.10.3" - "@babel/generator" "^7.10.3" - "@babel/helper-module-transforms" "^7.10.1" - "@babel/helpers" "^7.10.1" - "@babel/parser" "^7.10.3" - "@babel/template" "^7.10.3" - "@babel/traverse" "^7.10.3" - "@babel/types" "^7.10.3" - 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.3", "@babel/generator@^7.4.0", "@babel/generator@^7.9.0": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.10.3.tgz#32b9a0d963a71d7a54f5f6c15659c3dbc2a523a5" - integrity sha512-drt8MUHbEqRzNR0xnF8nMehbY11b1SDkRw03PSNH/3Rb2Z35oxkddVSi3rcaak0YJQ86PCuE7Qx1jSFhbLNBMA== - dependencies: - "@babel/types" "^7.10.3" - 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.3" - resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.3.tgz#4e9012d6701bef0030348d7f9c808209bd3e8687" - integrity sha512-lo4XXRnBlU6eRM92FkiZxpo1xFLmv3VsPFk61zJKMm7XYJfwqXHsYJTY6agoc4a3L8QPw1HqWehO18coZgbT6A== - dependencies: - "@babel/helper-explode-assignable-expression" "^7.10.3" - "@babel/types" "^7.10.3" - -"@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.3": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/helper-builder-react-jsx/-/helper-builder-react-jsx-7.10.3.tgz#62c4b7bb381153a0a5f8d83189b94b9fb5384fc5" - integrity sha512-vkxmuFvmovtqTZknyMGj9+uQAZzz5Z9mrbnkJnPkaYGfKTaSsYcjQdXP0lgrWLVh8wU6bCjOmXOpx+kqUi+S5Q== - dependencies: - "@babel/helper-annotate-as-pure" "^7.10.1" - "@babel/types" "^7.10.3" - -"@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.10.3", "@babel/helper-create-class-features-plugin@^7.8.3": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.10.3.tgz#2783daa6866822e3d5ed119163b50f0fc3ae4b35" - integrity sha512-iRT9VwqtdFmv7UheJWthGc/h2s7MqoweBF9RUj77NFZsg9VfISvBTum3k6coAhJ8RWv2tj3yUjA03HxPd0vfpQ== - dependencies: - "@babel/helper-function-name" "^7.10.3" - "@babel/helper-member-expression-to-functions" "^7.10.3" - "@babel/helper-optimise-call-expression" "^7.10.3" - "@babel/helper-plugin-utils" "^7.10.3" - "@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.3": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.10.3.tgz#d27120a5e57c84727b30944549b2dfeca62401a8" - integrity sha512-bxRzDi4Sin/k0drWCczppOhov1sBSdBvXJObM1NLHQzjhXhwRtn7aRWGvLJWCYbuu2qUk3EKs6Ci9C9ps8XokQ== - dependencies: - "@babel/helper-function-name" "^7.10.3" - "@babel/types" "^7.10.3" - lodash "^4.17.13" - -"@babel/helper-explode-assignable-expression@^7.10.3": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.10.3.tgz#9dc14f0cfa2833ea830a9c8a1c742b6e7461b05e" - integrity sha512-0nKcR64XrOC3lsl+uhD15cwxPvaB6QKUDlD84OT9C3myRbhJqTMYir69/RWItUvHpharv0eJ/wk7fl34ONSwZw== - dependencies: - "@babel/traverse" "^7.10.3" - "@babel/types" "^7.10.3" - -"@babel/helper-function-name@^7.10.1", "@babel/helper-function-name@^7.10.3": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.10.3.tgz#79316cd75a9fa25ba9787ff54544307ed444f197" - integrity sha512-FvSj2aiOd8zbeqijjgqdMDSyxsGHaMt5Tr0XjQsGKHD3/1FP3wksjnLAWzxw7lvXiej8W1Jt47SKTZ6upQNiRw== - dependencies: - "@babel/helper-get-function-arity" "^7.10.3" - "@babel/template" "^7.10.3" - "@babel/types" "^7.10.3" - -"@babel/helper-get-function-arity@^7.10.1", "@babel/helper-get-function-arity@^7.10.3": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.3.tgz#3a28f7b28ccc7719eacd9223b659fdf162e4c45e" - integrity sha512-iUD/gFsR+M6uiy69JA6fzM5seno8oE85IYZdbVVEuQaZlEzMO2MXblh+KSPJgsZAUx0EEbWXU0yJaW7C9CdAVg== - dependencies: - "@babel/types" "^7.10.3" - -"@babel/helper-hoist-variables@^7.10.3": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.3.tgz#d554f52baf1657ffbd7e5137311abc993bb3f068" - integrity sha512-9JyafKoBt5h20Yv1+BXQMdcXXavozI1vt401KBiRc2qzUepbVnd7ogVNymY1xkQN9fekGwfxtotH2Yf5xsGzgg== - dependencies: - "@babel/types" "^7.10.3" - -"@babel/helper-member-expression-to-functions@^7.10.1", "@babel/helper-member-expression-to-functions@^7.10.3": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.10.3.tgz#bc3663ac81ac57c39148fef4c69bf48a77ba8dd6" - integrity sha512-q7+37c4EPLSjNb2NmWOjNwj0+BOyYlssuQ58kHEWk1Z78K5i8vTUsteq78HMieRPQSl/NtpQyJfdjt3qZ5V2vw== - dependencies: - "@babel/types" "^7.10.3" - -"@babel/helper-module-imports@^7.10.1", "@babel/helper-module-imports@^7.10.3", "@babel/helper-module-imports@^7.8.3": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.10.3.tgz#766fa1d57608e53e5676f23ae498ec7a95e1b11a" - integrity sha512-Jtqw5M9pahLSUWA+76nhK9OG8nwYXzhQzVIGFoNaHnXF/r4l7kz4Fl0UAW7B6mqC5myoJiBP5/YQlXQTMfHI9w== - dependencies: - "@babel/types" "^7.10.3" - -"@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", "@babel/helper-optimise-call-expression@^7.10.3": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.3.tgz#f53c4b6783093195b0f69330439908841660c530" - integrity sha512-kT2R3VBH/cnSz+yChKpaKRJQJWxdGoc6SjioRId2wkeV3bK0wLLioFpJROrX0U4xr/NmxSSAWT/9Ih5snwIIzg== - dependencies: - "@babel/types" "^7.10.3" - -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.1", "@babel/helper-plugin-utils@^7.10.3", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.3.tgz#aac45cccf8bc1873b99a85f34bceef3beb5d3244" - integrity sha512-j/+j8NAWUTxOtx4LKHybpSClxHoq6I91DQ/mKgAXn5oNUPIUiGppjPIX3TDtJWPrdfP9Kfl7e4fgVMiQR9VE/g== - -"@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", "@babel/helper-remap-async-to-generator@^7.10.3": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.10.3.tgz#18564f8a6748be466970195b876e8bba3bccf442" - integrity sha512-sLB7666ARbJUGDO60ZormmhQOyqMX/shKBXZ7fy937s+3ID8gSrneMvKSSb+8xIM5V7Vn6uNVtOY1vIm26XLtA== - dependencies: - "@babel/helper-annotate-as-pure" "^7.10.1" - "@babel/helper-wrap-function" "^7.10.1" - "@babel/template" "^7.10.3" - "@babel/traverse" "^7.10.3" - "@babel/types" "^7.10.3" - -"@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.3": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.3.tgz#60d9847f98c4cea1b279e005fdb7c28be5412d15" - integrity sha512-bU8JvtlYpJSBPuj1VUmKpFGaDZuLxASky3LhaKj3bmpSTY6VWooSM8msk+Z0CZoErFye2tlABF6yDkT3FOPAXw== - -"@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.3", "@babel/highlight@^7.8.3": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.10.3.tgz#c633bb34adf07c5c13156692f5922c81ec53f28d" - integrity sha512-Ih9B/u7AtgEnySE2L2F0Xm0GaM729XqqLfHkalTsbjXGyqmf/6M0Cu0WpvqueUlW+xk88BHw9Nkpj49naU+vWw== - dependencies: - "@babel/helper-validator-identifier" "^7.10.3" +"@auth0/auth0-react@^1.7.0": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@auth0/auth0-react/-/auth0-react-1.7.0.tgz#0fbfaf69b7ce5b50fcd82fb087a5fc6fc314e91e" + integrity sha512-D0JVqt8kvB8t+KkYAVyfTXxkMx0bJBTPNiQKWbiSwy9rm1PNVKgbS1/ABpf6DNbPeSdpOzVOQp0NVw5s6JivXw== + dependencies: + "@auth0/auth0-spa-js" "^1.17.1" + +"@auth0/auth0-spa-js@^1.17.1": + version "1.17.1" + resolved "https://registry.yarnpkg.com/@auth0/auth0-spa-js/-/auth0-spa-js-1.17.1.tgz#038f356019d1a0a7a48c694f95cf3cca69082317" + integrity sha512-1XfySBfqZnqrWeaCROZ2IMLD+8rQ9TlfttLv0utS91d4rjIKX1g/zTcLYqP68QnqKKGHaMOIee/eQJLnk/o+ow== + dependencies: + abortcontroller-polyfill "^1.7.3" + browser-tabs-lock "^1.2.14" + core-js "^3.16.1" + es-cookie "^1.3.2" + fast-text-encoding "^1.0.3" + promise-polyfill "^8.2.0" + unfetch "^4.2.0" + +"@babel/code-frame@7.12.11": + version "7.12.11" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f" + integrity sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw== + dependencies: + "@babel/highlight" "^7.10.4" + +"@babel/code-frame@^7.0.0": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.14.5.tgz#23b08d740e83f49c5e59945fbf1b43e80bbf4edb" + integrity sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw== + dependencies: + "@babel/highlight" "^7.14.5" + +"@babel/helper-plugin-utils@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz#5ac822ce97eec46741ab70a517971e443a70c5a9" + integrity sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ== + +"@babel/helper-validator-identifier@^7.14.5", "@babel/helper-validator-identifier@^7.14.9": + version "7.14.9" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz#6654d171b2024f6d8ee151bf2509699919131d48" + integrity sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g== + +"@babel/highlight@^7.10.4", "@babel/highlight@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.14.5.tgz#6861a52f03966405001f6aa534a01a24d99e8cd9" + integrity sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg== + dependencies: + "@babel/helper-validator-identifier" "^7.14.5" chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.10.3", "@babel/parser@^7.4.3", "@babel/parser@^7.7.0", "@babel/parser@^7.9.0": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.10.3.tgz#7e71d892b0d6e7d04a1af4c3c79d72c1f10f5315" - integrity sha512-oJtNJCMFdIMwXGmx+KxuaD7i3b8uS7TTFYW/FNG2BT8m+fmGHoiPYoH0Pe3gya07WuFmM5FCDIr1x0irkD/hyA== - -"@babel/plugin-proposal-async-generator-functions@^7.10.3", "@babel/plugin-proposal-async-generator-functions@^7.8.3": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.10.3.tgz#5a02453d46e5362e2073c7278beab2e53ad7d939" - integrity sha512-WUUWM7YTOudF4jZBAJIW9D7aViYC/Fn0Pln4RIHlQALyno3sXSjqmTA4Zy1TKC2D49RCR8Y/Pn4OIUtEypK3CA== - dependencies: - "@babel/helper-plugin-utils" "^7.10.3" - "@babel/helper-remap-async-to-generator" "^7.10.3" - "@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.3", "@babel/plugin-proposal-object-rest-spread@^7.9.0": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.10.3.tgz#b8d0d22f70afa34ad84b7a200ff772f9b9fce474" - integrity sha512-ZZh5leCIlH9lni5bU/wB/UcjtcVLgR8gc+FAgW2OOY+m9h1II3ItTO1/cewNUcsIDZSYcSaz/rYVls+Fb0ExVQ== - dependencies: - "@babel/helper-plugin-utils" "^7.10.3" - "@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.3", "@babel/plugin-proposal-optional-chaining@^7.9.0": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.10.3.tgz#9a726f94622b653c0a3a7a59cdce94730f526f7c" - integrity sha512-yyG3n9dJ1vZ6v5sfmIlMMZ8azQoqx/5/nZTSWX1td6L1H1bsjzA8TInDChpafCZiJkeOFzp/PtrfigAQXxI1Ng== - dependencies: - "@babel/helper-plugin-utils" "^7.10.3" - "@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.3", "@babel/plugin-transform-classes@^7.9.0": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.10.3.tgz#8d9a656bc3d01f3ff69e1fccb354b0f9d72ac544" - integrity sha512-irEX0ChJLaZVC7FvvRoSIxJlmk0IczFLcwaRXUArBKYHCHbOhe57aG8q3uw/fJsoSXvZhjRX960hyeAGlVBXZw== - dependencies: - "@babel/helper-annotate-as-pure" "^7.10.1" - "@babel/helper-define-map" "^7.10.3" - "@babel/helper-function-name" "^7.10.3" - "@babel/helper-optimise-call-expression" "^7.10.3" - "@babel/helper-plugin-utils" "^7.10.3" - "@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.3", "@babel/plugin-transform-computed-properties@^7.8.3": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.10.3.tgz#d3aa6eef67cb967150f76faff20f0abbf553757b" - integrity sha512-GWzhaBOsdbjVFav96drOz7FzrcEW6AP5nax0gLIpstiFaI3LOb2tAg06TimaWU6YKOfUACK3FVrxPJ4GSc5TgA== - dependencies: - "@babel/helper-plugin-utils" "^7.10.3" - -"@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== +"@babel/plugin-syntax-jsx@7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.14.5.tgz#000e2e25d8673cce49300517a3eda44c263e4201" + integrity sha512-ohuFIsOMXJnbOMRfX7/w7LocdR6R7whhuRD4ax8IipLcLPlZGJKkBxgHp++U4N/vKyU16/YDQr2f5seajD3jIw== dependencies: - "@babel/helper-plugin-utils" "^7.10.1" + "@babel/helper-plugin-utils" "^7.14.5" -"@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== +"@babel/runtime-corejs3@^7.10.2": + version "7.15.4" + resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.15.4.tgz#403139af262b9a6e8f9ba04a6fdcebf8de692bf1" + integrity sha512-lWcAqKeB624/twtTc3w6w/2o9RqJPaNBhPGK6DKLSiwuVWC7WFkypWyNg+CpZoyJH0jVzv1uMtXZ/5/lQOLtCg== 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.3", "@babel/plugin-transform-modules-systemjs@^7.9.0": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.10.3.tgz#004ae727b122b7b146b150d50cba5ffbff4ac56b" - integrity sha512-GWXWQMmE1GH4ALc7YXW56BTh/AlzvDWhUNn9ArFF0+Cz5G8esYlVbXfdyHa1xaD1j+GnBoCeoQNlwtZTVdiG/A== - dependencies: - "@babel/helper-hoist-variables" "^7.10.3" - "@babel/helper-module-transforms" "^7.10.1" - "@babel/helper-plugin-utils" "^7.10.3" - 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.10.3", "@babel/plugin-transform-named-capturing-groups-regex@^7.8.3": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.10.3.tgz#a4f8444d1c5a46f35834a410285f2c901c007ca6" - integrity sha512-I3EH+RMFyVi8Iy/LekQm948Z4Lz4yKT7rK+vuCAeRm0kTa6Z5W7xuhRxDNJv0FPya/her6AUgrDITb70YHtTvA== - 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" + core-js-pure "^3.16.0" + regenerator-runtime "^0.13.4" -"@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== +"@babel/runtime@7.15.3": + version "7.15.3" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.15.3.tgz#2e1c2880ca118e5b2f9988322bd8a7656a32502b" + integrity sha512-OvwMLqNXkCXSz1kSm58sEsNuhqOx/fKpnUnKnFB5v8uDda5bLNEHNgKPvhDN6IU0LDcnHQ90LlJ0Q6jnyBSIBA== dependencies: - "@babel/helper-plugin-utils" "^7.10.1" - "@babel/helper-replace-supers" "^7.10.1" + regenerator-runtime "^0.13.4" -"@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== +"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.9.2": + version "7.15.4" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.15.4.tgz#fd17d16bfdf878e6dd02d19753a39fa8a8d9c84a" + integrity sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw== dependencies: - "@babel/helper-get-function-arity" "^7.10.1" - "@babel/helper-plugin-utils" "^7.10.1" + regenerator-runtime "^0.13.4" -"@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== +"@babel/types@7.15.0": + version "7.15.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.15.0.tgz#61af11f2286c4e9c69ca8deb5f4375a73c72dcbd" + integrity sha512-OBvfqnllOIdX4ojTHpwZbpvz4j3EWyjkZEdmjH0/cgsd6QOdSgU8rLSk6ard/pcW7rlmjdVSX/AWOaORR1uNOQ== dependencies: - "@babel/helper-plugin-utils" "^7.10.1" + "@babel/helper-validator-identifier" "^7.14.9" + to-fast-properties "^2.0.0" -"@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== +"@eslint/eslintrc@^0.4.3": + version "0.4.3" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.3.tgz#9e42981ef035beb3dd49add17acb96e8ff6f394c" + integrity sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw== dependencies: - "@babel/helper-plugin-utils" "^7.10.1" + ajv "^6.12.4" + debug "^4.1.1" + espree "^7.3.0" + globals "^13.9.0" + ignore "^4.0.6" + import-fresh "^3.2.1" + js-yaml "^3.13.1" + minimatch "^3.0.4" + strip-json-comments "^3.1.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" +"@fortawesome/fontawesome-common-types@^0.2.36": + version "0.2.36" + resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.36.tgz#b44e52db3b6b20523e0c57ef8c42d315532cb903" + integrity sha512-a/7BiSgobHAgBWeN7N0w+lAhInrGxksn13uK7231n2m8EDPE3BMCl9NZLTGrj9ZXfCmC6LM0QLqXidIizVQ6yg== -"@babel/plugin-transform-react-display-name@^7.10.1", "@babel/plugin-transform-react-display-name@^7.8.3": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.10.3.tgz#e3c246e1b4f3e52cc7633e237ad9194c0ec482e7" - integrity sha512-dOV44bnSW5KZ6kYF6xSHBth7TFiHHZReYXH/JH3XnFNV+soEL1F5d8JT7AJ3ZBncd19Qul7SN4YpBnyWOnQ8KA== +"@fortawesome/fontawesome-svg-core@^1.2.35": + version "1.2.36" + resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.36.tgz#4f2ea6f778298e0c47c6524ce2e7fd58eb6930e3" + integrity sha512-YUcsLQKYb6DmaJjIHdDWpBIGCcyE/W+p/LMGvjQem55Mm2XWVAP5kWTMKWLv9lwpCVjpLxPyOMOyUocP1GxrtA== dependencies: - "@babel/helper-plugin-utils" "^7.10.3" + "@fortawesome/fontawesome-common-types" "^0.2.36" -"@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== +"@fortawesome/free-brands-svg-icons@^5.15.3": + version "5.15.4" + resolved "https://registry.yarnpkg.com/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-5.15.4.tgz#ec8a44dd383bcdd58aa7d1c96f38251e6fec9733" + integrity sha512-f1witbwycL9cTENJegcmcZRYyawAFbm8+c6IirLmwbbpqz46wyjbQYLuxOc7weXFXfB7QR8/Vd2u5R3q6JYD9g== dependencies: - "@babel/helper-builder-react-jsx-experimental" "^7.10.1" - "@babel/helper-plugin-utils" "^7.10.1" - "@babel/plugin-syntax-jsx" "^7.10.1" + "@fortawesome/fontawesome-common-types" "^0.2.36" -"@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== +"@fortawesome/free-solid-svg-icons@^5.15.3": + version "5.15.4" + resolved "https://registry.yarnpkg.com/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.15.4.tgz#2a68f3fc3ddda12e52645654142b9e4e8fbb6cc5" + integrity sha512-JLmQfz6tdtwxoihXLg6lT78BorrFyCf59SAwBM6qV/0zXyVeDygJVb3fk+j5Qat+Yvcxp1buLTY5iDh1ZSAQ8w== dependencies: - "@babel/helper-plugin-utils" "^7.10.1" - "@babel/plugin-syntax-jsx" "^7.10.1" + "@fortawesome/fontawesome-common-types" "^0.2.36" -"@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== +"@fortawesome/react-fontawesome@^0.1.14": + version "0.1.15" + resolved "https://registry.yarnpkg.com/@fortawesome/react-fontawesome/-/react-fontawesome-0.1.15.tgz#1450b838f905981d721bf07e14e3b52c0e9a91ed" + integrity sha512-/HFHdcoLESxxMkqZAcZ6RXDJ69pVApwdwRos/B2kiMWxDSAX2dFK8Er2/+rG+RsrzWB/dsAyjefLmemgmfE18g== dependencies: - "@babel/helper-plugin-utils" "^7.10.1" - "@babel/plugin-syntax-jsx" "^7.10.1" + prop-types "^15.7.2" -"@babel/plugin-transform-react-jsx@^7.10.1", "@babel/plugin-transform-react-jsx@^7.9.1": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.10.3.tgz#c07ad86b7c159287c89b643f201f59536231048e" - integrity sha512-Y21E3rZmWICRJnvbGVmDLDZ8HfNDIwjGF3DXYHx1le0v0mIHCs0Gv5SavyW5Z/jgAHLaAoJPiwt+Dr7/zZKcOQ== +"@hapi/accept@5.0.2": + version "5.0.2" + resolved "https://registry.yarnpkg.com/@hapi/accept/-/accept-5.0.2.tgz#ab7043b037e68b722f93f376afb05e85c0699523" + integrity sha512-CmzBx/bXUR8451fnZRuZAJRlzgm0Jgu5dltTX/bszmR2lheb9BpyN47Q1RbaGTsvFzn0PXAEs+lXDKfshccYZw== dependencies: - "@babel/helper-builder-react-jsx" "^7.10.3" - "@babel/helper-builder-react-jsx-experimental" "^7.10.1" - "@babel/helper-plugin-utils" "^7.10.3" - "@babel/plugin-syntax-jsx" "^7.10.1" + "@hapi/boom" "9.x.x" + "@hapi/hoek" "9.x.x" -"@babel/plugin-transform-react-pure-annotations@^7.10.1": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.10.3.tgz#97840981673fcb0df2cc33fb25b56cc421f7deef" - integrity sha512-n/fWYGqvTl7OLZs/QcWaKMFdADPvC3V6jYuEOpPyvz97onsW9TXn196fHnHW1ZgkO20/rxLOgKnEtN1q9jkgqA== +"@hapi/boom@9.x.x": + version "9.1.4" + resolved "https://registry.yarnpkg.com/@hapi/boom/-/boom-9.1.4.tgz#1f9dad367c6a7da9f8def24b4a986fc5a7bd9db6" + integrity sha512-Ls1oH8jaN1vNsqcaHVYJrKmgMcKsC1wcp8bujvXrHaAqD2iDYq3HoOwsxwo09Cuda5R5nC0o0IxlrlTuvPuzSw== dependencies: - "@babel/helper-annotate-as-pure" "^7.10.1" - "@babel/helper-plugin-utils" "^7.10.3" - -"@babel/plugin-transform-regenerator@^7.10.3", "@babel/plugin-transform-regenerator@^7.8.7": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.10.3.tgz#6ec680f140a5ceefd291c221cb7131f6d7e8cb6d" - integrity sha512-H5kNeW0u8mbk0qa1jVIVTeJJL6/TJ81ltD4oyPx0P499DhMJrTmmIFCmJ3QloGpQG8K9symccB7S7SJpCKLwtw== - 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.3", "@babel/plugin-transform-template-literals@^7.8.3": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.10.3.tgz#69d39b3d44b31e7b4864173322565894ce939b25" - integrity sha512-yaBn9OpxQra/bk0/CaA4wr41O0/Whkg6nqjqApcinxM7pro51ojhX6fv1pimAnVjVfDy14K0ULoRL70CA9jWWA== - dependencies: - "@babel/helper-annotate-as-pure" "^7.10.1" - "@babel/helper-plugin-utils" "^7.10.3" - -"@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.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.10.3.tgz#b3b35fb34ef0bd628b4b8329b0e5f985369201d4" - integrity sha512-qU9Lu7oQyh3PGMQncNjQm8RWkzw6LqsWZQlZPQMgrGt6s3YiBIaQ+3CQV/FA/icGS5XlSWZGwo/l8ErTyelS0Q== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.10.3" - "@babel/helper-plugin-utils" "^7.10.3" - "@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.3" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.10.3.tgz#3e58c9861bbd93b6a679987c7e4bd365c56c80c9" - integrity sha512-jHaSUgiewTmly88bJtMHbOd1bJf2ocYxb5BWKSDQIP5tmgFuS/n0gl+nhSrYDhT33m0vPxp+rP8oYYgPgMNQlg== - dependencies: - "@babel/compat-data" "^7.10.3" - "@babel/helper-compilation-targets" "^7.10.2" - "@babel/helper-module-imports" "^7.10.3" - "@babel/helper-plugin-utils" "^7.10.3" - "@babel/plugin-proposal-async-generator-functions" "^7.10.3" - "@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.3" - "@babel/plugin-proposal-optional-catch-binding" "^7.10.1" - "@babel/plugin-proposal-optional-chaining" "^7.10.3" - "@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.3" - "@babel/plugin-transform-computed-properties" "^7.10.3" - "@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.3" - "@babel/plugin-transform-modules-umd" "^7.10.1" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.10.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.3" - "@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.3" - "@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.3" - 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" + "@hapi/hoek" "9.x.x" -"@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.3" - resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.10.3.tgz#931ed6941d3954924a7aa967ee440e60c507b91a" - integrity sha512-HA7RPj5xvJxQl429r5Cxr2trJwOfPjKiqhCXcdQPSqO2G0RHPZpXu4fkYmBaTKCp2c/jRaMK9GB/lN+7zvvFPw== - dependencies: - core-js-pure "^3.0.0" - regenerator-runtime "^0.13.4" +"@hapi/hoek@9.x.x": + version "9.2.0" + resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.2.0.tgz#f3933a44e365864f4dad5db94158106d511e8131" + integrity sha512-sqKVVVOe5ivCaXDWivIJYVSaEgdQK9ul7a4Kity5Iw7u9+wBAPbX1RMSnLLmp7O4Vzj0WOWwMAJsTL00xwaNug== -"@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== +"@humanwhocodes/config-array@^0.5.0": + version "0.5.0" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.5.0.tgz#1407967d4c6eecd7388f83acf1eaf4d0c6e58ef9" + integrity sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg== dependencies: - regenerator-runtime "^0.13.4" + "@humanwhocodes/object-schema" "^1.2.0" + debug "^4.1.1" + minimatch "^3.0.4" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.3.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.10.3.tgz#670d002655a7c366540c67f6fd3342cd09500364" - integrity sha512-RzGO0RLSdokm9Ipe/YD+7ww8X2Ro79qiXZF3HU9ljrM+qnJmH1Vqth+hbiQZy761LnMJTMitHDuKVYTk3k4dLw== - dependencies: - regenerator-runtime "^0.13.4" +"@humanwhocodes/object-schema@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.0.tgz#87de7af9c231826fdd68ac7258f77c429e0e5fcf" + integrity sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w== -"@babel/runtime@^7.1.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.2.0": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.1.tgz#b4116a6b6711d010b2dad3b7b6e43bf1b9954740" - integrity sha512-J5AIf3vPj3UwXaAzb5j1xM4WAQDX3EMgemF8rjCP3SoW09LfRKAXQKt6CoVYl230P6iWdRcBbnLDDdnqWxZSCA== - dependencies: - regenerator-runtime "^0.13.4" +"@juggle/resize-observer@^3.3.1": + version "3.3.1" + resolved "https://registry.yarnpkg.com/@juggle/resize-observer/-/resize-observer-3.3.1.tgz#b50a781709c81e10701004214340f25475a171a0" + integrity sha512-zMM9Ds+SawiUkakS7y94Ymqx+S0ORzpG3frZirN3l+UlXUmSUR7hF4wxCVqW+ei94JzV5kt0uXBcoOEAuiydrw== -"@babel/template@^7.10.1", "@babel/template@^7.10.3", "@babel/template@^7.4.0", "@babel/template@^7.8.6": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.3.tgz#4d13bc8e30bf95b0ce9d175d30306f42a2c9a7b8" - integrity sha512-5BjI4gdtD+9fHZUsaxPHPNpwa+xRkDO7c7JbhYn2afvrkDu5SfAAbi9AIMXw2xEhO/BR35TqiW97IqNvCo/GqA== - dependencies: - "@babel/code-frame" "^7.10.3" - "@babel/parser" "^7.10.3" - "@babel/types" "^7.10.3" - -"@babel/traverse@^7.1.0", "@babel/traverse@^7.10.1", "@babel/traverse@^7.10.3", "@babel/traverse@^7.4.3", "@babel/traverse@^7.7.0", "@babel/traverse@^7.9.0": - version "7.10.3" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.10.3.tgz#0b01731794aa7b77b214bcd96661f18281155d7e" - integrity sha512-qO6623eBFhuPm0TmmrUFMT1FulCmsSeJuVGhiLodk2raUDFhhTECLd9E9jC4LBIWziqt4wgF6KuXE4d+Jz9yug== - dependencies: - "@babel/code-frame" "^7.10.3" - "@babel/generator" "^7.10.3" - "@babel/helper-function-name" "^7.10.3" - "@babel/helper-split-export-declaration" "^7.10.1" - "@babel/parser" "^7.10.3" - "@babel/types" "^7.10.3" - 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.3", "@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.3" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.10.3.tgz#6535e3b79fea86a6b09e012ea8528f935099de8e" - integrity sha512-nZxaJhBXBQ8HVoIcGsf9qWep3Oh3jCENK54V4mRF7qaJabVsAYdbTtmSD8WmAp1R6ytPiu5apMwSXyxB1WlaBA== - dependencies: - "@babel/helper-validator-identifier" "^7.10.3" - lodash "^4.17.13" - to-fast-properties "^2.0.0" +"@napi-rs/triples@^1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@napi-rs/triples/-/triples-1.0.3.tgz#76d6d0c3f4d16013c61e45dfca5ff1e6c31ae53c" + integrity sha512-jDJTpta+P4p1NZTFVLHJ/TLFVYVcOqv6l8xwOeBKNPMgY/zDYH/YH7SJbvrr/h1RcS9GzbPcLKGzpuK9cV56UA== + +"@next/env@11.1.2": + version "11.1.2" + resolved "https://registry.yarnpkg.com/@next/env/-/env-11.1.2.tgz#27996efbbc54c5f949f5e8c0a156e3aa48369b99" + integrity sha512-+fteyVdQ7C/OoulfcF6vd1Yk0FEli4453gr8kSFbU8sKseNSizYq6df5MKz/AjwLptsxrUeIkgBdAzbziyJ3mA== + +"@next/eslint-plugin-next@11.1.2": + version "11.1.2" + resolved "https://registry.yarnpkg.com/@next/eslint-plugin-next/-/eslint-plugin-next-11.1.2.tgz#f26cf90bcb6cd2e4645e2ba253bbc9aaaa43a170" + integrity sha512-cN+ojHRsufr9Yz0rtvjv8WI5En0RPZRJnt0y16Ha7DD+0n473evz8i1ETEJHmOLeR7iPJR0zxRrxeTN/bJMOjg== + dependencies: + glob "7.1.7" + +"@next/polyfill-module@11.1.2": + version "11.1.2" + resolved "https://registry.yarnpkg.com/@next/polyfill-module/-/polyfill-module-11.1.2.tgz#1fe92c364fdc81add775a16c678f5057c6aace98" + integrity sha512-xZmixqADM3xxtqBV0TpAwSFzWJP0MOQzRfzItHXf1LdQHWb0yofHHC+7eOrPFic8+ZGz5y7BdPkkgR1S25OymA== + +"@next/react-dev-overlay@11.1.2": + version "11.1.2" + resolved "https://registry.yarnpkg.com/@next/react-dev-overlay/-/react-dev-overlay-11.1.2.tgz#73795dc5454b7af168bac93df7099965ebb603be" + integrity sha512-rDF/mGY2NC69mMg2vDqzVpCOlWqnwPUXB2zkARhvknUHyS6QJphPYv9ozoPJuoT/QBs49JJd9KWaAzVBvq920A== + dependencies: + "@babel/code-frame" "7.12.11" + anser "1.4.9" + chalk "4.0.0" + classnames "2.2.6" + css.escape "1.5.1" + data-uri-to-buffer "3.0.1" + platform "1.3.6" + shell-quote "1.7.2" + source-map "0.8.0-beta.0" + stacktrace-parser "0.1.10" + strip-ansi "6.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" +"@next/react-refresh-utils@11.1.2": + version "11.1.2" + resolved "https://registry.yarnpkg.com/@next/react-refresh-utils/-/react-refresh-utils-11.1.2.tgz#44ea40d8e773e4b77bad85e24f6ac041d5e4b4a5" + integrity sha512-hsoJmPfhVqjZ8w4IFzoo8SyECVnN+8WMnImTbTKrRUHOVJcYMmKLL7xf7T0ft00tWwAl/3f3Q3poWIN2Ueql/Q== -"@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== +"@next/swc-darwin-arm64@11.1.2": + version "11.1.2" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-11.1.2.tgz#93226c38db488c4b62b30a53b530e87c969b8251" + integrity sha512-hZuwOlGOwBZADA8EyDYyjx3+4JGIGjSHDHWrmpI7g5rFmQNltjlbaefAbiU5Kk7j3BUSDwt30quJRFv3nyJQ0w== -"@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== +"@next/swc-darwin-x64@11.1.2": + version "11.1.2" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-11.1.2.tgz#792003989f560c00677b5daeff360b35b510db83" + integrity sha512-PGOp0E1GisU+EJJlsmJVGE+aPYD0Uh7zqgsrpD3F/Y3766Ptfbe1lEPPWnRDl+OzSSrSrX1lkyM/Jlmh5OwNvA== -"@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== +"@next/swc-linux-x64-gnu@11.1.2": + version "11.1.2" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-11.1.2.tgz#8216b2ae1f21f0112958735c39dd861088108f37" + integrity sha512-YcDHTJjn/8RqvyJVB6pvEKXihDcdrOwga3GfMv/QtVeLphTouY4BIcEUfrG5+26Nf37MP1ywN3RRl1TxpurAsQ== -"@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" +"@next/swc-win32-x64-msvc@11.1.2": + version "11.1.2" + resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-11.1.2.tgz#e15824405df137129918205e43cb5e9339589745" + integrity sha512-e/pIKVdB+tGQYa1cW3sAeHm8gzEri/HYLZHT4WZojrUxgWXqx8pk7S7Xs47uBcFTqBDRvK3EcQpPLf3XdVsDdg== -"@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== +"@node-rs/helper@1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@node-rs/helper/-/helper-1.2.1.tgz#e079b05f21ff4329d82c4e1f71c0290e4ecdc70c" + integrity sha512-R5wEmm8nbuQU0YGGmYVjEc0OHtYsuXdpRG+Ut/3wZ9XAvQWyThN08bTh2cBJgoZxHQUPtvRfeQuxcAgLuiBISg== dependencies: - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^1.1.1" - "@types/yargs" "^13.0.0" + "@napi-rs/triples" "^1.0.3" -"@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== +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== dependencies: - call-me-maybe "^1.0.1" - glob-to-regexp "^0.3.0" + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" -"@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== +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@patternfly/react-core@^4.152.4": + version "4.152.4" + resolved "https://registry.yarnpkg.com/@patternfly/react-core/-/react-core-4.152.4.tgz#fa008d155ab0c4161aab27bb67ad88fb8689dee2" + integrity sha512-saMnAoF3KnYB5k6K04Y6NtO61bXYl71yzGPYSrM/DjyWDlIGLqPPhMe4kbWafLTpSgJdkxPoODgVMDp2ZIJ0Jw== + dependencies: + "@patternfly/react-icons" "^4.11.14" + "@patternfly/react-styles" "^4.11.13" + "@patternfly/react-tokens" "^4.12.15" + focus-trap "6.2.2" + react-dropzone "9.0.0" + tippy.js "5.1.2" + tslib "^2.0.0" + +"@patternfly/react-icons@^4.11.14": + version "4.11.14" + resolved "https://registry.yarnpkg.com/@patternfly/react-icons/-/react-icons-4.11.14.tgz#ef7a2de3f78d5bd83715afb9651a185a89901c22" + integrity sha512-SBwID1p+UaQ9BSmzIRFr+BJEhYgx1rWHlm2HIZzhoz7BG3Q7byaQ8ZNfZLm0D+ZGVJQ+fq0zUHGE1nzxDPFqNQ== + +"@patternfly/react-styles@^4.11.13": + version "4.11.13" + resolved "https://registry.yarnpkg.com/@patternfly/react-styles/-/react-styles-4.11.13.tgz#6595adc5ff40585add0128d27d420322b65b86a9" + integrity sha512-svhnWIqZwJt1cOxwYjvz6lVYeL+c9D17xpKqlkJapXRxJL3ppTfIqwBrT3o9+02ElaXUTKt4xjMkSnEVjw4qxA== + +"@patternfly/react-table@^4.29.58": + version "4.29.58" + resolved "https://registry.yarnpkg.com/@patternfly/react-table/-/react-table-4.29.58.tgz#be77a6eee6a60c26e1f6939aaae007f21be52355" + integrity sha512-0KaypqnO1baaS8ESpaMyPcGmOsAva7vO9GzBiiiKnYizfjsPPE58KryyiLu0fEXBT6ESzYYm8I6pZNzzlI50Ww== + dependencies: + "@patternfly/react-core" "^4.152.4" + "@patternfly/react-icons" "^4.11.14" + "@patternfly/react-styles" "^4.11.13" + "@patternfly/react-tokens" "^4.12.15" + lodash "^4.17.19" + tslib "^2.0.0" + +"@patternfly/react-tokens@^4.12.15": + version "4.12.15" + resolved "https://registry.yarnpkg.com/@patternfly/react-tokens/-/react-tokens-4.12.15.tgz#af760a1b378edce85fd2f7394553911b6ee3c4f0" + integrity sha512-lRW0qxGjuFEPMweBSQFHNRNoxavx5uR8b28f0lPN0Jlz4QsaCFVTmHM2XqflOHDpjE8SPJW/hJMSsyUrqnM5dw== "@redux-saga/core@^1.1.3": version "1.1.3" @@ -1371,269 +355,127 @@ resolved "https://registry.yarnpkg.com/@redux-saga/types/-/types-1.1.0.tgz#0e81ce56b4883b4b2a3001ebe1ab298b84237204" integrity sha512-afmTuJrylUU/0OtqzaRkbyYFFNgCF73Bvel/sw90pvGrWIZ+vyoIJqA6eMSoA6+nb443kTmulmBtC9NerXboNg== -"@sentry/browser@5.27.3": - version "5.27.3" - resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-5.27.3.tgz#02e78a4502ee99988d3cbb0075a11ec44b503871" - integrity sha512-vczS+XTW4Nk2A7TIpAw8IVFHpp+NK6mV9euBG2I61Bs2QbQY9yKLfbjiln/yH2Q8X4THX6MKa0GuiPoCEeq3uw== +"@rushstack/eslint-patch@^1.0.6": + version "1.0.6" + resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.0.6.tgz#023d72a5c4531b4ce204528971700a78a85a0c50" + integrity sha512-Myxw//kzromB9yWgS8qYGuGVf91oBUUJpNvy5eM50sqvmKLbKjwLxohJnkWGTeeI9v9IBMtPLxz5Gc60FIfvCA== + +"@sentry/browser@5.30.0": + version "5.30.0" + resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-5.30.0.tgz#c28f49d551db3172080caef9f18791a7fd39e3b3" + integrity sha512-rOb58ZNVJWh1VuMuBG1mL9r54nZqKeaIlwSlvzJfc89vyfd7n6tQ1UXMN383QBz/MS5H5z44Hy5eE+7pCrYAfw== dependencies: - "@sentry/core" "5.27.3" - "@sentry/types" "5.27.3" - "@sentry/utils" "5.27.3" + "@sentry/core" "5.30.0" + "@sentry/types" "5.30.0" + "@sentry/utils" "5.30.0" tslib "^1.9.3" -"@sentry/core@5.27.3": - version "5.27.3" - resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.27.3.tgz#d7a175b71596b7eb4b2e8b4cd1858a60d95813bb" - integrity sha512-yqepQO88jSt5hy0awpk61AxI4oHB09LjVbUEk4nJDg+1YXuND23cuZvH+Sp2jCZX2vrsw2tefwflToYfA8/U2w== +"@sentry/core@5.30.0": + version "5.30.0" + resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.30.0.tgz#6b203664f69e75106ee8b5a2fe1d717379b331f3" + integrity sha512-TmfrII8w1PQZSZgPpUESqjB+jC6MvZJZdLtE/0hZ+SrnKhW3x5WlYLvTXZpcWePYBku7rl2wn1RZu6uT0qCTeg== dependencies: - "@sentry/hub" "5.27.3" - "@sentry/minimal" "5.27.3" - "@sentry/types" "5.27.3" - "@sentry/utils" "5.27.3" + "@sentry/hub" "5.30.0" + "@sentry/minimal" "5.30.0" + "@sentry/types" "5.30.0" + "@sentry/utils" "5.30.0" tslib "^1.9.3" -"@sentry/hub@5.27.3": - version "5.27.3" - resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-5.27.3.tgz#f509c2fd38f500afef6030504e82510dbd0649d6" - integrity sha512-icEH3hr6NVQkpowXZcPOs9IgJZP5lMKtvud4mVioSpkd+NxtRdKrGEX4eF2TCviOJc9Md0mV4K+aL5Au7hxggQ== +"@sentry/hub@5.30.0": + version "5.30.0" + resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-5.30.0.tgz#2453be9b9cb903404366e198bd30c7ca74cdc100" + integrity sha512-2tYrGnzb1gKz2EkMDQcfLrDTvmGcQPuWxLnJKXJvYTQDGLlEvi2tWz1VIHjunmOvJrB5aIQLhm+dcMRwFZDCqQ== dependencies: - "@sentry/types" "5.27.3" - "@sentry/utils" "5.27.3" + "@sentry/types" "5.30.0" + "@sentry/utils" "5.30.0" tslib "^1.9.3" -"@sentry/minimal@5.27.3": - version "5.27.3" - resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-5.27.3.tgz#c9263bdd6270bfeae64137177448911dff568e53" - integrity sha512-ng01cM0rsE1RMjqVTpPLN0ZVkTo0I675usM1krkpQe8ddW6tfQ6EJWpt02/BrpQZRQzTtfWp6/RyB1KFXg6icg== +"@sentry/minimal@5.30.0": + version "5.30.0" + resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-5.30.0.tgz#ce3d3a6a273428e0084adcb800bc12e72d34637b" + integrity sha512-BwWb/owZKtkDX+Sc4zCSTNcvZUq7YcH3uAVlmh/gtR9rmUvbzAA3ewLuB3myi4wWRAMEtny6+J/FN/x+2wn9Xw== dependencies: - "@sentry/hub" "5.27.3" - "@sentry/types" "5.27.3" + "@sentry/hub" "5.30.0" + "@sentry/types" "5.30.0" tslib "^1.9.3" -"@sentry/react@^5.27.3": - version "5.27.3" - resolved "https://registry.yarnpkg.com/@sentry/react/-/react-5.27.3.tgz#aefff1cb2249a4e7f123c7467d1da205d5c02e92" - integrity sha512-p7E+djSUVKz02HoRVDX+zamjV8+RL4bqoPnS9JQESweB0sRTYlpvi+CqWLYWNWnamWQWOl97hOw/lLDpo4kUSA== +"@sentry/react@^5.30.0": + version "5.30.0" + resolved "https://registry.yarnpkg.com/@sentry/react/-/react-5.30.0.tgz#320e05f766b6a26faefa8d76d1101fd50c69f541" + integrity sha512-dvn4mqCgbeEuUXEGp5P9PaW5j4GWTFUSdx/yG8f9IxNZv5zM+7otjog9ukrubFZvlxVxD/PrIxK0MhadfFY/Dw== dependencies: - "@sentry/browser" "5.27.3" - "@sentry/minimal" "5.27.3" - "@sentry/types" "5.27.3" - "@sentry/utils" "5.27.3" + "@sentry/browser" "5.30.0" + "@sentry/minimal" "5.30.0" + "@sentry/types" "5.30.0" + "@sentry/utils" "5.30.0" hoist-non-react-statics "^3.3.2" tslib "^1.9.3" -"@sentry/tracing@^5.27.3": - version "5.27.3" - resolved "https://registry.yarnpkg.com/@sentry/tracing/-/tracing-5.27.3.tgz#787e57a2f7071e375f4fad0f3c3a5ff3381928e7" - integrity sha512-UWrHMdGxPfx1u558CWm1tptc2z0BuqCHVe2+BNN7POahq5BkpbGqaotyPQTBHbfmcs6QGfsMG57ou8HQFrBxyA== +"@sentry/tracing@^5.30.0": + version "5.30.0" + resolved "https://registry.yarnpkg.com/@sentry/tracing/-/tracing-5.30.0.tgz#501d21f00c3f3be7f7635d8710da70d9419d4e1f" + integrity sha512-dUFowCr0AIMwiLD7Fs314Mdzcug+gBVo/+NCMyDw8tFxJkwWAKl7Qa2OZxLQ0ZHjakcj1hNKfCQJ9rhyfOl4Aw== dependencies: - "@sentry/hub" "5.27.3" - "@sentry/minimal" "5.27.3" - "@sentry/types" "5.27.3" - "@sentry/utils" "5.27.3" + "@sentry/hub" "5.30.0" + "@sentry/minimal" "5.30.0" + "@sentry/types" "5.30.0" + "@sentry/utils" "5.30.0" tslib "^1.9.3" -"@sentry/types@5.27.3": - version "5.27.3" - resolved "https://registry.yarnpkg.com/@sentry/types/-/types-5.27.3.tgz#d377508769bc658d672c287166c7f6c5db45660c" - integrity sha512-PkWhMArFMxBb1g3HtMEL8Ea9PYae2MU0z9CMIWiqzerFy2ZpKG98IU3pt8ic4JkmKQdwB8hDiZpRPMHhW0WYwQ== +"@sentry/types@5.30.0": + version "5.30.0" + resolved "https://registry.yarnpkg.com/@sentry/types/-/types-5.30.0.tgz#19709bbe12a1a0115bc790b8942917da5636f402" + integrity sha512-R8xOqlSTZ+htqrfteCWU5Nk0CDN5ApUTvrlvBuiH1DyP6czDZ4ktbZB0hAgBlVcK0U+qpD3ag3Tqqpa5Q67rPw== -"@sentry/utils@5.27.3": - version "5.27.3" - resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-5.27.3.tgz#1fc45dfad1f1e4398bee58684d8947666d8d3003" - integrity sha512-R9WvFrRBALZvCzu/9BsuXBCfkNxz4MwdBNSXaBsJo4afQw1ljkjIc9DpHzlL9S9goIwXo81Buwmr5gGDO6aH+Q== +"@sentry/utils@5.30.0": + version "5.30.0" + resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-5.30.0.tgz#9a5bd7ccff85ccfe7856d493bffa64cabc41e980" + integrity sha512-zaYmoH0NWWtvnJjC9/CBseXMtKHm/tm40sz3YfJRxeQjyzRqNQPgivpd9R/oDJCYj999mzdW382p/qi2ypjLww== dependencies: - "@sentry/types" "5.27.3" + "@sentry/types" "5.30.0" tslib "^1.9.3" -"@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" +"@types/d3-path@^2": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@types/d3-path/-/d3-path-2.0.1.tgz#ca03dfa8b94d8add97ad0cd97e96e2006b4763cb" + integrity sha512-6K8LaFlztlhZO7mwsZg7ClRsdLg3FJRzIIi6SZXDWmmSJc2x8dd2VkESbLXdk3p8cuvz71f36S0y8Zv2AxqvQw== -"@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== +"@types/d3-scale@^3.0.0": + version "3.3.2" + resolved "https://registry.yarnpkg.com/@types/d3-scale/-/d3-scale-3.3.2.tgz#18c94e90f4f1c6b1ee14a70f14bfca2bd1c61d06" + integrity sha512-gGqr7x1ost9px3FvIfUMi5XA/F/yAf4UkUDtdQhpH92XCT0Oa7zkkRzY61gPVJq+DxpHn/btouw5ohWkbBsCzQ== 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.9" - resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.9.tgz#77e59d438522a6fb898fa43dc3455c6e72f3963d" - integrity sha512-sY2RsIJ5rpER1u3/aQ8OFSI7qGIy8o1NEEbgb2UaJcvOtXOMpd39ko723NBpjQFg9SIX7TXtjejZVGeIMLhoOw== - 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/d3-time" "^2" -"@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== +"@types/d3-shape@^2.0.0": + version "2.1.3" + resolved "https://registry.yarnpkg.com/@types/d3-shape/-/d3-shape-2.1.3.tgz#35d397b9e687abaa0de82343b250b9897b8cacf3" + integrity sha512-HAhCel3wP93kh4/rq+7atLdybcESZ5bRHDEZUojClyZWsRuEMo3A52NGYJSh48SxfxEU6RZIVbZL2YFZ2OAlzQ== dependencies: - "@types/minimatch" "*" - "@types/node" "*" - -"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#4ba8ddb720221f432e443bd5f9117fd22cfd4762" - integrity sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw== + "@types/d3-path" "^2" -"@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/d3-time@^2": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@types/d3-time/-/d3-time-2.1.1.tgz#743fdc821c81f86537cbfece07093ac39b4bc342" + integrity sha512-9MVYlmIgmRR31C5b4FVSWtuMmBHh2mOWQYfl7XAYOa8dsnb7iEmUmRSWSFgXFtkjxO65d7hTUHQC+RhR/9IWFg== -"@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== +"@types/hoist-non-react-statics@^3.3.0": + version "3.3.1" + resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f" + integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA== dependencies: - "@types/istanbul-lib-coverage" "*" - "@types/istanbul-lib-report" "*" + "@types/react" "*" + hoist-non-react-statics "^3.3.0" -"@types/json-schema@^7.0.3", "@types/json-schema@^7.0.4": - version "7.0.5" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.5.tgz#dcce4430e64b443ba8945f0290fb564ad5bac6dd" - integrity sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ== - -"@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/json5@^0.0.29": + version "0.0.29" + resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" + integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= "@types/node@*": - version "14.0.14" - resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.14.tgz#24a0b5959f16ac141aeb0c5b3cd7a15b7c64cbce" - integrity sha512-syUgf67ZQpaJj01/tRTknkMNoBBLWJOBODF0Zm4NrXmiSuxjymFrxnTu1QVYRubhVkRcZLYZG8STTwJRdVm/WQ== + version "16.7.13" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.7.13.tgz#86fae356b03b5a12f2506c6cf6cd9287b205973f" + integrity sha512-pLUPDn+YG3FYEt/pHI74HmnJOWzeR+tOIQzUx93pi9M7D8OE7PSLr97HboXwk5F+JS+TLtWuzCOW97AHjmOXXA== "@types/parse-json@^4.0.0": version "4.0.0" @@ -1641,392 +483,154 @@ integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== "@types/prop-types@*": - version "15.7.3" - resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7" - integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw== + version "15.7.4" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.4.tgz#fcf7205c25dff795ee79af1e30da2c9790808f11" + integrity sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ== -"@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-redux@^7.1.16": + version "7.1.18" + resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.18.tgz#2bf8fd56ebaae679a90ebffe48ff73717c438e04" + integrity sha512-9iwAsPyJ9DLTRH+OFeIrm9cAbIj1i2ANL3sKQFATqnPWRbg+jEFXyZOKHiQK/N86pNRXbb4HRxAxo0SIX1XwzQ== + dependencies: + "@types/hoist-non-react-statics" "^3.3.0" + "@types/react" "*" + hoist-non-react-statics "^3.3.0" + redux "^4.0.0" "@types/react@*": - version "16.9.41" - resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.41.tgz#925137ee4d2ff406a0ecf29e8e9237390844002e" - integrity sha512-6cFei7F7L4wwuM+IND/Q2cV1koQUvJ8iSV+Gwn0c3kvABZ691g7sp3hfEQHOUBJtccl1gPi+EyNjMIl9nGA0ug== + version "17.0.20" + resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.20.tgz#a4284b184d47975c71658cd69e759b6bd37c3b8c" + integrity sha512-wWZrPlihslrPpcKyCSlmIlruakxr57/buQN1RjlIeaaTWDLtJkTtRW429MoQJergvVKc4IWBpRhWw7YNh/7GVA== 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/uuid@8.0.0": - version "8.0.0" - resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.0.0.tgz#165aae4819ad2174a17476dbe66feebd549556c0" - integrity sha512-xSQfNcvOiE5f9dyd4Kzxbof1aTrLobL278pGLKOZI6esGfZ7ts9Ka16CzIN6Y8hFHE1C7jIBZokULhK1bOgjRw== + "@types/scheduler" "*" + csstype "^3.0.2" -"@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" +"@types/resize-observer-browser@^0.1.6": + version "0.1.6" + resolved "https://registry.yarnpkg.com/@types/resize-observer-browser/-/resize-observer-browser-0.1.6.tgz#d8e6c2f830e2650dc06fe74464472ff64b54a302" + integrity sha512-61IfTac0s9jvNtBCpyo86QeaN8qqpMGHdK0uGKCCIy2dt5/Yk84VduHIdWAcmkC5QvdkPL0p5eWYgUZtHKKUVg== + +"@types/scheduler@*": + version "0.16.2" + resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39" + integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew== + +"@types/uuid@8.3.1": + version "8.3.1" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.1.tgz#1a32969cf8f0364b3d8c8af9cc3555b7805df14f" + integrity sha512-Y2mHTRAbqfFkpjldbkHGY8JIzRN6XqYRliG8/24FcHm2D2PwW24fl5xMRTVGdrb7iMrwCaIEbLWerGIkXuFWVg== + +"@typescript-eslint/parser@^4.20.0": + version "4.31.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.31.0.tgz#87b7cd16b24b9170c77595d8b1363f8047121e05" + integrity sha512-oWbzvPh5amMuTmKaf1wp0ySxPt2ZXHnFQBN2Szu1O//7LmOvgaKTCIDNLK2NvzpmVd5A2M/1j/rujBqO37hj3w== + dependencies: + "@typescript-eslint/scope-manager" "4.31.0" + "@typescript-eslint/types" "4.31.0" + "@typescript-eslint/typescript-estree" "4.31.0" + debug "^4.3.1" + +"@typescript-eslint/scope-manager@4.31.0": + version "4.31.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.31.0.tgz#9be33aed4e9901db753803ba233b70d79a87fc3e" + integrity sha512-LJ+xtl34W76JMRLjbaQorhR0hfRAlp3Lscdiz9NeI/8i+q0hdBZ7BsiYieLoYWqy+AnRigaD3hUwPFugSzdocg== + dependencies: + "@typescript-eslint/types" "4.31.0" + "@typescript-eslint/visitor-keys" "4.31.0" + +"@typescript-eslint/types@4.31.0": + version "4.31.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.31.0.tgz#9a7c86fcc1620189567dc4e46cad7efa07ee8dce" + integrity sha512-9XR5q9mk7DCXgXLS7REIVs+BaAswfdHhx91XqlJklmqWpTALGjygWVIb/UnLh4NWhfwhR5wNe1yTyCInxVhLqQ== + +"@typescript-eslint/typescript-estree@4.31.0": + version "4.31.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.31.0.tgz#4da4cb6274a7ef3b21d53f9e7147cc76f278a078" + integrity sha512-QHl2014t3ptg+xpmOSSPn5hm4mY8D4s97ftzyk9BZ8RxYQ3j73XcwuijnJ9cMa6DO4aLXeo8XS3z1omT9LA/Eg== + dependencies: + "@typescript-eslint/types" "4.31.0" + "@typescript-eslint/visitor-keys" "4.31.0" + debug "^4.3.1" + globby "^11.0.3" 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== + semver "^7.3.5" + tsutils "^3.21.0" -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== - -accepts@~1.3.4, accepts@~1.3.5, 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== +"@typescript-eslint/visitor-keys@4.31.0": + version "4.31.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.31.0.tgz#4e87b7761cb4e0e627dc2047021aa693fc76ea2b" + integrity sha512-HUcRp2a9I+P21+O21yu3ezv3GEPGjyGiXoEUQwZXjR8UxRApGeLyWH4ZIIUSalE28aG4YsV6GjtaAVB3QKOu0w== 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== + "@typescript-eslint/types" "4.31.0" + eslint-visitor-keys "^2.0.0" -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== +abortcontroller-polyfill@^1.7.3: + version "1.7.3" + resolved "https://registry.yarnpkg.com/abortcontroller-polyfill/-/abortcontroller-polyfill-1.7.3.tgz#1b5b487bd6436b5b764fd52a612509702c3144b5" + integrity sha512-zetDJxd89y3X99Kvo4qFx8GKlt6GsvN3UcRZHwU6iFA/0KiOmhkTVhe8oRoTBiTVPZu09x3vCra47+w8Yz1+2Q== -acorn@^7.1.1: - version "7.3.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.3.1.tgz#85010754db53c3fbaf3b9ea3e083aa5c5d147ffd" - integrity sha512-tLc0wSnatxAQHVHUapaHdz72pi9KUyHjq5KyHjGg9Y8Ifdc79pTh2XvI6I1/chZbnM7QtNKzh66ooDogPZSleA== +acorn-jsx@^5.3.1: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== -address@1.1.2, address@^1.0.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/address/-/address-1.1.2.tgz#bf1116c9c758c51b7a933d296b72c221ed9428b6" - integrity sha512-aT6camzM4xEA54YVJYSqxz1kv4IHnQZRtThJJHhUMRExaU5spC7jX5ugSwTaTgJliIgs4VhZOk7htClvQ/LmRA== - -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" - integrity sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8= +acorn@^7.4.0: + version "7.4.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" + integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== 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== + version "3.1.0" + resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" + integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== 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.5.0" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.0.tgz#5c894537098785926d71e696114a53ce768ed773" - integrity sha512-eyoaac3btgU8eJlvh01En8OCKzRqlLe2G5jDsCr3RiE2uLGMEEB1aaGwVVpwR8M95956tGH6R+9edC++OvzaVw== - -ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.2, ajv@^6.5.5: - 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== +ajv@^6.10.0, ajv@^6.12.4: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== 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" -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= - -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" - integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ== - -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== +ajv@^8.0.1: + version "8.6.2" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.6.2.tgz#2fb45e0e5fcbc0813326c1c3da535d1881bb0571" + integrity sha512-9807RlWAgT564wT+DjeyU5OFMPjmzxVobvDFmNAhY+5zD6A2ly3jDp6sgnfyDtlIQ+7H97oc/DGCzzfu9rjw9w== 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" - integrity sha1-gTWEAhliqenm/QOflA0S9WynhZ4= + fast-deep-equal "^3.1.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + uri-js "^4.2.2" -ansi-regex@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" - integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= +anser@1.4.9: + version "1.4.9" + resolved "https://registry.yarnpkg.com/anser/-/anser-1.4.9.tgz#1f85423a5dcf8da4631a341665ff675b96845760" + integrity sha512-AI+BjTeGt2+WFk4eWcqbQ7snZpDBt8SaLlj0RT2h5xfdWaiy51OjYvqwMrNzJLGy8iOAL6nKDITWO+rd4MkYEA== -ansi-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" - integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= +ansi-colors@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" + integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== -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-escapes@^4.3.0: + version "4.3.2" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + dependencies: + type-fest "^0.21.3" 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" - integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4= - -ansi-styles@^3.2.0, ansi-styles@^3.2.1: +ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== @@ -2034,38 +638,24 @@ ansi-styles@^3.2.0, ansi-styles@^3.2.1: 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== + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== 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" - integrity sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw== - 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== +anymatch@~3.1.1, anymatch@~3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" + integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== 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.1.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" - integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== +approximate-number@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/approximate-number/-/approximate-number-2.1.0.tgz#8dbb27957f4f230e386b57258f8f0c75f32313c8" + integrity sha512-EioK6nto/hEnwaJ7d/TG1WOZ9o0zTyIFVP4Lk7zzR/3I4O7ivkBNo7EvLC2Xh2j2HD/cb9sUqXHdexfGXCXYDA== argparse@^1.0.7: version "1.0.10" @@ -2074,125 +664,68 @@ argparse@^1.0.7: 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= +aria-query@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-4.2.2.tgz#0d2ca6c9aceb56b8977e9fed6aed7e15bbd2f83b" + integrity sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA== dependencies: - ast-types-flow "0.0.7" - commander "^2.11.0" + "@babel/runtime" "^7.10.2" + "@babel/runtime-corejs3" "^7.10.2" -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" - integrity sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA= - -arr-flatten@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" - integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== - -arr-union@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" - integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= - -array-equal@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93" - integrity sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM= - -array-flatten@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" - integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= - -array-flatten@^2.1.0: - version "2.1.2" - resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.2.tgz#24ef80a28c1a893617e2149b0c6d0d788293b099" - integrity sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ== - -array-includes@^3.0.3, 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== +array-includes@^3.1.1, array-includes@^3.1.2, array-includes@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.3.tgz#c7f619b382ad2afaf5326cddfdc0afc61af7690a" + integrity sha512-gcem1KlBU7c9rB+Rq8/3PPKsK2kjqeEBa3bD5kkQo4nYlOHQCJqIJFqBXDEfwaRuYTT4E+FxA9xez7Gf/e3Q7A== dependencies: + call-bind "^1.0.2" define-properties "^1.1.3" - es-abstract "^1.17.0" + es-abstract "^1.18.0-next.2" + get-intrinsic "^1.1.1" is-string "^1.0.5" -array-union@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" - integrity sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk= - 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" - integrity sha1-r2rId6Jcx/dOBYiUdThY39sk/bY= - -array-unique@^0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" - integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== -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== +array.prototype.flat@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.2.4.tgz#6ef638b43312bd401b4c6199fdec7e2dc9e9a123" + integrity sha512-4470Xi3GAPAjZqFcljX2xzckv1qeKPizoNkiS0+O4IoPR2ZNpcjE0pkhdihlDouK+x6QOast26B4Q/O9DJnwSg== dependencies: + call-bind "^1.0.0" define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" + es-abstract "^1.18.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" - integrity sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog== - -arrify@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" - integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0= - -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= +array.prototype.flatmap@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.2.4.tgz#94cfd47cc1556ec0747d97f7c7738c58122004c9" + integrity sha512-r9Z0zYoxqHz60vvQbWEdXIEtCwHF0yxaWfno9qzXeNHvfyl3BZqygmGzb84dsubyaXLH4husF+NFgMSdpZhk2Q== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + es-abstract "^1.18.0-next.1" + function-bind "^1.1.1" -asn1.js@^4.0.0: - version "4.10.1" - resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.10.1.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0" - integrity sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw== +asn1.js@^5.2.0: + version "5.4.1" + resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-5.4.1.tgz#11a980b84ebb91781ce35b0fdc2ee294e3783f07" + integrity sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA== dependencies: bn.js "^4.0.0" inherits "^2.0.1" minimalistic-assert "^1.0.0" + safer-buffer "^2.1.0" -asn1@~0.2.3: - version "0.2.4" - resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" - integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== - 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" - integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= - -assert@1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/assert/-/assert-1.4.1.tgz#99912d591836b5a6f5b345c0f07eefc08fc65d91" - integrity sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE= +assert@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/assert/-/assert-2.0.0.tgz#95fc1c616d48713510680f2eaf2d10dd22e02d32" + integrity sha512-se5Cd+js9dXJnu6Ag2JFc00t+HmHOen+8Q+L7O9zI0PqQXr20uk2J0XQqMxZEeo5U50o8Nvmmx7dZrl+Ufr35A== dependencies: - util "0.10.3" + es6-object-assign "^1.1.0" + is-nan "^1.2.1" + object-is "^1.0.1" + util "^0.12.0" assert@^1.1.1: version "1.5.0" @@ -2202,367 +735,77 @@ assert@^1.1.1: object-assign "^4.1.1" 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" - integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= - -ast-types-flow@0.0.7, 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== +ast-types@0.13.2: + version "0.13.2" + resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.13.2.tgz#df39b677a911a83f3a049644fb74fdded23cea48" + integrity sha512-uWMHxJxtfj/1oZClOxDEV1sQ1HCDkA4MG8Gr69KKeBjEVH0R84WlejZ0y2DcwyBlpAEMltmVYkVgqfLFb2oyiA== 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.3" - resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" - integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ== - -async-limiter@~1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" - integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== - -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== +attr-accept@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/attr-accept/-/attr-accept-1.1.3.tgz#48230c79f93790ef2775fcec4f0db0f5db41ca52" + integrity sha512-iT40nudw8zmCweivz6j58g+RT33I4KbaIvRUhjNmDwO2WmsQUxFEZZYZ5w3vXe5x5MX9D7mfvA/XaLOZYFR9EQ== 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" - integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= + core-js "^2.5.0" -atob@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" - integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== - -autoprefixer@^9.6.1: - version "9.8.4" - resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.8.4.tgz#736f1012673a70fa3464671d78d41abd54512863" - integrity sha512-84aYfXlpUe45lvmS+HoAWKCkirI/sw4JK0/bTeeqgHYco3dcsOn0NqdejISjptsYwNji/21dnkDri9PsYKk89A== - dependencies: - browserslist "^4.12.0" - caniuse-lite "^1.0.30001087" - colorette "^1.2.0" - normalize-range "^0.1.2" - num2fraction "^1.2.2" - postcss "^7.0.32" - 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" - integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= +available-typed-arrays@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" + integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== -aws4@^1.8.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.10.0.tgz#a17b3a8ea811060e74d47d306122400ad4497ae2" - integrity sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA== +axe-core@^4.0.2: + version "4.3.3" + resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.3.3.tgz#b55cd8e8ddf659fe89b064680e1c6a4dceab0325" + integrity sha512-/lqqLAmuIPi79WYfRpy2i8z+x+vxU3zX2uAm0gs1q52qTuKwolOj1P8XbufpXcsydrpKx2yGn2wzAnxCMV86QA== -axobject-query@^2.0.2: +axobject-query@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be" integrity sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA== -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" - integrity sha1-Y/1D99weO7fONZR9uP42mj9Yx0s= - 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" - integrity sha1-/WU28rzhODb/o6VFjEkDpZe7O/U= - -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" - integrity sha1-llxwWGaOgrVde/4E/yM3vItWR/4= - 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" - integrity sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ== - -backo2@1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947" - integrity sha1-MasayLEpNjRj41s+u2n038+6eUc= - -balanced-match@^0.4.2: - version "0.4.2" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.4.2.tgz#cb3f3e3c732dc0f01ee70b403f302e61d7709838" - integrity sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg= - balanced-match@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" - integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= - -base64-arraybuffer@0.1.5: - version "0.1.5" - resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz#73926771923b5a19747ad666aa5cd4bf9c6e9ce8" - integrity sha1-c5JncZI7Whl0etZmqlzUv5xunOg= + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== base64-js@^1.0.2: - version "1.3.1" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1" - integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g== - -base@^0.11.1: - version "0.11.2" - resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" - integrity sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg== - 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" - integrity sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY= - -bcrypt-pbkdf@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" - integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= - dependencies: - tweetnacl "^0.14.3" + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== -better-assert@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/better-assert/-/better-assert-1.0.2.tgz#40866b9e1b9e0b55b481894311e68faffaebc522" - integrity sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI= - dependencies: - callsite "1.0.0" +big-integer@^1.6.16: + version "1.6.48" + resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.48.tgz#8fd88bd1632cba4a1c8c3e3d7159f08bb95b4b9e" + integrity sha512-j51egjPa7/i+RdiRuJbPdJ2FIUYYPhvYLjzoYbcMMm62ooO6F94fETG4MTs46zPAF9Brs04OajboA/qTGuz78w== big.js@^5.2.2: version "5.2.2" resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== -binary-extensions@^1.0.0: - version "1.13.1" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" - integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw== - binary-extensions@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.1.0.tgz#30fa40c9e7fe07dbc895678cd287024dea241dd9" - integrity sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ== - -bindings@^1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" - integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== - dependencies: - file-uri-to-path "1.0.0" - -blob@0.0.5: - version "0.0.5" - resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.5.tgz#d680eeef25f8cd91ad533f5b01eed48e64caf683" - integrity sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig== - -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.4.0: - version "4.11.9" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.9.tgz#26d556829458f9d1e81fc48952493d0ba3507828" - integrity sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw== - -bn.js@^5.1.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.1.2.tgz#c9686902d3c9a27729f43ab10f9d79c2004da7b0" - integrity sha512-40rZaf3bUNKTVYu9sIeeEGOg7g14Yvnj9kH7b50EiwX0Q7A6umbvfI5tvHaOERH0XigqKkfLkFQxzb4e6CIXnA== - -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" - integrity sha1-jokKGD2O6aI5OzhExpGkK897yfU= - 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" + version "2.2.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" + integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== -boolbase@^1.0.0, boolbase@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" - integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= +bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.9: + version "4.12.0" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" + integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== -bootstrap@4.5.3: - version "4.5.3" - resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.5.3.tgz#c6a72b355aaf323920be800246a6e4ef30997fe6" - integrity sha512-o9ppKQioXGqhw8Z7mah6KdTYpNQY//tipnkxppWhPbiSWdD+1raYsnhwEZjkTHYbGee4cVQ0Rx65EhOY/HNLcQ== +bn.js@^5.0.0, bn.js@^5.1.1: + version "5.2.0" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.0.tgz#358860674396c6997771a9d051fcc1b57d4ae002" + integrity sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw== brace-expansion@^1.1.7: version "1.1.11" @@ -2572,22 +815,6 @@ brace-expansion@^1.1.7: 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" - integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== - 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" @@ -2595,22 +822,31 @@ braces@^3.0.1, braces@~3.0.2: dependencies: fill-range "^7.0.1" -brorand@^1.0.1: +broadcast-channel@^3.4.1: + version "3.7.0" + resolved "https://registry.yarnpkg.com/broadcast-channel/-/broadcast-channel-3.7.0.tgz#2dfa5c7b4289547ac3f6705f9c00af8723889937" + integrity sha512-cIAKJXAxGJceNZGTZSBzMxzyOn72cVgPnKx4dc6LRjQgbaJUQqhy5rzL3zbMxkMWsGKkv2hSFkPRMEXfoMZ2Mg== + dependencies: + "@babel/runtime" "^7.7.2" + detect-node "^2.1.0" + js-sha3 "0.8.0" + microseconds "0.2.0" + nano-time "1.0.0" + oblivious-set "1.0.0" + rimraf "3.0.2" + unload "2.2.0" + +brorand@^1.0.1, brorand@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8= -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== +browser-tabs-lock@^1.2.14: + version "1.2.15" + resolved "https://registry.yarnpkg.com/browser-tabs-lock/-/browser-tabs-lock-1.2.15.tgz#d5012e652e2a0cb4eba471b0a2300c2fa5d92788" + integrity sha512-J8K9vdivK0Di+b8SBdE7EZxDr88TnATing7XoLw6+nFkXMQ6sVBh92K3NQvZlZU91AIkFRi0w3sztk5Z+vsswA== dependencies: - resolve "1.1.7" + lodash ">=4.17.21" browserify-aes@^1.0.0, browserify-aes@^1.0.4: version "1.2.0" @@ -2644,77 +880,59 @@ browserify-des@^1.0.0: safe-buffer "^5.1.2" browserify-rsa@^4.0.0, browserify-rsa@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.0.1.tgz#21e0abfaf6f2029cf2fafb133567a701d4135524" - integrity sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ= + version "4.1.0" + resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.1.0.tgz#b2fd06b5b75ae297f7ce2dc651f918f5be158c8d" + integrity sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog== dependencies: - bn.js "^4.1.0" + bn.js "^5.0.0" randombytes "^2.0.1" browserify-sign@^4.0.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.2.0.tgz#545d0b1b07e6b2c99211082bf1b12cce7a0b0e11" - integrity sha512-hEZC1KEeYuoHRqhGhTy6gWrpJA3ZDjFWv0DE61643ZnOXAKJb3u7yWcrU0mMc9SwAqK1n7myPGndkp0dFG7NFA== + version "4.2.1" + resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.2.1.tgz#eaf4add46dd54be3bb3b36c0cf15abbeba7956c3" + integrity sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg== dependencies: bn.js "^5.1.1" browserify-rsa "^4.0.1" create-hash "^1.2.0" create-hmac "^1.1.7" - elliptic "^6.5.2" + elliptic "^6.5.3" inherits "^2.0.4" parse-asn1 "^5.1.5" readable-stream "^3.6.0" safe-buffer "^5.2.0" -browserify-zlib@^0.2.0: +browserify-zlib@0.2.0, browserify-zlib@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz#2869459d9aa3be245fe8fe2ca1f46e2e7f54d73f" integrity sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA== 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.2" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.12.2.tgz#76653d7e4c57caa8a1a28513e2f4e197dc11a711" - integrity sha512-MfZaeYqR8StRZdstAK9hCKDd2StvePCYp5rHzQCPicUjfFliDgmuaBNPHYUTpAywBN8+Wc/d7NYVFkO0aqaBUw== +browserslist@4.16.6: + version "4.16.6" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.6.tgz#d7901277a5a88e554ed305b183ec9b0c08f66fa2" + integrity sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ== dependencies: - caniuse-lite "^1.0.30001088" - electron-to-chromium "^1.3.483" - escalade "^3.0.1" - node-releases "^1.1.58" - -bser@2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" - integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== - 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" - integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== - -buffer-indexof@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/buffer-indexof/-/buffer-indexof-1.1.1.tgz#52fabcc6a606d1a00302802648ef68f639da268c" - integrity sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g== + caniuse-lite "^1.0.30001219" + colorette "^1.2.2" + electron-to-chromium "^1.3.723" + escalade "^3.1.1" + node-releases "^1.1.71" buffer-xor@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" integrity sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk= +buffer@5.6.0: + version "5.6.0" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.6.0.tgz#a31749dc7d81d84db08abf937b6b8c4033f62786" + integrity sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw== + dependencies: + base64-js "^1.0.2" + ieee754 "^1.1.4" + buffer@^4.3.0: version "4.9.2" resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.2.tgz#230ead344002988644841ab0244af8c44bbe3ef8" @@ -2729,161 +947,30 @@ builtin-status-codes@^3.0.0: resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug= -bytes@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" - integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg= - 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" - integrity sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ== - 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= +call-bind@^1.0.0, call-bind@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" + integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== 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" - integrity sha1-KAOY5dZkvXQDi28JBRU+borxvCA= - -callsites@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50" - integrity sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA= + function-bind "^1.1.1" + get-intrinsic "^1.0.2" 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@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== +caniuse-lite@^1.0.30001202, caniuse-lite@^1.0.30001219, caniuse-lite@^1.0.30001228: + version "1.0.30001255" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001255.tgz#f3b09b59ab52e39e751a569523618f47c4298ca0" + integrity sha512-F+A3N9jTZL882f/fg/WWVnKSu6IOo3ueLz4zwaOPbPYHNmM/ZaDUyzyJwS1mZhX7Ex5jqTyW599Gdelh5PDYLQ== -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.30001087, caniuse-lite@^1.0.30001088: - version "1.0.30001090" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001090.tgz#ff7766332f60e80fea4903f30d360622e5551850" - integrity sha512-QzPRKDCyp7RhjczTPZaqK3CjPA5Ht2UnXhZhCI4f7QiB5JK6KEuZBxIzyWnB3wO4hgAj4GMRxAhuiacfw0Psjg== - -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" - integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= - -chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.4.1, chalk@^2.4.2: +chalk@2.4.2, chalk@^2.0.0: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -2892,39 +979,23 @@ chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.4.1, chalk@^2.4. escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" - integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg= - 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== +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" -chalk@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" - integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== +chalk@^4.0.0, chalk@^4.1.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== 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@>=3.0.0 <4.0.0": +chokidar@3.5.1: version "3.5.1" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.1.tgz#ee9ce7bbebd2b79f49f304799d5468e31e14e68a" integrity sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw== @@ -2939,51 +1010,20 @@ chardet@^0.7.0: optionalDependencies: fsevents "~2.3.1" -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== +"chokidar@>=3.0.0 <4.0.0": + version "3.5.2" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.2.tgz#dba3976fcadb016f66fd365021d91600d01c1e75" + integrity sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ== dependencies: - anymatch "~3.1.1" + anymatch "~3.1.2" braces "~3.0.2" - glob-parent "~5.1.0" + glob-parent "~5.1.2" is-binary-path "~2.1.0" is-glob "~4.0.1" normalize-path "~3.0.0" - readdirp "~3.4.0" + readdirp "~3.6.0" optionalDependencies: - fsevents "~2.1.2" - -chownr@^1.1.1, 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" + fsevents "~2.3.2" ci-info@^2.0.0: version "2.0.0" @@ -2998,27 +1038,15 @@ cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: 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" - integrity sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg== - dependencies: - arr-union "^3.1.0" - define-property "^0.2.5" - isobject "^3.0.0" - static-extend "^0.1.1" - -classnames@^2.2.3, classnames@^2.2.5, classnames@~2.2.5: +classnames@2.2.6: 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" +classnames@^2.2.5, classnames@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.1.tgz#dfcfa3891e306ec1dad105d0e88f4417b8535e8e" + integrity sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA== clean-stack@^2.0.0: version "2.2.0" @@ -3032,7 +1060,7 @@ cli-cursor@^3.1.0: dependencies: restore-cursor "^3.1.0" -cli-truncate@2.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== @@ -3040,77 +1068,7 @@ cli-truncate@2.1.0, cli-truncate@^2.1.0: slice-ansi "^3.0.0" string-width "^4.2.0" -cli-width@^2.0.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.1.tgz#b0433d0b4e9c847ef18868a4ef16fd5fc8271c48" - integrity sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw== - -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" - integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ= - -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" - integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= - -collection-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" - integrity sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA= - dependencies: - map-visit "^1.0.0" - object-visit "^1.0.0" - -color-convert@^1.9.0, color-convert@^1.9.1: +color-convert@^1.9.0: version "1.9.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== @@ -3129,63 +1087,20 @@ color-name@1.1.3: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= -color-name@^1.0.0, color-name@~1.1.4: +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" - -colorette@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.0.tgz#45306add826d196e8c87236ac05d797f25982e63" - integrity sha512-soRSroY+OF/8OdA3PTQXwaDJeMc7TfknKKrxeSCencL2a4+Tx5zhxmmv7hdpCjhKBjehzp8+bwe/T68K0hpIjw== - -combined-stream@^1.0.6, combined-stream@~1.0.6: - version "1.0.8" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" - integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== - 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, 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== +colorette@^1.2.2: + version "1.3.0" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.3.0.tgz#ff45d2f0edb244069d3b772adeb04fed38d0a0af" + integrity sha512-ecORCqbSFP7Wm8Y6lyqMJjexBQqXSF7SSeaTyGGphogUjBlFP9m9o08wy86HL2uB7fMTxtOUzLMk7ogKcxMg1w== -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== +commander@^6.2.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" + integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== commondir@^1.0.1: version "1.0.1" @@ -3198,56 +1113,9 @@ compare-versions@^3.6.0: integrity sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA== complex.js@^2.0.11: - version "2.0.11" - resolved "https://registry.yarnpkg.com/complex.js/-/complex.js-2.0.11.tgz#09a873fbf15ffd8c18c9c2201ccef425c32b8bf1" - integrity sha512-6IArJLApNtdg1P1dFtn3dnyzoZBEF0MwMnrfF1exSBRpZYoy4yieMkpZhQDC0uwctw48vii0CFVyHfpgZ/DfGw== - -component-bind@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/component-bind/-/component-bind-1.0.0.tgz#00c608ab7dcd93897c0009651b1d3a8e1e73bbd1" - integrity sha1-AMYIq33Nk4l8AAllGx06jh5zu9E= - -component-emitter@1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" - integrity sha1-E3kY1teCg/ffemt8WmPhQOaUJeY= - -component-emitter@^1.2.1, 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" - integrity sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM= - -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" + version "2.0.15" + resolved "https://registry.yarnpkg.com/complex.js/-/complex.js-2.0.15.tgz#7add6848b4c1d12aa9262f7df925ebe7a51a7406" + integrity sha512-gDBvQU8IG139ZBQTSo2qvDFP+lANMGluM779csXOr6ny1NUtA3wkUnCFjlDNH/moAVfXtvClYt6G0zarFbtz5w== computed-styles@^1.1.2: version "1.1.2" @@ -3259,148 +1127,61 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= -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.2.0" resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.2.0.tgz#67063cef57ceb6cf4993a2ab3a55840ae8c49336" integrity sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA== -constants-browserify@^1.0.0: +constants-browserify@1.0.0, constants-browserify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" integrity sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U= -contains-path@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a" - integrity sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo= - -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" - integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== - -convert-source-map@1.7.0, convert-source-map@^1.4.0, 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= +core-js-pure@^3.16.0: + version "3.17.2" + resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.17.2.tgz#ba6311b6aa1e2f2adeba4ac6ec51a9ff40bdc1af" + integrity sha512-2VV7DlIbooyTI7Bh+yzOOWL9tGwLnQKHno7qATE+fqZzDKYr6llVjVQOzpD/QLZFgXDPb8T71pJokHEZHEYJhQ== -cookie-signature@1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" - integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= - -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== +core-js@^2.5.0: + version "2.6.12" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec" + integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ== -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" - integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= - -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, core-js@^2.6.10: - version "2.6.11" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c" - integrity sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg== - -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" - integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= +core-js@^3.16.1: + version "3.17.2" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.17.2.tgz#f960eae710dc62c29cca93d5332e3660e289db10" + integrity sha512-XkbXqhcXeMHPRk2ItS+zQYliAMilea2euoMsnpRRdDad6b2VY6CQQcwz1K8AnWesfw4p165RzY0bTnr3UrbYiA== -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" +core-util-is@~1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" + integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== -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== +cosmiconfig@^7.0.0: + version "7.0.1" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.1.tgz#714d756522cace867867ccb4474c5d01bbae5d6d" + integrity sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ== dependencies: "@types/parse-json" "^4.0.0" - import-fresh "^3.1.0" + import-fresh "^3.2.1" parse-json "^5.0.0" path-type "^4.0.0" - yaml "^1.7.2" + yaml "^1.10.0" create-ecdh@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.3.tgz#c9111b6f33045c4697f144787f9254cdc77c45ff" - integrity sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw== + version "4.0.4" + resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.4.tgz#d6e7f4bffa66736085a0762fd3a632684dabcc4e" + integrity sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A== dependencies: bn.js "^4.1.0" - elliptic "^6.0.0" + elliptic "^6.5.3" create-hash@^1.1.0, create-hash@^1.1.2, create-hash@^1.2.0: version "1.2.0" @@ -3425,35 +1206,7 @@ create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7: safe-buffer "^5.0.1" sha.js "^2.4.8" -create-react-context@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/create-react-context/-/create-react-context-0.3.0.tgz#546dede9dc422def0d3fc2fe03afe0bc0f4f7d8c" - integrity sha512-dNldIoSuNSvlTJ7slIKC/ZFGKexBMBrrcc+TTe1NdmROnaASuLPvqpwj9v4XS4uXZ8+YPu0sNmShX2rXI5LNsw== - dependencies: - gud "^1.0.0" - warning "^4.0.3" - -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@^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" - integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== - 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: +cross-spawn@^7.0.0, cross-spawn@^7.0.2: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== @@ -3462,7 +1215,7 @@ cross-spawn@^7.0.0: shebang-command "^2.0.0" which "^2.0.1" -crypto-browserify@^3.11.0: +crypto-browserify@3.12.0, crypto-browserify@^3.11.0: version "3.12.0" resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" integrity sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg== @@ -3479,371 +1232,136 @@ crypto-browserify@^3.11.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" - integrity sha1-gIrcLnnPhHOAabZGyyDsJ762KeA= - -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-unit-converter@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/css-unit-converter/-/css-unit-converter-1.1.2.tgz#4c77f5a1954e6dbff60695ecb214e3270436ab21" + integrity sha512-IiJwMC8rdZE0+xiEZHeru6YoONC4rfPMqGm2W85jMIbkFvv5nFTwJVFHam2eFrN6txmoUYFAFXiv8ICVeTO0MA== -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.escape@1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb" + integrity sha1-QuJ9T6BK4y+TGktNQZH6nN3ul8s= -css-select@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858" - integrity sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg= +cssnano-preset-simple@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cssnano-preset-simple/-/cssnano-preset-simple-3.0.0.tgz#e95d0012699ca2c741306e9a3b8eeb495a348dbe" + integrity sha512-vxQPeoMRqUT3c/9f0vWeVa2nKQIHFpogtoBvFdW4GQ3IvEJ6uauCP6p3Y5zQDLFcI7/+40FTgX12o7XUL0Ko+w== dependencies: - boolbase "~1.0.0" - css-what "2.1" - domutils "1.5.1" - nth-check "~1.0.1" + caniuse-lite "^1.0.30001202" -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== +cssnano-simple@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cssnano-simple/-/cssnano-simple-3.0.0.tgz#a4b8ccdef4c7084af97e19bc5b93b4ecf211e90f" + integrity sha512-oU3ueli5Dtwgh0DyeohcIEE00QVfbPR3HzyXdAl89SfnQG3y0/qcpfLVW+jPIh3/rgMZGwuW96rejZGaYE9eUg== dependencies: - boolbase "^1.0.0" - css-what "^3.2.1" - domutils "^1.7.0" - nth-check "^1.0.2" + cssnano-preset-simple "^3.0.0" -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" +csstype@^3.0.2: + version "3.0.8" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.8.tgz#d2266a792729fb227cd216fb572f43728e1ad340" + integrity sha512-jXKhWqXPmlUeoQnF/EhTtTl4C9SnrxSH/jZUih3jmO6lBKr99rP3/+FmrMj4EFpOXzMtXHAZkd3x0E6h6Fgflw== -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== +d3-array@2, d3-array@^2.3.0: + version "2.12.1" + resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-2.12.1.tgz#e20b41aafcdffdf5d50928004ececf815a465e81" + integrity sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ== dependencies: - mdn-data "2.0.6" - source-map "^0.6.1" + internmap "^1.0.0" -css-what@2.1: - version "2.1.3" - resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.3.tgz#a6d7604573365fe74686c3f311c56513d88285f2" - integrity sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg== +"d3-color@1 - 2": + version "2.0.0" + resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-2.0.0.tgz#8d625cab42ed9b8f601a1760a389f7ea9189d62e" + integrity sha512-SPXi0TSKPD4g9tw0NMZFnR95XVgUZiBH+uUTqQuDu1OsE2zomHU7ho0FISciaPvosimixwHFl3WHLGabv6dDgQ== -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== +"d3-format@1 - 2": + version "2.0.0" + resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-2.0.0.tgz#a10bcc0f986c372b729ba447382413aabf5b0767" + integrity sha512-Ab3S6XuE/Q+flY96HXT0jOXcM4EAClYFnRGY5zsjRGNy6qCYrQsMffs7cV5Q9xejb35zxW5hf/guKw34kvIKsA== -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== +"d3-interpolate@1.2.0 - 2", d3-interpolate@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-2.0.1.tgz#98be499cfb8a3b94d4ff616900501a64abc91163" + integrity sha512-c5UhwwTs/yybcmTpAVqwSFl6vrQ8JZJoT5F7xNFK9pymv5C0Ymcc9/LIJHtYIggg/yS9YHw8i8O8tgb9pupjeQ== 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== + d3-color "1 - 2" -cssesc@^2.0.0: +"d3-path@1 - 2": 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= + resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-2.0.0.tgz#55d86ac131a0548adae241eebfb56b4582dd09d8" + integrity sha512-ZwZQxKhBnv9yHaiWd6ZU4x5BtCQ7pXszEV9CU6kRgwIQVQGLMv1oiL4M+MK/n79sYzsj+gcgpPQSctJUsLN7fA== -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== +d3-scale@^3.2.3: + version "3.3.0" + resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-3.3.0.tgz#28c600b29f47e5b9cd2df9749c206727966203f3" + integrity sha512-1JGp44NQCt5d1g+Yy+GeOnZP7xHo0ii8zsQp6PGzd+C1/dl0KGsp9A7Mxwp+1D1o4unbTTxVdU/ZOIEBoeZPbQ== dependencies: - postcss "^7.0.0" + d3-array "^2.3.0" + d3-format "1 - 2" + d3-interpolate "1.2.0 - 2" + d3-time "^2.1.1" + d3-time-format "2 - 3" -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== +d3-shape@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-2.1.0.tgz#3b6a82ccafbc45de55b57fcf956c584ded3b666f" + integrity sha512-PnjUqfM2PpskbSLTJvAzp2Wv4CZsnAgTfcVRTwW03QR3MkXF8Uo7B1y/lWkAsmbKwuecto++4NlsYcvYpXpTHA== dependencies: - cosmiconfig "^5.0.0" - cssnano-preset-default "^4.0.7" - is-resolvable "^1.0.0" - postcss "^7.0.0" + d3-path "1 - 2" -csso@^4.0.2: - version "4.0.3" - resolved "https://registry.yarnpkg.com/csso/-/csso-4.0.3.tgz#0d9985dc852c7cc2b2cacfbbe1079014d1a8e903" - integrity sha512-NL3spysxUkcrOgnpsT4Xdl2aiEiBG6bXswAABQVHcMrfjjBisFOKwLDOmf4wf32aPdcJws1zds2B0Rg+jqMyHQ== +"d3-time-format@2 - 3": + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-3.0.0.tgz#df8056c83659e01f20ac5da5fdeae7c08d5f1bb6" + integrity sha512-UXJh6EKsHBTjopVqZBhFysQcoXSv/5yLONZvkQ5Kk3qbwiUYkdX17Xa1PT6U1ZWXGGfB1ey5L8dKMlFq2DO0Ag== dependencies: - css-tree "1.0.0-alpha.39" - -cssom@0.3.x, "cssom@>= 0.3.2 < 0.4.0", cssom@^0.3.4: - version "0.3.8" - resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" - integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== + d3-time "1 - 2" -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== +"d3-time@1 - 2", d3-time@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-2.1.1.tgz#e9d8a8a88691f4548e68ca085e5ff956724a6682" + integrity sha512-/eIQe/eR4kCQwq7yxi7z4c6qEXf2IYGcjoWB5OOQy4Tq9Uv39/947qlDcN2TLkiTzQWzvnsuYPB9TrWaNfipKQ== dependencies: - cssom "0.3.x" - -csstype@^2.2.0: - version "2.6.10" - resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.10.tgz#e63af50e66d7c266edb6b32909cfd0aabe03928b" - integrity sha512-D34BqZU4cIlMCY93rZHbrq9pjTAQJ3U8S8rfBqjwHxkGPThWFjzZDQpgMJY0QViLxth6ZKYiwFBo14RdN44U/w== - -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 "2" -d3-array@^1.2.0: - version "1.2.4" - resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-1.2.4.tgz#635ce4d5eea759f6f605863dbcfc30edc737f71f" - integrity sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw== - -d3-collection@1: +damerau-levenshtein@^1.0.6: version "1.0.7" - resolved "https://registry.yarnpkg.com/d3-collection/-/d3-collection-1.0.7.tgz#349bd2aa9977db071091c13144d5e4f16b5b310e" - integrity sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A== - -d3-color@1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-1.4.1.tgz#c52002bf8846ada4424d55d97982fef26eb3bc8a" - integrity sha512-p2sTHSLCJI2QKunbGb7ocOh7DgTAn8IrLx21QRc/BSnodXM4sv6aLQlnfpvehFMLZEfBc6g9pH9SWQccFYfJ9Q== - -d3-format@1: - version "1.4.4" - resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-1.4.4.tgz#356925f28d0fd7c7983bfad593726fce46844030" - integrity sha512-TWks25e7t8/cqctxCmxpUuzZN11QxIA7YrMbram94zMQ0PXjE4LVIMe/f6a4+xxL8HQ3OsAFULOINQi1pE62Aw== - -d3-interpolate@1, d3-interpolate@^1.3.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-1.4.0.tgz#526e79e2d80daa383f9e0c1c1c7dcc0f0583e987" - integrity sha512-V9znK0zc3jOPV4VD2zZn0sDhZU3WAE2bmlxdIwwQPPzPjvyLkd8B3JUVdS1IDUFDkWZ72c9qnv1GK2ZagTZ8EA== - dependencies: - d3-color "1" - -d3-path@1: - version "1.0.9" - resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-1.0.9.tgz#48c050bb1fe8c262493a8caf5524e3e9591701cf" - integrity sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg== + resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.7.tgz#64368003512a1a6992593741a09a9d31a836f55d" + integrity sha512-VvdQIPGdWP0SqFXghj79Wf/5LArmreyMsGLa6FG6iC4t3j7j5s71TrwWmT/4akbDQIqjfACkLZmjXhA7g2oUZw== -d3-scale@^2.1.0: - version "2.2.2" - resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-2.2.2.tgz#4e880e0b2745acaaddd3ede26a9e908a9e17b81f" - integrity sha512-LbeEvGgIb8UMcAa0EATLNX0lelKWGYDQiPdHj+gLblGVhGLyNbaCn3EvrJf0A3Y/uOOU5aD6MTh5ZFCdEwGiCw== - dependencies: - d3-array "^1.2.0" - d3-collection "1" - d3-format "1" - d3-interpolate "1" - d3-time "1" - d3-time-format "2" - -d3-shape@^1.2.0: - version "1.3.7" - resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-1.3.7.tgz#df63801be07bc986bc54f63789b4fe502992b5d7" - integrity sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw== - dependencies: - d3-path "1" - -d3-time-format@2: - version "2.2.3" - resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-2.2.3.tgz#0c9a12ee28342b2037e5ea1cf0b9eb4dd75f29cb" - integrity sha512-RAHNnD8+XvC4Zc4d2A56Uw0yJoM7bsvOlJR33bclxq399Rak/b9bhvu/InjxdWhPtkgU53JJcleJTGkNRnN6IA== - dependencies: - d3-time "1" - -d3-time@1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-1.1.0.tgz#b1e19d307dae9c900b7e5b25ffc5dcc249a8a0f1" - integrity sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA== - -d@1, d@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a" - integrity sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA== - dependencies: - es5-ext "^0.10.50" - type "^1.0.1" - -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" - integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA= - 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" +data-uri-to-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz#594b8973938c5bc2c33046535785341abc4f3636" + integrity sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og== -debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.9: +debug@2, debug@^2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== 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== +debug@^3.2.7: + version "3.2.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" + integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== dependencies: ms "^2.1.1" -debug@~3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" - integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== +debug@^4.0.1, debug@^4.1.1, debug@^4.2.0, debug@^4.3.1: + version "4.3.2" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" + integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== dependencies: - ms "2.0.0" - -decamelize@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" - integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= + ms "2.1.2" decimal.js-light@^2.4.1: - version "2.5.0" - resolved "https://registry.yarnpkg.com/decimal.js-light/-/decimal.js-light-2.5.0.tgz#ca7faf504c799326df94b0ab920424fdfc125348" - integrity sha512-b3VJCbd2hwUpeRGG3Toob+CRo8W22xplipNhP3tN7TSVB/cyMX71P1vM2Xjc9H74uV6dS2hDDmo/rHq8L87Upg== - -decimal.js@^10.2.0: - version "10.2.0" - resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.2.0.tgz#39466113a9e036111d02f82489b5fd6b0b5ed231" - integrity sha512-vDPw+rDgn3bZe1+F/pyEwb1oMG2XTlRVgAa6B4KccTEpYgF8w6eQllVbQcfIJnZyvzFtFpxnpGtx8dd7DJp/Rw== + version "2.5.1" + resolved "https://registry.yarnpkg.com/decimal.js-light/-/decimal.js-light-2.5.1.tgz#134fd32508f19e208f4fb2f8dac0d2626a867934" + integrity sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg== -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" - integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= +decimal.js@^10.2.1: + version "10.3.1" + resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.3.1.tgz#d8c3a444a9c6774ba60ca6ad7261c3a94fd5e783" + integrity sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ== dedent@^0.7.0: version "0.7.0" @@ -3855,78 +1373,18 @@ deep-diff@^0.3.5: resolved "https://registry.yarnpkg.com/deep-diff/-/deep-diff-0.3.8.tgz#c01de63efb0eec9798801d40c7e0dae25b582c84" integrity sha1-wB3mPvsO7JeYgB1Ax+Da4ltYLIQ= -deep-equal@^1.0.1, deep-equal@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.1.1.tgz#b5c98c942ceffaf7cb051e24e1434a25a2e6076a" - integrity sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g== - dependencies: - is-arguments "^1.0.4" - is-date-object "^1.0.1" - is-regex "^1.0.4" - object-is "^1.0.1" - object-keys "^1.1.1" - regexp.prototype.flags "^1.2.0" - -deep-is@~0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" - integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= - -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" +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== -define-properties@^1.1.2, define-properties@^1.1.3: +define-properties@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== 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" - integrity sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY= - 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" - integrity sha1-dp66rz9KY6rTr56NMEybvnm/sOY= - 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" - integrity sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ== - 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" - -delayed-stream@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" - integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= - depd@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" @@ -3940,33 +1398,10 @@ des.js@^1.0.0: 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" - integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= - -detect-newline@^2.1.0: +detect-node@^2.0.4, detect-node@^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" - integrity sha512-5tQykt+LqfJFBEYaDITx7S7cR7mJ/zQmLXZ2qt5w04ainYZw6tBf9dBunMjVeVOdYVRUzUOE4HkY5J7+uttb5Q== - 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== + resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" + integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== diffie-hellman@^5.0.0: version "5.0.3" @@ -3977,41 +1412,12 @@ diffie-hellman@^5.0.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" - integrity sha1-s55/HabrCnW6nBcySzR1PEfgZU0= - -dns-packet@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-1.3.1.tgz#12aa426981075be500b910eedcd0b47dd7deda5a" - integrity sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg== - 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" - integrity sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY= - 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" - integrity sha1-N53Ocw9hZvds76TmcHoVmwLFpvo= +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== dependencies: - esutils "^2.0.2" - isarray "^1.0.0" + path-type "^4.0.0" doctrine@^2.1.0: version "2.1.0" @@ -4027,13 +1433,6 @@ doctrine@^3.0.0: 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" - integrity sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA== - dependencies: - utila "~0.4" - dom-helpers@^3.4.0: version "3.4.0" resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.4.0.tgz#e9b369700f959f62ecde5a6babde4bccd9169af8" @@ -4041,246 +1440,112 @@ dom-helpers@^3.4.0: dependencies: "@babel/runtime" "^7.1.2" -dom-serializer@0: - version "0.2.2" - resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51" - integrity sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g== - dependencies: - domelementtype "^2.0.1" - entities "^2.0.0" +domain-browser@4.19.0: + version "4.19.0" + resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-4.19.0.tgz#1093e17c0a17dbd521182fe90d49ac1370054af1" + integrity sha512-fRA+BaAWOR/yr/t7T9E9GJztHPeFjj8U35ajyAjCDtAAnTn1Rc1f6W6VGPJrO1tkQv9zWu+JRof7z6oQtiYVFQ== domain-browser@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" integrity sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA== -domelementtype@1, domelementtype@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" - integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== - -domelementtype@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.0.1.tgz#1f8bdfe91f5a78063274e803b4bdcedf6e94f94d" - integrity sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ== - -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.3.0: - version "2.4.2" - resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803" - integrity sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA== - dependencies: - domelementtype "1" - -domutils@1.5.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf" - integrity sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8= - dependencies: - dom-serializer "0" - domelementtype "1" - -domutils@^1.5.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" - integrity sha1-rOb/gIwc5mtX0ev5eXessCM0z8E= - -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" - integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk= - 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" - integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= - -electron-to-chromium@^1.3.378, electron-to-chromium@^1.3.483: - version "1.3.483" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.483.tgz#9269e7cfc1c8e72709824da171cbe47ca5e3ca9e" - integrity sha512-+05RF8S9rk8S0G8eBCqBRBaRq7+UN3lDs2DAvnG8SBSgQO3hjy0+qt4CmRk5eiuGbTcaicgXfPmBi31a+BD3lg== +electron-to-chromium@^1.3.723: + version "1.3.831" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.831.tgz#96201f83f6eef05054b532a897fd3cd73cd60250" + integrity sha512-0tc2lPzgEipHCyRcvDTTaBk5+jSPfNaCvbQdevNMqJkHLvrBiwhygPR0hDyPZEK7Xztvv+58gSFKJ/AUVT1yYQ== -elliptic@^6.0.0, elliptic@^6.5.2: - version "6.5.3" - resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.3.tgz#cb59eb2efdaf73a0bd78ccd7015a62ad6e0f93d6" - integrity sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw== +elliptic@^6.5.3: + version "6.5.4" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" + integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== dependencies: - bn.js "^4.4.0" - brorand "^1.0.1" + bn.js "^4.11.9" + brorand "^1.1.0" 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== + hmac-drbg "^1.0.1" + inherits "^2.0.4" + minimalistic-assert "^1.0.1" + minimalistic-crypto-utils "^1.0.1" 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== +emoji-regex@^9.0.0: + version "9.2.2" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" + integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== + emojis-list@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389" integrity sha1-TapNnbAPmBmIDHn6RXrlsJof04k= -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" - integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= +encoding@0.1.13: + version "0.1.13" + resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9" + integrity sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A== + dependencies: + iconv-lite "^0.6.2" -end-of-stream@^1.0.0, end-of-stream@^1.1.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.2.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.2.0.tgz#5d43bda4a0fd447cb0ebbe71bef8deff8805ad0d" - integrity sha512-S7eiFb/erugyd1rLb6mQ3Vuq+EXHv5cpCkNqqIkYkBgN2QdFnyCZzFBleqwGEx4lgNGYij81BWnCrFNK7vxvjQ== - 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== +enhanced-resolve@^5.7.0: + version "5.8.2" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.8.2.tgz#15ddc779345cbb73e97c611cd00c01c1e7bf4d8b" + integrity sha512-F27oB3WuHDzvR2DOGNTaYy0D5o0cnrv8TeI482VM4kYgQd/FT9lUQwuNsJ0oOHtBUq7eiW5ytqzp7nBFknL+GA== 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" - integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w== - -entities@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.3.tgz#5c487e5742ab93c15abb5da22759b8590ec03b7f" - integrity sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ== + graceful-fs "^4.2.4" + tapable "^2.2.0" -errno@^0.1.3, errno@~0.1.7: - version "0.1.7" - resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618" - integrity sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg== +enquirer@^2.3.5, enquirer@^2.3.6: + version "2.3.6" + resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" + integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== dependencies: - prr "~1.0.1" + ansi-colors "^4.1.1" -error-ex@^1.2.0, error-ex@^1.3.1: +error-ex@^1.3.1: version "1.3.2" resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== 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.6" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.6.tgz#9142071707857b2cacc7b89ecb670316c3e2d52a" - integrity sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw== +es-abstract@^1.18.0-next.1, es-abstract@^1.18.0-next.2, es-abstract@^1.18.2, es-abstract@^1.18.5: + version "1.18.5" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.5.tgz#9b10de7d4c206a3581fd5b2124233e04db49ae19" + integrity sha512-DDggyJLoS91CkJjgauM5c0yZMjiD1uK3KcaCeAmffGwZ+ODWzOkPN4QwRbsK5DOFf06fywmyLci3ZD8jLGhVYA== dependencies: + call-bind "^1.0.2" es-to-primitive "^1.2.1" function-bind "^1.1.1" + get-intrinsic "^1.1.1" has "^1.0.3" - has-symbols "^1.0.1" - is-callable "^1.2.0" - is-regex "^1.1.0" - object-inspect "^1.7.0" + has-symbols "^1.0.2" + internal-slot "^1.0.3" + is-callable "^1.2.3" + is-negative-zero "^2.0.1" + is-regex "^1.1.3" + is-string "^1.0.6" + object-inspect "^1.11.0" object-keys "^1.1.1" - object.assign "^4.1.0" - string.prototype.trimend "^1.0.1" - string.prototype.trimstart "^1.0.1" + object.assign "^4.1.2" + string.prototype.trimend "^1.0.4" + string.prototype.trimstart "^1.0.4" + unbox-primitive "^1.0.1" + +es-cookie@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/es-cookie/-/es-cookie-1.3.2.tgz#80e831597f72a25721701bdcb21d990319acd831" + integrity sha512-UTlYYhXGLOy05P/vKVT2Ui7WtC7NiRzGtJyAKKn32g5Gvcjn7KAClLPWlipCtxIus934dFg9o9jXiBL0nP+t9Q== es-to-primitive@^1.2.1: version "1.2.1" @@ -4291,313 +1556,263 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" -es5-ext@^0.10.35, es5-ext@^0.10.50: - version "0.10.53" - resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.53.tgz#93c5a3acfdbef275220ad72644ad02ee18368de1" - integrity sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q== - dependencies: - es6-iterator "~2.0.3" - es6-symbol "~3.1.3" - next-tick "~1.0.0" - -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.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18" - integrity sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA== - dependencies: - d "^1.0.1" - ext "^1.1.2" - -escalade@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.0.1.tgz#52568a77443f6927cd0ab9c73129137533c965ed" - integrity sha512-DR6NO3h9niOT+MZs7bjxlj2a1k+POu5RN8CLTPX2+i78bRi9eLe7+0zXgUHMnGXWybYcL61E9hGhPKqedy8tQA== +es6-object-assign@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/es6-object-assign/-/es6-object-assign-1.1.0.tgz#c2c3582656247c39ea107cb1e6652b6f9f24523c" + integrity sha1-wsNYJlYkfDnqEHyx5mUrb58kUjw= -escape-html@~1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" - integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= +escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== escape-latex@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/escape-latex/-/escape-latex-1.2.0.tgz#07c03818cf7dac250cce517f4fda1b001ef2bca1" integrity sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw== -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: +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" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= -escodegen@^1.11.0, escodegen@^1.9.1: - version "1.14.3" - resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.3.tgz#4e7b81fba61581dc97582ed78cab7f0e8d63f503" - integrity sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw== - 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.4" - resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz#85ffa81942c25012d8231096ddf679c03042c717" - integrity sha512-ogtf+5AB/O+nM6DIeBUNr2fuT7ot9Qg/1harBfBtaP13ekEWFQEEMP94BCB7zaNW3gyY+8SHYF00rnqYwXKWOA== +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +eslint-config-next@^11.1.2: + version "11.1.2" + resolved "https://registry.yarnpkg.com/eslint-config-next/-/eslint-config-next-11.1.2.tgz#73c918f2fa6120d5f65080bf3fcf6b154905707e" + integrity sha512-dFutecxX2Z5/QVlLwdtKt+gIfmNMP8Qx6/qZh3LM/DFVdGJEAnUKrr4VwGmACB2kx/PQ5bx3R+QxnEg4fDPiTg== + dependencies: + "@next/eslint-plugin-next" "11.1.2" + "@rushstack/eslint-patch" "^1.0.6" + "@typescript-eslint/parser" "^4.20.0" + eslint-import-resolver-node "^0.3.4" + eslint-import-resolver-typescript "^2.4.0" + eslint-plugin-import "^2.22.1" + eslint-plugin-jsx-a11y "^6.4.1" + eslint-plugin-react "^7.23.1" + eslint-plugin-react-hooks "^4.2.0" + +eslint-import-resolver-node@^0.3.4, eslint-import-resolver-node@^0.3.6: + version "0.3.6" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz#4048b958395da89668252001dbd9eca6b83bacbd" + integrity sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw== dependencies: - debug "^2.6.9" - resolve "^1.13.1" + debug "^3.2.7" + resolve "^1.20.0" -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== +eslint-import-resolver-typescript@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-2.4.0.tgz#ec1e7063ebe807f0362a7320543aaed6fe1100e1" + integrity sha512-useJKURidCcldRLCNKWemr1fFQL1SzB3G4a0li6lFGvlc5xGe1hY343bvG07cbpCzPuM/lK19FIJB3XGFSkplA== 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" + debug "^4.1.1" + glob "^7.1.6" + is-glob "^4.0.1" + resolve "^1.17.0" + tsconfig-paths "^3.9.0" -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== +eslint-module-utils@^2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.6.2.tgz#94e5540dd15fe1522e8ffa3ec8db3b7fa7e7a534" + integrity sha512-QG8pcgThYOuqxupd06oYTZoNOGaUdTY1PqK+oS6ElF6vs4pBdk/aYxFVQQXzcrAqp9m7cl7lb2ubazX+g16k2Q== dependencies: - debug "^2.6.9" + debug "^3.2.7" 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== +eslint-plugin-import@^2.22.1: + version "2.24.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.24.2.tgz#2c8cd2e341f3885918ee27d18479910ade7bb4da" + integrity sha512-hNVtyhiEtZmpsabL4neEj+6M5DCLgpYyG9nzJY8lZQeQXEn5UPW1DpUdsMHMXsq98dbNm7nt1w9ZMSVpfJdi8Q== 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" + array-includes "^3.1.3" + array.prototype.flat "^1.2.4" debug "^2.6.9" - doctrine "1.5.0" - eslint-import-resolver-node "^0.3.2" - eslint-module-utils "^2.4.1" + doctrine "^2.1.0" + eslint-import-resolver-node "^0.3.6" + eslint-module-utils "^2.6.2" + find-up "^2.0.0" has "^1.0.3" + is-core-module "^2.6.0" 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" + object.values "^1.1.4" + pkg-up "^2.0.0" + read-pkg-up "^3.0.0" + resolve "^1.20.0" + tsconfig-paths "^3.11.0" + +eslint-plugin-jsx-a11y@^6.4.1: + version "6.4.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.4.1.tgz#a2d84caa49756942f42f1ffab9002436391718fd" + integrity sha512-0rGPJBbwHoGNPU73/QCLP/vveMlM1b1Z9PponxO87jfr6tuH5ligXbDT6nHSSzBC8ovX2Z+BQu7Bk5D/Xgq9zg== + dependencies: + "@babel/runtime" "^7.11.2" + aria-query "^4.2.2" + array-includes "^3.1.1" ast-types-flow "^0.0.7" - axobject-query "^2.0.2" - damerau-levenshtein "^1.0.4" - emoji-regex "^7.0.2" + axe-core "^4.0.2" + axobject-query "^2.2.0" + damerau-levenshtein "^1.0.6" + emoji-regex "^9.0.0" has "^1.0.3" - jsx-ast-utils "^2.2.1" + jsx-ast-utils "^3.1.0" + language-tags "^1.0.5" -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-hooks@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.2.0.tgz#8c229c268d468956334c943bb45fc860280f5556" + integrity sha512-623WEiZJqxR7VdxFCKLI6d6LLpwJkGPYKODnkH3D7WpOG5KM8yWueBd8TLsNAetEJNF5iJmolaAKO3F8yzyVBQ== -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== +eslint-plugin-react@^7.23.1: + version "7.25.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.25.1.tgz#9286b7cd9bf917d40309760f403e53016eda8331" + integrity sha512-P4j9K1dHoFXxDNP05AtixcJEvIT6ht8FhYKsrkY0MPCPaUMYijhpWwNiRDZVtA8KFuZOkGSeft6QwH8KuVpJug== dependencies: - array-includes "^3.1.1" + array-includes "^3.1.3" + array.prototype.flatmap "^1.2.4" doctrine "^2.1.0" + estraverse "^5.2.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" + jsx-ast-utils "^2.4.1 || ^3.0.0" + minimatch "^3.0.4" + object.entries "^1.1.4" + object.fromentries "^2.0.4" + object.values "^1.1.4" 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" + resolve "^2.0.0-next.3" + string.prototype.matchall "^4.0.5" -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== +eslint-scope@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== dependencies: - esrecurse "^4.1.0" + esrecurse "^4.3.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: +eslint-utils@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27" integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg== dependencies: eslint-visitor-keys "^1.1.0" -eslint-visitor-keys@^1.0.0, eslint-visitor-keys@^1.1.0: +eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== -eslint@^6.6.0: - version "6.8.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.8.0.tgz#62262d6729739f9275723824302fb227c8c93ffb" - integrity sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig== +eslint-visitor-keys@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" + integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== + +eslint@^7.32.0: + version "7.32.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.32.0.tgz#c6d328a14be3fb08c8d1d21e12c02fdb7a2a812d" + integrity sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA== dependencies: - "@babel/code-frame" "^7.0.0" + "@babel/code-frame" "7.12.11" + "@eslint/eslintrc" "^0.4.3" + "@humanwhocodes/config-array" "^0.5.0" ajv "^6.10.0" - chalk "^2.1.0" - cross-spawn "^6.0.5" + chalk "^4.0.0" + cross-spawn "^7.0.2" 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" + enquirer "^2.3.5" + escape-string-regexp "^4.0.0" + eslint-scope "^5.1.1" + eslint-utils "^2.1.0" + eslint-visitor-keys "^2.0.0" + espree "^7.3.1" + esquery "^1.4.0" esutils "^2.0.2" - file-entry-cache "^5.0.1" + fast-deep-equal "^3.1.3" + file-entry-cache "^6.0.1" functional-red-black-tree "^1.0.1" - glob-parent "^5.0.0" - globals "^12.1.0" + glob-parent "^5.1.2" + globals "^13.6.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" + levn "^0.4.1" + lodash.merge "^4.6.2" minimatch "^3.0.4" - mkdirp "^0.5.1" natural-compare "^1.4.0" - optionator "^0.8.3" + optionator "^0.9.1" 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" + regexpp "^3.1.0" + semver "^7.2.1" + strip-ansi "^6.0.0" + strip-json-comments "^3.1.0" + table "^6.0.9" 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== +espree@^7.3.0, espree@^7.3.1: + version "7.3.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.1.tgz#f2df330b752c6f55019f8bd89b7660039c1bbbb6" + integrity sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g== dependencies: - acorn "^7.1.1" - acorn-jsx "^5.2.0" - eslint-visitor-keys "^1.1.0" + acorn "^7.4.0" + acorn-jsx "^5.3.1" + eslint-visitor-keys "^1.3.0" -esprima@^4.0.0, esprima@^4.0.1: +esprima@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== -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== +esquery@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" + integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== 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" - integrity sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ== +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== dependencies: - estraverse "^4.1.0" + estraverse "^5.2.0" -estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0: +estraverse@^4.1.1: version "4.3.0" resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== -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== +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.2.0.tgz#307df42547e6cc7324d3cf03c155d5cdb8c53880" + integrity sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ== esutils@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== -etag@~1.8.1: +etag@1.8.1: version "1.8.1" resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= -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" - integrity sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ= +eventemitter3@^4.0.1: + version "4.0.7" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" + integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== events@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/events/-/events-3.1.0.tgz#84279af1b34cb75aa88bf5ff291f6d0bd9b31a59" - integrity sha512-Rv+u8MLHNOdMjTAFeT3nCjHn2aGlx435FP/sDHNaRhDEMwyI/aB22Kj2qIN8R0cw3z28psEQLYwxVKLsKrMgWg== - -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" + version "3.3.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" + integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: version "1.0.3" @@ -4607,28 +1822,10 @@ evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: 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== +execa@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a" + integrity sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA== dependencies: cross-spawn "^7.0.0" get-stream "^5.0.0" @@ -4640,231 +1837,67 @@ execa@^4.0.1: signal-exit "^3.0.2" strip-final-newline "^2.0.0" -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" - integrity sha1-t3c14xXOMPa27/D4OwQVGiJEliI= - 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" - -ext@^1.1.2: - version "1.4.0" - resolved "https://registry.yarnpkg.com/ext/-/ext-1.4.0.tgz#89ae7a07158f79d35517882904324077e4379244" - integrity sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A== - dependencies: - type "^2.0.0" - -extend-shallow@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" - integrity sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8= - 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" - integrity sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg= - 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" - integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== - -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" - integrity sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw== - 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" - integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= - -extsprintf@^1.2.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" - integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= - -fast-deep-equal@^3.1.1: +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: 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== +fast-equals@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/fast-equals/-/fast-equals-2.0.3.tgz#7039b0a039909f345a2ce53f6202a14e5f392efc" + integrity sha512-0EMw4TTUxsMDpDkCg0rXor2gsg+npVrMIHbEhvD0HZyIhUX6AktC/yasm+qKwfyswd06Qy95ZKk8p2crTo0iPA== + +fast-glob@^3.1.1: + version "3.2.7" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.7.tgz#fd6cb7a2d7e9aa7a7846111e85a196d6b2f766a1" + integrity sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q== 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" + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" fast-json-stable-stringify@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== -fast-levenshtein@~2.0.6: +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" - integrity sha1-TkkvjQTftviQA1B/btvy1QHnxvQ= - 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.1" - resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.1.tgz#fc84fb39d2709cf3ff6d743706157bb5708a8a85" - integrity sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg== - dependencies: - bser "2.1.1" - -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" +fast-text-encoding@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/fast-text-encoding/-/fast-text-encoding-1.0.3.tgz#ec02ac8e01ab8a319af182dae2681213cfe9ce53" + integrity sha512-dtm4QZH9nZtcDt8qJiOH9fcQd1NAgi+K1O2DbE6GG1PPCK/BWfOH3idCTRQ4ImXRUOyopDEgDEnVEE7Y/2Wrig== -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== +fastq@^1.6.0: + version "1.12.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.12.0.tgz#ed7b6ab5d62393fb2cc591c853652a5c318bf794" + integrity sha512-VNX0QkHK3RsXVKr9KrlUv/FoTa0NdbYoHHl7uXHv2rzyHSlxjdNAKug2twd9luJxpcyNeAgf5iPPMutJO67Dfg== dependencies: - flat-cache "^2.0.1" + reusify "^1.0.4" -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== +file-entry-cache@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== dependencies: - loader-utils "^1.2.3" - schema-utils "^2.5.0" + flat-cache "^3.0.4" file-saver@^1.3.3: version "1.3.8" resolved "https://registry.yarnpkg.com/file-saver/-/file-saver-1.3.8.tgz#e68a30c7cb044e2fb362b428469feb291c2e09d8" integrity sha512-spKHSBQIxxS81N/O21WmuXA2F6wppUCsutpzenOeZzOCCJ5gEfcbqJP983IrpLXzYmXnMUa6J03SubcNPdKrlg== -file-uri-to-path@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" - integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== - -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" - integrity sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc= +file-selector@^0.1.8: + version "0.1.19" + resolved "https://registry.yarnpkg.com/file-selector/-/file-selector-0.1.19.tgz#8ecc9d069a6f544f2e4a096b64a8052e70ec8abf" + integrity sha512-kCWw3+Aai8Uox+5tHCNgMFaUdgidxvMnLWO6fM5sZ0hA2wlHP5/DHGF0ECe84BiB95qdJbKNEJhWKVDvMN+JDQ== dependencies: - extend-shallow "^2.0.1" - is-number "^3.0.0" - repeat-string "^1.6.1" - to-regex-range "^2.1.0" + tslib "^2.0.1" fill-range@^7.0.1: version "7.0.1" @@ -4873,38 +1906,7 @@ fill-range@^7.0.1: 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" - integrity sha1-yN765XyKUqinhPnjHFfHQumToLk= - 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: +find-cache-dir@3.3.1: 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== @@ -4913,22 +1915,6 @@ find-cache-dir@^3.2.0: 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" - integrity sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8= - 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" @@ -4936,195 +1922,65 @@ find-up@^2.0.0, find-up@^2.1.0: 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== +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: - semver-regex "^2.0.0" + locate-path "^5.0.0" + path-exists "^4.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== +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== 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.3" - resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.3.tgz#c1283ac9f27b368abc1e36d1ff7b04501a30356b" - integrity sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg== + locate-path "^6.0.0" + path-exists "^4.0.0" -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== +find-versions@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/find-versions/-/find-versions-4.0.0.tgz#3c57e573bf97769b8cb8df16934b627915da4965" + integrity sha512-wgpWy002tA+wgmO27buH/9KzyEOQnKsG/R0yrcjPT9BOFm0zRBVQbZ95nRGXWMywS8YR5knRbpohio0bcJABxQ== dependencies: - inherits "^2.0.3" - readable-stream "^2.3.6" - -follow-redirects@^1.0.0: - version "1.12.1" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.12.1.tgz#de54a6205311b93d60398ebc01cf7015682312b6" - integrity sha512-tmRv0AVuR7ZyouUHLeNSiO6pqulF7dYa3s19c6t+wz9LD69/uSzdMxJ2S91nTI9U3rt/IldxpzMOFejp6f0hjg== - -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" - integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= + semver-regex "^3.1.2" -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= +flat-cache@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" + integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== 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" - integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= + flatted "^3.1.0" + rimraf "^3.0.2" -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" +flatted@^3.1.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.2.tgz#64bfed5cb68fe3ca78b3eb214ad97b63bedce561" + integrity sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA== -form-data@~2.3.2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" - integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== +focus-trap@6.2.2: + version "6.2.2" + resolved "https://registry.yarnpkg.com/focus-trap/-/focus-trap-6.2.2.tgz#0e6f391415b0697c99da932702dedd13084fa131" + integrity sha512-qWovH9+LGoKqREvJaTCzJyO0hphQYGz+ap5Hc4NqXHNhZBdxCi5uBPPcaOUw66fHmzXLVwvETLvFgpwPILqKpg== dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.6" - mime-types "^2.1.12" + tabbable "^5.1.4" -forwarded@~0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" - integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= +foreach@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" + integrity sha1-C+4AUBiusmDQo6865ljdATbsG5k= fraction.js@^4.0.12: - version "4.0.12" - resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.0.12.tgz#0526d47c65a5fb4854df78bc77f7bec708d7b8c3" - integrity sha512-8Z1K0VTG4hzYY7kA/1sj4/r1/RWLBD3xwReT/RCrUCbzPszjNQCCsy3ktkU/eaEqX3MYa4pY37a52eiBlPMlhA== - -fragment-cache@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" - integrity sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk= - 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" - integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= - -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@^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" + version "4.1.1" + resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.1.1.tgz#ac4e520473dae67012d618aab91eda09bcb400ff" + integrity sha512-MHOhvvxHTfRFpF1geTK9czMIZ6xclsEor2wkIGYYq+PxcQqT7vStJqjhe6S1TenZrMZzo+wlqOufBDVepUEgPg== fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= -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.13" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.13.tgz#f325cb0455592428bcf11b383370ef70e3bfcc38" - integrity sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw== - dependencies: - bindings "^1.5.0" - nan "^2.12.1" - -fsevents@~2.1.2: - version "2.1.3" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e" - integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ== - -fsevents@~2.3.1: +fsevents@~2.3.1, fsevents@~2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== @@ -5139,76 +1995,50 @@ functional-red-black-tree@^1.0.1: resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= -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" - integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w== +get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" + integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" -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-orientation@1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/get-orientation/-/get-orientation-1.1.2.tgz#20507928951814f8a91ded0a0e67b29dfab98947" + integrity sha512-/pViTfifW+gBbh/RnlFYHINvELT9Znt+SYyDKAUL6uV6By019AK/s+i9XP4jSwq7lwP38Fd8HVeTxym3+hkwmQ== + dependencies: + stream-parser "^0.3.1" get-own-enumerable-property-symbols@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz#b5fde77f22cbe35f390b4e089922c50bce6ef664" integrity sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g== -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== + version "5.2.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" + integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== 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" - integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= - -getpass@^0.1.1: - version "0.1.7" - resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" - integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo= - 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" - integrity sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4= - 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== +glob-parent@^5.1.2, glob-parent@~5.1.0, glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== 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-to-regexp@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" + integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== -glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, 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== +glob@7.1.7, glob@^7.1.3, glob@^7.1.6: + version "7.1.7" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" + integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ== dependencies: fs.realpath "^1.0.0" inflight "^1.0.4" @@ -5217,122 +2047,34 @@ glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: 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" - integrity sha1-9abXDoOV4hyFj7BInWTfAkJNUGw= +globals@^13.6.0, globals@^13.9.0: + version "13.11.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.11.0.tgz#40ef678da117fe7bd2e28f1fab24951bd0255be7" + integrity sha512-08/xrJ7wQjK9kkkRoI3OFUBbLx4f+6x3SGwcPvQ0QH6goFDrOU2oyAWrmh3dJezu65buo+HBMzAMQy6rovVC3g== dependencies: - array-union "^1.0.1" - glob "^7.0.3" - object-assign "^4.0.1" - pify "^2.0.0" - pinkie-promise "^2.0.0" - -graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, 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== + type-fest "^0.20.2" -growly@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" - integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE= - -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== +globby@^11.0.3: + version "11.0.4" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.4.tgz#2cbaff77c2f2a62e71e9b2813a67b97a3a3001a5" + integrity sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg== 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" - integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= - -har-validator@~5.1.3: - version "5.1.3" - resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080" - integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g== - 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" - integrity sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE= - dependencies: - ansi-regex "^2.0.0" + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.1.1" + ignore "^5.1.4" + merge2 "^1.3.0" + slash "^3.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" - integrity sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw== - dependencies: - isarray "2.0.1" +graceful-fs@^4.1.2, graceful-fs@^4.2.4: + version "4.2.8" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a" + integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg== -has-cors@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/has-cors/-/has-cors-1.1.0.tgz#5e474793f7ea9843d1bb99c23eef49ff126fff39" - integrity sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk= +has-bigints@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113" + integrity sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA== has-flag@^3.0.0: version "3.0.0" @@ -5344,43 +2086,19 @@ has-flag@^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, 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-value@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" - integrity sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8= - 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" - integrity sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc= - 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" - integrity sha1-bWHeldkd/Km5oCCJrThL/49it3E= +has-symbols@^1.0.1, has-symbols@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423" + integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw== -has-values@^1.0.0: +has-tostringtag@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" - integrity sha1-lbC2P+whRmGab+V/51Yo1aOe/k8= + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25" + integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ== dependencies: - is-number "^3.0.0" - kind-of "^4.0.0" + has-symbols "^1.0.2" -has@^1.0.0, has@^1.0.3: +has@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== @@ -5404,29 +2122,12 @@ hash.js@^1.0.0, hash.js@^1.0.3: inherits "^2.0.3" minimalistic-assert "^1.0.1" -he@^1.2.0: +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: +hmac-drbg@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" integrity sha1-0nRXAQJabHdabFRXk+1QL8DGSaE= @@ -5435,7 +2136,7 @@ hmac-drbg@^1.0.0: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" -hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.2: +hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2: 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== @@ -5443,116 +2144,11 @@ hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.2: react-is "^16.7.0" hosted-git-info@^2.1.4: - version "2.8.8" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488" - integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg== - -hpack.js@^2.1.6: - version "2.1.6" - resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2" - integrity sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI= - 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" - integrity sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ== - -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== + version "2.8.9" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" + integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== -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.10.1" - resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f" - integrity sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ== - dependencies: - domelementtype "^1.3.1" - domhandler "^2.3.0" - domutils "^1.5.1" - entities "^1.1.1" - inherits "^2.0.1" - readable-stream "^3.1.1" - -http-deceiver@^1.2.7: - version "1.2.7" - resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" - integrity sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc= - -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" - integrity sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0= - dependencies: - depd "~1.1.2" - inherits "2.0.3" - setprototypeof "1.1.0" - statuses ">= 1.4.0 < 2" - -http-errors@~1.7.2: +http-errors@1.7.3: version "1.7.3" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06" integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw== @@ -5563,40 +2159,7 @@ http-errors@~1.7.2: statuses ">= 1.5.0 < 2" toidentifier "1.0.0" -http-parser-js@>=0.5.1: - version "0.5.2" - resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.2.tgz#da2e31d237b393aae72ace43882dd7e270a8ff77" - integrity sha512-opCO9ASqg5Wy2FNo7A0sxy71yGbbkJJXLdgMK04Tcypw9jr2MgWbyubb0+WdmDmGnFflO7fRbqbaihh/ENDlRQ== - -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" - integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE= - dependencies: - assert-plus "^1.0.0" - jsprim "^1.2.2" - sshpk "^1.7.0" - -https-browserify@^1.0.0: +https-browserify@1.0.0, https-browserify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM= @@ -5606,106 +2169,71 @@ human-signals@^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== +husky@^4.3.8: + version "4.3.8" + resolved "https://registry.yarnpkg.com/husky/-/husky-4.3.8.tgz#31144060be963fd6850e5cc8f019a1dfe194296d" + integrity sha512-LCqqsB0PzJQ/AlCgfrfzRe3e3+NvmefAdKQhRYpxS4u6clblBoDdzzvHi8fmxKRzvMxPY/1WZWzomPZww0Anow== dependencies: chalk "^4.0.0" ci-info "^2.0.0" compare-versions "^3.6.0" - cosmiconfig "^6.0.0" - find-versions "^3.2.0" + cosmiconfig "^7.0.0" + find-versions "^4.0.0" opencollective-postinstall "^2.0.2" - pkg-dir "^4.2.0" + pkg-dir "^5.0.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.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== 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= +iconv-lite@^0.6.2: + version "0.6.3" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== dependencies: - harmony-reflect "^1.4.6" + safer-buffer ">= 2.1.2 < 3.0.0" ieee754@^1.1.4: - version "1.1.13" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" - integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== - -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@^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== + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== 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== +ignore@^5.1.4: + version "5.1.8" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" + integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== -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= +image-size@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/image-size/-/image-size-1.0.0.tgz#58b31fe4743b1cec0a0ac26f5c914d3c5b2f0750" + integrity sha512-JLJ6OwBfO1KcA+TvJT+v8gbE6iWbj24LyDNFgFEN0lzegn6cC6a/p3NIDaepMsJjQjlUWqIC7wJv8lBFxPNjcw== dependencies: - import-from "^2.1.0" + queue "6.0.2" -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" +immer@^9.0.6: + version "9.0.6" + resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.6.tgz#7a96bf2674d06c8143e327cbf73539388ddf1a73" + integrity sha512-G95ivKpy+EvVAnAab4fVa4YGYn24J1SpEktnJX7JJ45Bd7xqME/SCplFzYFmTbrkwZbQ4xJK1xMTUYBkN6pWsQ== -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== +import-fresh@^3.0.0, import-fresh@^3.2.1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== 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" @@ -5716,21 +2244,6 @@ indent-string@^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" - integrity sha1-8w9xbI4r00bHtn0985FVZqfAVgc= - -indexof@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d" - integrity sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10= - -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" @@ -5739,7 +2252,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3: +inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3, 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== @@ -5754,138 +2267,39 @@ inherits@2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= -ini@^1.3.5: - version "1.3.5" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" - integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== - -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.2.0" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.2.0.tgz#63ce99d823090de7eb420e4bb05e6f3449aa389a" - integrity sha512-E0c4rPwr9ByePfNlTIB8z51kK1s2n6jrHuJeEHENl/sbq2G/S1auvibgEwNR4uSyiU+PiYHqSwsgGiXjG8p5ZQ== - 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== +internal-slot@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c" + integrity sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA== dependencies: - es-abstract "^1.17.0-next.1" + get-intrinsic "^1.1.0" 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" - integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== - dependencies: - loose-envify "^1.0.0" - -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" - integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo= - -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== + side-channel "^1.0.4" -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" - integrity sha1-UFMN+4T8yap9vnhS6Do3uTufKqY= - -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" - integrity sha1-qeEss66Nh2cn7u84Q/igiXtcmNY= - 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" - integrity sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ== - dependencies: - kind-of "^6.0.0" +internmap@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/internmap/-/internmap-1.0.1.tgz#0017cc8a3b99605f0302f2b198d272e015e5df95" + integrity sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw== is-arguments@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.0.4.tgz#3faf966c7cba0ff437fb31f6250082fcf0448cf3" - integrity sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA== + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b" + integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.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" integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= -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" - integrity sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg= +is-bigint@^1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" + integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== dependencies: - binary-extensions "^1.0.0" + has-bigints "^1.0.1" is-binary-path@~2.1.0: version "2.1.0" @@ -5894,127 +2308,49 @@ is-binary-path@~2.1.0: 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" - integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== - -is-callable@^1.1.4, is-callable@^1.2.0: - 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= +is-boolean-object@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" + integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== 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" + call-bind "^1.0.2" + has-tostringtag "^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" - integrity sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y= - dependencies: - kind-of "^3.0.2" +is-callable@^1.1.4, is-callable@^1.2.3: + version "1.2.4" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.4.tgz#47301d58dd0259407865547853df6d61fe471945" + integrity sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w== -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" - integrity sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ== +is-core-module@^2.2.0, is-core-module@^2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.6.0.tgz#d7553b2526fe59b92ba3e40c8df757ec8a709e19" + integrity sha512-wShG8vs60jKfPWpF2KZRaAtvt3a20OAn7+IJ6hLPECpSABLcKtFKTTI4ZtH5QcBruBHlq+WsdHWyz0BCZW7svQ== dependencies: - kind-of "^6.0.0" + has "^1.0.3" is-date-object@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" - integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== - -is-descriptor@^0.1.0: - version "0.1.6" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" - integrity sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg== - 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" - integrity sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg== - 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" - integrity sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE= - -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" - integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik= - -is-extendable@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" - integrity sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA== + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" + integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== dependencies: - is-plain-object "^2.0.4" + has-tostringtag "^1.0.0" -is-extglob@^2.1.0, is-extglob@^2.1.1: +is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= -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" - integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs= - 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" - integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= - 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" - integrity sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo= +is-generator-function@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.10.tgz#f1558baf1ac17e0deea7c0415c438351ff2b3c72" + integrity sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A== dependencies: - is-extglob "^2.1.0" + has-tostringtag "^1.0.0" is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: version "4.0.1" @@ -6023,12 +2359,25 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: 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" - integrity sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU= +is-nan@^1.2.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/is-nan/-/is-nan-1.3.2.tgz#043a54adea31748b55b6cd4e09aadafa69bd9e1d" + integrity sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w== dependencies: - kind-of "^3.0.2" + call-bind "^1.0.0" + define-properties "^1.1.3" + +is-negative-zero@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24" + integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w== + +is-number-object@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.6.tgz#6a7aaf838c7f0686a50b4553f7e54a96494e89f0" + integrity sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g== + dependencies: + has-tostringtag "^1.0.0" is-number@^7.0.0: version "7.0.0" @@ -6040,723 +2389,121 @@ is-obj@^1.0.1: resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" integrity sha1-PkcprB9f3gJc19g6iW2rn09n2w8= -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" - integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4= - -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" - integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== - dependencies: - isobject "^3.0.1" - -is-regex@^1.0.4, is-regex@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.0.tgz#ece38e389e490df0dc21caea2bd596f987f767ff" - integrity sha512-iI97M8KTWID2la5uYXlkbSDQIg4F6o1sYboZKKTDpnDQMLtUL86zxhgDet3Q2SriaYsyGqZ6Mn2SjbRKeLHdqw== +is-regex@^1.1.3: + version "1.1.4" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" + integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== dependencies: - has-symbols "^1.0.1" + call-bind "^1.0.2" + has-tostringtag "^1.0.0" is-regexp@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-1.0.0.tgz#fd2d883545c46bac5a633e7b9a09e87fa2cb5069" integrity sha1-/S2INUXEa6xaYz57mgnof6LLUGk= -is-resolvable@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88" - integrity sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg== - -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" - integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= - 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== + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== -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== +is-string@^1.0.5, is-string@^1.0.6: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" + integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== dependencies: - html-comment-regex "^1.1.0" + has-tostringtag "^1.0.0" -is-symbol@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" - integrity sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ== +is-symbol@^1.0.2, is-symbol@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" + integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== dependencies: - has-symbols "^1.0.1" - -is-typedarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" - integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= - -is-windows@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" - integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== - -is-wsl@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" - integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0= + has-symbols "^1.0.2" -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== +is-typed-array@^1.1.3, is-typed-array@^1.1.7: + version "1.1.8" + resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.8.tgz#cbaa6585dc7db43318bc5b89523ea384a6f65e79" + integrity sha512-HqH41TNZq2fgtGT8WHVFVJhBVGuY3AnP3Q36K8JKXUxSxRgk/d+7NjmwG2vo2mYmXK8UYZKu0qH8bVP5gEisjA== dependencies: - is-docker "^2.0.0" + available-typed-arrays "^1.0.5" + call-bind "^1.0.2" + es-abstract "^1.18.5" + foreach "^2.0.5" + has-tostringtag "^1.0.0" -isarray@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" - integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= +is-unicode-supported@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" + integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== -isarray@1.0.0, isarray@^1.0.0, 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" integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= -isarray@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.1.tgz#a37d94ed9cda2d59865c9f76fe596ee1f338741e" - integrity sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4= - isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= -isobject@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" - integrity sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk= - 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" - integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= - -isstream@~0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" - integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= - -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" - javascript-natural-sort@^0.7.1: version "0.7.1" resolved "https://registry.yarnpkg.com/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz#f9e2303d4507f6d74355a73664d1440fb5a0ef59" integrity sha1-+eIwPUUH9tdDVac2ZNFED7Wg71k= -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.2" - resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz#b704ac0ae028a89108a4d040b3f919dfddc8e33c" - integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w== - -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== +jest-worker@27.0.0-next.5: + version "27.0.0-next.5" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.0.0-next.5.tgz#5985ee29b12a4e191f4aae4bb73b97971d86ec28" + integrity sha512-mk0umAQ5lT+CaOJ+Qp01N6kz48sJG2kr2n1rX0koqKf6FIygQV0qLOdN9SCYID4IVeSigDOcPeGLozdMLYfb5g== dependencies: + "@types/node" "*" merge-stream "^2.0.0" - supports-color "^7.0.0" + supports-color "^8.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-sha3@0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840" + integrity sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q== "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" - integrity sha1-mGbfOVECEw449/mWvOtlRDIJwls= - 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== + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== 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" - integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= - -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" - integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0= - -json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2: +json-parse-better-errors@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== +json-parse-even-better-errors@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + 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" integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== -json-schema@0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" - integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= +json-schema-traverse@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" + integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== 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" - integrity sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8= - 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" - integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= - -json3@^3.3.2: - version "3.3.3" - resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.3.tgz#7fc10e375fc5ae42c4705a5cc0aa6f62be305b81" - integrity sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA== - json5@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" @@ -6764,163 +2511,58 @@ json5@^1.0.1: 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" - integrity sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM= - -jsprim@^1.2.2: - version "1.4.1" - resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" - integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= - 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.4.1" - resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-2.4.1.tgz#1114a4c1209481db06c690c2b4f488cc665f657e" - integrity sha512-z1xSldJ6imESSzOjd3NNkieVJKRlKYSOtMG8SFyCj2FIrvSaSuli/WjpBkEzCBoR9bYYYFgqJw61Xhu7Lcgk+w== - 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" - integrity sha512-nGyg7N9FEZsyrGQNilkyVLxKPsf96iel5v0DrozQ19ML+96HntyS/53bOP68iK/kZUGvsL3FKygV8nQYYhgTFw== - -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" - integrity sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ= - 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" - integrity sha1-IIE989cSkosgc3hpGkUGb65y3Vc= - 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" - integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== - -kind-of@^6.0.0, kind-of@^6.0.2: - version "6.0.3" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" - integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== - -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" - integrity sha1-odePw6UEdMuAhF07O24dpJpEbo4= - -lcid@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/lcid/-/lcid-2.0.0.tgz#6ef5d2df60e52f82eb228a4c373e8d1f397253cf" - integrity sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA== +"jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.1.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.2.0.tgz#41108d2cec408c3453c1bbe8a4aae9e1e2bd8f82" + integrity sha512-EIsmt3O3ljsU6sot/J4E1zDRxfBNrhjyf/OKjlydwgEimQuznlM4Wv7U+ueONJMyEn1WRE0K8dhi3dVAXYT24Q== dependencies: - invert-kv "^2.0.0" + array-includes "^3.1.2" + object.assign "^4.1.2" -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== +konva@^7.2.5: + version "7.2.5" + resolved "https://registry.yarnpkg.com/konva/-/konva-7.2.5.tgz#9b4ac3a353e6be66e3e69123bf2a0cbc61efeb26" + integrity sha512-yk/li8rUF+09QNlOdkwbEId+QvfATMe/aMGVouWW1oFoUVTYWHsQuIAE6lWy11DK8mLJEJijkNAXC5K+NVlMew== -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== +language-subtag-registry@~0.3.2: + version "0.3.21" + resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.21.tgz#04ac218bea46f04cb039084602c6da9e788dd45a" + integrity sha512-L0IqwlIXjilBVVYKFT37X9Ih11Um5NEl9cbJIuU/SwP/zEEAbBPOnEeeuxVMf45ydWQRDQN3Nqc96OgbH1K+Pg== -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== +language-tags@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/language-tags/-/language-tags-1.0.5.tgz#d321dbc4da30ba8bf3024e040fa5c14661f9193a" + integrity sha1-0yHbxNowuovzAk4ED6XBRmH5GTo= dependencies: - leven "^3.1.0" + language-subtag-registry "~0.3.2" -levn@^0.3.0, levn@~0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" - integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== dependencies: - prelude-ls "~1.1.2" - type-check "~0.3.2" + prelude-ls "^1.2.1" + type-check "~0.4.0" 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.11" - resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-10.2.11.tgz#713c80877f2dc8b609b05bc59020234e766c9720" - integrity sha512-LRRrSogzbixYaZItE2APaS4l2eJMjjf5MbclRZpLJtcQJShcvUzKXsNeZgsLIZ0H0+fg2tL4B59fU9wHIHtFIA== +lint-staged@^10.5.4: + version "10.5.4" + resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-10.5.4.tgz#cd153b5f0987d2371fc1d2847a409a2fe705b665" + integrity sha512-EechC3DdFic/TdOPgj/RB3FicqE6932LTHCUm0Y2fsD9KGlLB+RwJl2q1IYBIvEsKzDOgn0D4gll+YxG5RsrKg== dependencies: - chalk "^4.0.0" - cli-truncate "2.1.0" - commander "^5.1.0" - cosmiconfig "^6.0.0" - debug "^4.1.1" + chalk "^4.1.0" + cli-truncate "^2.1.0" + commander "^6.2.0" + cosmiconfig "^7.0.0" + debug "^4.2.0" dedent "^0.7.0" - enquirer "^2.3.5" - execa "^4.0.1" - listr2 "^2.1.0" + enquirer "^2.3.6" + execa "^4.1.0" + listr2 "^3.2.2" log-symbols "^4.0.0" micromatch "^4.0.2" normalize-path "^3.0.0" @@ -6928,29 +2570,18 @@ lint-staged@~10.2.2: string-argv "0.3.1" stringify-object "^3.3.0" -listr2@^2.1.0: - version "2.1.8" - resolved "https://registry.yarnpkg.com/listr2/-/listr2-2.1.8.tgz#8af7ebc70cdbe866ddbb6c80909142bd45758f1f" - integrity sha512-Op+hheiChfAphkJ5qUxZtHgyjlX9iNnAeFS/S134xw7mVSg0YVrQo1IY4/K+ElY6XgOPg2Ij4z07urUXR+YEew== +listr2@^3.2.2: + version "3.11.1" + resolved "https://registry.yarnpkg.com/listr2/-/listr2-3.11.1.tgz#a9bab5cd5874fd3cb7827118dbea6fedefbcb43f" + integrity sha512-ZXQvQfmH9iWLlb4n3hh31yicXDxlzB0pE7MM1zu6kgbVL4ivEsO4H8IPh4E682sC8RjnYO9anose+zT52rrpyg== dependencies: - chalk "^4.0.0" cli-truncate "^2.1.0" - figures "^3.2.0" - indent-string "^4.0.0" + colorette "^1.2.2" log-update "^4.0.0" p-map "^4.0.0" - rxjs "^6.5.5" + rxjs "^6.6.7" through "^2.3.8" - -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" - integrity sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg= - dependencies: - graceful-fs "^4.1.2" - parse-json "^2.2.0" - pify "^2.0.0" - strip-bom "^3.0.0" + wrap-ansi "^7.0.0" load-json-file@^4.0.0: version "4.0.0" @@ -6962,19 +2593,6 @@ load-json-file@^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: version "1.2.3" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7" @@ -6984,15 +2602,6 @@ loader-utils@1.2.3: emojis-list "^2.0.0" json5 "^1.0.1" -loader-utils@^1.1.0, 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" @@ -7001,14 +2610,6 @@ locate-path@^2.0.0: 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" @@ -7016,67 +2617,55 @@ locate-path@^5.0.0: dependencies: p-locate "^4.1.0" -lodash._reinterpolate@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" - integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0= +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +lodash.clonedeep@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" + integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8= lodash.debounce@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= -lodash.memoize@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" - integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== 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, 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.2.0" - resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz#e481310f049d3cf6d47e912ad09313b154f0fb33" - integrity sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ== - dependencies: - lodash._reinterpolate "^3.0.0" - lodash.throttle@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4" integrity sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ= -lodash.uniq@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" - integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= - -"lodash@>=3.5 <5", 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== +lodash.truncate@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" + integrity sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM= -lodash@~4.17.4: - version "4.17.19" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b" - integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ== +lodash@>=4.17.21, lodash@^4.17.19: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== 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== + version "4.1.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" + integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== dependencies: - chalk "^4.0.0" + chalk "^4.1.0" + is-unicode-supported "^0.1.0" log-update@^4.0.0: version "4.0.0" @@ -7088,39 +2677,19 @@ log-update@^4.0.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: +loose-envify@^1.0.0, loose-envify@^1.1.0, 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" -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@^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== +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== dependencies: - pify "^4.0.1" - semver "^5.6.0" + yallist "^4.0.0" make-dir@^3.0.2: version "3.1.0" @@ -7129,49 +2698,21 @@ make-dir@^3.0.2: 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" - integrity sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw= - 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" - integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= - -map-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" - integrity sha1-7Nyo8TFE5mDxtb1B8S80edmN+48= +match-sorter@^6.0.2: + version "6.3.0" + resolved "https://registry.yarnpkg.com/match-sorter/-/match-sorter-6.3.0.tgz#454a1b31ed218cddbce6231a0ecb5fdc549fed01" + integrity sha512-efYOf/wUpNb8FgNY+cOD2EIJI1S5I7YPKsw0LBp7wqPh5pmMS6i/wr3ZWwfwrAw1NvqTA2KUReVRWDX84lUcOQ== dependencies: - object-visit "^1.0.0" + "@babel/runtime" "^7.12.5" + remove-accents "0.4.2" -math-expression-evaluator@^1.2.14: - version "1.2.22" - resolved "https://registry.yarnpkg.com/math-expression-evaluator/-/math-expression-evaluator-1.2.22.tgz#c14dcb3d8b4d150e5dcea9c68c8dad80309b0d5e" - integrity sha512-L0j0tFVZBQQLeEjmWOvDLoRciIY8gQGWahvkztXUal8jH8R5Rlqo9GCvgqvXcy9LQhEWdQCVvzqAbxgYNt4blQ== - -mathjs@~7.1.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/mathjs/-/mathjs-7.1.0.tgz#83226e336b8b258b046a139865373e667db94afb" - integrity sha512-Km6PO2UR+COs5mru5auKQKi84GKBryuL5JDdKeAxAi0QV8mH/qwpZKLnzrycxBacQ/X/4Z4Kn+gtYc5gEeWsDQ== +mathjs@^7.6.0: + version "7.6.0" + resolved "https://registry.yarnpkg.com/mathjs/-/mathjs-7.6.0.tgz#f0b7579e0756b13422995d0c4f29bd17d65d4dcc" + integrity sha512-abywR28hUpKF4at5jE8Ys+Kigk40eKMT5mcBLD0/dtsqjfOLbtzd3WjlRqIopNo7oQ6FME51qph6lb8h/bhpUg== dependencies: complex.js "^2.0.11" - decimal.js "^10.2.0" + decimal.js "^10.2.1" escape-latex "^1.2.0" fraction.js "^4.0.12" javascript-natural-sort "^0.7.1" @@ -7188,106 +2729,28 @@ md5.js@^1.3.4: 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" - integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= - -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" - -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" - integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= - 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: +merge2@^1.3.0: 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" - integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= - -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" - integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== - 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== +micromatch@^4.0.2, micromatch@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" + integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg== dependencies: braces "^3.0.1" - picomatch "^2.0.5" + picomatch "^2.2.3" + +microseconds@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/microseconds/-/microseconds-0.2.0.tgz#233b25f50c62a65d861f978a4a4f8ec18797dc39" + integrity sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA== miller-rabin@^4.0.0: version "4.0.1" @@ -7297,249 +2760,152 @@ miller-rabin@^4.0.0: 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-types@^2.1.12, mime-types@~2.1.17, mime-types@~2.1.19, 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: +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.3" - resolved "https://registry.yarnpkg.com/mini-create-react-context/-/mini-create-react-context-0.3.3.tgz#b1b2bc6604d3a6c5d9752bad7692615410ebb38e" - integrity sha512-TtF6hZE59SGmS4U8529qB+jJFeW6asTLDIpPgvPLSCsooAwJS7QprHIFTqv9/Qh3NdLwQxFYgiHX5lqb6jqzPA== - dependencies: - "@babel/runtime" "^7.12.1" - tiny-warning "^1.0.3" - -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" integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== -minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: +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" integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= -minimatch@3.0.4, minimatch@^3.0.4: +minimatch@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== dependencies: brace-expansion "^1.1.7" -minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.5: +minimist@^1.2.0: 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@^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" - -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.2" - resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" - integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA== - 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.1, mkdirp@^0.5.3, mkdirp@~0.5.1: - 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" integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= -ms@2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" - integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== - -ms@^2.1.1: +ms@2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -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" - integrity sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE= - -multicast-dns@^6.0.1: - version "6.2.3" - resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-6.2.3.tgz#a0ec7bd9055c4282f790c3c82f4e28db3b31b229" - integrity sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g== - 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.12.1: - version "2.14.1" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01" - integrity sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw== - -nanomatch@^1.2.9: - version "1.2.13" - resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" - integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA== - 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" +ms@^2.1.1: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +nano-time@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/nano-time/-/nano-time-1.0.0.tgz#b0554f69ad89e22d0907f7a12b0993a5d96137ef" + integrity sha1-sFVPaa2J4i0JB/ehKwmTpdlhN+8= + dependencies: + big-integer "^1.6.16" + +nanoid@^3.1.23: + version "3.1.25" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.25.tgz#09ca32747c0e543f0e1814b7d3793477f9c8e152" + integrity sha512-rdwtIXaXCLFAQbnfqDRnI6jaRHp9fTcYBjtFKE8eezcZ7LuLjhUaQGNeMXf1HmRoCH32CLz6XwX0TtxEOS/A3Q== + +native-url@0.3.4: + version "0.3.4" + resolved "https://registry.yarnpkg.com/native-url/-/native-url-0.3.4.tgz#29c943172aed86c63cee62c8c04db7f5756661f8" + integrity sha512-6iM8R99ze45ivyH8vybJ7X0yekIcPf5GgLV5K0ENCbmRcaRIDoj37BC8iLEmaaBfqqb8enuZ5p0uhY+lVAbAcA== + dependencies: + querystring "^0.2.0" natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= -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== +next-transpile-modules@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/next-transpile-modules/-/next-transpile-modules-8.0.0.tgz#56375cdc25ae5d23a834195f277fc2737b26cb97" + integrity sha512-Q2f2yB0zMJ8KJbIYAeZoIxG6cSfVk813zr6B5HzsLMBVcJ3FaF8lKr7WG66n0KlHCwjLSmf/6EkgI6QQVWHrDw== + dependencies: + enhanced-resolve "^5.7.0" + escalade "^3.1.1" + +next@^11.1.2: + version "11.1.2" + resolved "https://registry.yarnpkg.com/next/-/next-11.1.2.tgz#527475787a9a362f1bc916962b0c0655cc05bc91" + integrity sha512-azEYL0L+wFjv8lstLru3bgvrzPvK0P7/bz6B/4EJ9sYkXeW8r5Bjh78D/Ol7VOg0EIPz0CXoe72hzAlSAXo9hw== + dependencies: + "@babel/runtime" "7.15.3" + "@hapi/accept" "5.0.2" + "@next/env" "11.1.2" + "@next/polyfill-module" "11.1.2" + "@next/react-dev-overlay" "11.1.2" + "@next/react-refresh-utils" "11.1.2" + "@node-rs/helper" "1.2.1" + assert "2.0.0" + ast-types "0.13.2" + browserify-zlib "0.2.0" + browserslist "4.16.6" + buffer "5.6.0" + caniuse-lite "^1.0.30001228" + chalk "2.4.2" + chokidar "3.5.1" + constants-browserify "1.0.0" + crypto-browserify "3.12.0" + cssnano-simple "3.0.0" + domain-browser "4.19.0" + encoding "0.1.13" + etag "1.8.1" + find-cache-dir "3.3.1" + get-orientation "1.1.2" + https-browserify "1.0.0" + image-size "1.0.0" + jest-worker "27.0.0-next.5" + native-url "0.3.4" + node-fetch "2.6.1" + node-html-parser "1.4.9" + node-libs-browser "^2.2.1" + os-browserify "0.3.0" + p-limit "3.1.0" + path-browserify "1.0.1" + pnp-webpack-plugin "1.6.4" + postcss "8.2.15" + process "0.11.10" + querystring-es3 "0.2.1" + raw-body "2.4.1" + react-is "17.0.2" + react-refresh "0.8.3" + stream-browserify "3.0.0" + stream-http "3.1.1" + string_decoder "1.3.0" + styled-jsx "4.0.1" + timers-browserify "2.0.12" + tty-browserify "0.0.1" + use-subscription "1.5.1" + util "0.12.4" + vm-browserify "1.1.2" + watchpack "2.1.1" + optionalDependencies: + "@next/swc-darwin-arm64" "11.1.2" + "@next/swc-darwin-x64" "11.1.2" + "@next/swc-linux-x64-gnu" "11.1.2" + "@next/swc-win32-x64-msvc" "11.1.2" -neo-async@^2.5.0, neo-async@^2.6.1: +node-fetch@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.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" - integrity sha1-yobR/ogoFpsBICCOPchCS524NCw= - -nice-try@^1.0.4: - version "1.0.5" - resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" - integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" + integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== -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== +node-html-parser@1.4.9: + version "1.4.9" + resolved "https://registry.yarnpkg.com/node-html-parser/-/node-html-parser-1.4.9.tgz#3c8f6cac46479fae5800725edb532e9ae8fd816c" + integrity sha512-UVcirFD1Bn0O+TSmloHeHqZZCxHjvtIeGdVdGMhyZ8/PWlEiZaZ5iJzR189yKZr8p0FXN58BUeC7RHRkf/KYGw== 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-int64@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" - integrity sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs= + he "1.2.0" node-libs-browser@^2.2.1: version "2.2.1" @@ -7570,26 +2936,10 @@ node-libs-browser@^2.2.1: 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-releases@^1.1.52, node-releases@^1.1.58: - 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-releases@^1.1.71: + version "1.1.75" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.75.tgz#6dd8c876b9897a1b8e5a02de26afa79bb54ebbfe" + integrity sha512-Qe5OUajvqrqDSy6wrWFmMwfJ0jVgwiw4T3KqmbTcZ62qW0gQkheXYhcFM1+lOVcGUoRxcEcfyvFMAnDgaF1VWw== normalize-package-data@^2.3.2: version "2.5.0" @@ -7601,44 +2951,15 @@ normalize-package-data@^2.3.2: 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" - integrity sha1-GrKLVW4Zg2Oowab35vogE3/mrtk= - 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" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== -normalize-range@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" - integrity sha1-LRDAa9/TEuqXd2laTShDlFa3WUI= - -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-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" - integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8= - dependencies: - path-key "^2.0.0" +normalizr@^3.6.1: + version "3.6.1" + resolved "https://registry.yarnpkg.com/normalizr/-/normalizr-3.6.1.tgz#d367ab840e031ff382141b8d81ce279292ff69fe" + integrity sha512-8iEmqXmPtll8PwbEFrbPoDxVw7MKnNvt3PZzR2Xvq9nggEEOgBlNICPXYzyZ4w4AkHUzCU998mdatER3n2VaMA== npm-run-path@^4.0.0: version "4.0.1" @@ -7647,157 +2968,71 @@ npm-run-path@^4.0.0: dependencies: path-key "^3.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" - integrity sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg== - 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" - integrity sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4= - -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" - integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= - -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" - integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== - -object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: +object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= -object-component@0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/object-component/-/object-component-0.0.3.tgz#f0c69aa50efc95b866c186f400a33769cb2f1291" - integrity sha1-8MaapQ78lbhmwYb0AKM3acsvEpE= - -object-copy@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" - integrity sha1-fn2Fi3gb18mRpBupde04EnVOmYw= - 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.8.0" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.8.0.tgz#df807e5ecf53a609cc6bfe93eac3cc7be5b3a9d0" - integrity sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA== +object-inspect@^1.11.0, object-inspect@^1.9.0: + version "1.11.0" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.11.0.tgz#9dceb146cedd4148a0d9e51ab88d34cf509922b1" + integrity sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg== object-is@^1.0.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.2.tgz#c5d2e87ff9e119f78b7a088441519e2eec1573b6" - integrity sha512-5lHCz+0uufF6wZ7CRFWJN3hp8Jqblpgve06U5CMQ3f//6iDjPr2PEo9MWCjEssDsa+UZEL4PkFpr+BMop6aKzQ== + version "1.1.5" + resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.5.tgz#b9deeaa5fc7f1846a0faecdceec138e5778f53ac" + integrity sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw== dependencies: + call-bind "^1.0.2" define-properties "^1.1.3" - es-abstract "^1.17.5" -object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1: +object-keys@^1.0.12, 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-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" - integrity sha1-95xEk68MU3e1n+OdOV5BBC3QRbs= - 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== +object.assign@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" + integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ== dependencies: + call-bind "^1.0.0" define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" - function-bind "^1.1.1" - has "^1.0.3" + has-symbols "^1.0.1" + object-keys "^1.1.1" -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== +object.entries@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.4.tgz#43ccf9a50bc5fd5b649d45ab1a579f24e088cafd" + integrity sha512-h4LWKWE+wKQGhtMjZEBud7uLGhqyLwj8fpHOarZhD2uY3C9cRtk57VQ89ke3moByLXMedqs3XCHzyb4AmA2DjA== dependencies: + call-bind "^1.0.2" define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" + es-abstract "^1.18.2" -object.pick@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" - integrity sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c= - 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== +object.fromentries@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.4.tgz#26e1ba5c4571c5c6f0890cef4473066456a120b8" + integrity sha512-EsFBshs5RUUpQEY1D4q/m59kMfz4YJvxuNCJcv/jWwOJr34EaVnG11ZrZa0UHB3wnzV1wx8m58T4hQL8IuNXlQ== dependencies: + call-bind "^1.0.2" define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" - function-bind "^1.1.1" + es-abstract "^1.18.0-next.2" 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" - integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== - -on-finished@~2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" - integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= +object.values@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.4.tgz#0d273762833e816b693a637d30073e7051535b30" + integrity sha512-TnGo7j4XSnKQoK3MfvkzqKCi0nVe/D9I9IjwTNYdb/fxYHpjrluHVOgw0AF6jrRFGMPHdfuidR09tIDiIvnaSg== dependencies: - ee-first "1.1.1" + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.18.2" -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== +oblivious-set@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/oblivious-set/-/oblivious-set-1.0.0.tgz#c8316f2c2fb6ff7b11b6158db3234c49f733c566" + integrity sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw== once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" @@ -7807,99 +3042,40 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0: 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== + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== 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, optionator@^0.8.3: - version "0.8.3" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" - integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== +optionator@^0.9.1: + version "0.9.1" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" + integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== 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" + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + 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: +os-browserify@0.3.0, os-browserify@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" integrity sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc= -os-locale@^3.0.0: +p-limit@3.1.0, p-limit@^3.0.2: 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== + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== dependencies: - execa "^1.0.0" - lcid "^2.0.0" - mem "^4.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" - integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= - -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" - integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= - -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== + yocto-queue "^0.1.0" p-limit@^1.1.0: version "1.3.0" @@ -7908,7 +3084,7 @@ p-limit@^1.1.0: dependencies: p-try "^1.0.0" -p-limit@^2.0.0, p-limit@^2.2.0, p-limit@^2.2.2: +p-limit@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== @@ -7922,13 +3098,6 @@ p-locate@^2.0.0: 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" @@ -7936,17 +3105,12 @@ p-locate@^4.1.0: 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== +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== dependencies: - aggregate-error "^3.0.0" + p-limit "^3.0.2" p-map@^4.0.0: version "4.0.0" @@ -7955,18 +3119,6 @@ p-map@^4.0.0: 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" @@ -7982,23 +3134,6 @@ pako@~1.0.5: resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== -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" @@ -8007,24 +3142,16 @@ parent-module@^1.0.0: callsites "^3.0.0" parse-asn1@^5.0.0, parse-asn1@^5.1.5: - version "5.1.5" - resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.5.tgz#003271343da58dc94cace494faef3d2147ecea0e" - integrity sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ== + version "5.1.6" + resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.6.tgz#385080a3ec13cb62a62d39409cb3e88844cdaed4" + integrity sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw== dependencies: - asn1.js "^4.0.0" + asn1.js "^5.2.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" - integrity sha1-9ID0BDTvgHQfhGkJn43qGPVaTck= - 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" @@ -8034,73 +3161,24 @@ parse-json@^4.0.0: 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== + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== dependencies: "@babel/code-frame" "^7.0.0" error-ex "^1.3.1" - json-parse-better-errors "^1.0.1" + json-parse-even-better-errors "^2.3.0" 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" - integrity sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0= - 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" - integrity sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo= - dependencies: - better-assert "~1.0.0" - -parseurl@~1.3.2, 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" - integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= - 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" - integrity sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA= - -path-exists@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b" - integrity sha1-D+tsZPD8UY2adU3V77YscCJ2H0s= - dependencies: - pinkie-promise "^2.0.0" +path-browserify@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-1.0.1.tgz#d98454a9c3753d5790860f16f68867b9e46be1fd" + integrity sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g== path-exists@^3.0.0: version "3.0.0" @@ -8117,44 +3195,15 @@ path-is-absolute@^1.0.0: resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= -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" - integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM= - -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" - integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= - 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" - integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== - -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" - integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= - -path-to-regexp@^1.7.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a" - integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA== - dependencies: - isarray "0.0.1" - -path-type@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73" - integrity sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM= - dependencies: - pify "^2.0.0" + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== path-type@^3.0.0: version "3.0.0" @@ -8169,9 +3218,9 @@ path-type@^4.0.0: integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== pbkdf2@^3.0.3: - version "3.1.1" - resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.1.tgz#cb8724b0fada984596856d1a6ebafd3584654b94" - integrity sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg== + version "3.1.2" + resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.2.tgz#dd822aa0887580e52f1a039dc3eda108efae3075" + integrity sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA== dependencies: create-hash "^1.1.2" create-hmac "^1.1.4" @@ -8184,52 +3233,16 @@ performance-now@^2.1.0: resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= -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== - -pify@^2.0.0: +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3: version "2.3.0" - resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" - integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw= + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" + integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw== pify@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= -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" - integrity sha1-ITXW36ejWMBprJsXh3YogihFD/o= - 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" - integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA= - -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" - integrity sha1-ektQio1bstYp1EcFb/TpyTFM89Q= - 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" @@ -8237,31 +3250,31 @@ pkg-dir@^2.0.0: 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: +pkg-dir@^4.1.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== +pkg-dir@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-5.0.0.tgz#a02d6aebe6ba133a928f74aec20bafdfe6b8e760" + integrity sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA== dependencies: - find-up "^3.0.0" + find-up "^5.0.0" -platform@^1.3.0: - version "1.3.5" - resolved "https://registry.yarnpkg.com/platform/-/platform-1.3.5.tgz#fb6958c696e07e2918d2eeda0f0bc9448d733444" - integrity sha512-TuvHS8AOIZNAlE77WUDiR4rySV/VMptyMfcfeoMgs4P8apaZM3JrnbzBiixKUv+XR6i+BXrQh8WAnjaSPFO65Q== +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.6: + version "1.3.6" + resolved "https://registry.yarnpkg.com/platform/-/platform-1.3.6.tgz#48b4ce983164b209c2d45a107adb31f473a6e7a7" + integrity sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg== please-upgrade-node@^3.2.0: version "3.2.0" @@ -8270,11 +3283,6 @@ please-upgrade-node@^3.2.0: 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" @@ -8282,735 +3290,41 @@ pnp-webpack-plugin@1.6.4: dependencies: ts-pnp "^1.1.6" -popper.js@^1.14.4: +popper.js@^1.16.0: version "1.16.1" resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.16.1.tgz#2a223cb3dc7b6213d740e40372be40de43e65b1b" integrity sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ== -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" - integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= - -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: +postcss-value-parser@^3.3.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== +postcss@8.2.15: + version "8.2.15" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.2.15.tgz#9e66ccf07292817d226fc315cbbf9bc148fbca65" + integrity sha512-2zO3b26eJD/8rb106Qu2o7Qgg52ND5HPjcyQiK2B98O388h43A448LCslC0dI2P97wCAQRJsFvwTRcXxTKds+Q== 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" + colorette "^1.2.2" + nanoid "^3.1.23" 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.32, 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" - integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= - -prepend-http@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" - integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw= - -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" +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== -private@^0.1.8: - version "0.1.8" - resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" - integrity sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg== +prettier@^2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.3.2.tgz#ef280a05ec253712e486233db5c6f23441e7342d" + integrity sha512-lnJzDfJ66zkMy58OL5/NY5zp70S7Nz6KqcKkXYzn2tMVrNxvbqaBpg7H3qHaLxCJ5lNMsGuM8+ohS7cZrthdLQ== process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== -process@^0.11.10: +process@0.11.10, process@^0.11.10: version "0.11.10" resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= @@ -9020,27 +3334,20 @@ progress@^2.0.0: resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== -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" +promise-polyfill@^8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/promise-polyfill/-/promise-polyfill-8.2.0.tgz#367394726da7561457aba2133c9ceefbd6267da0" + integrity sha512-k/TC0mIcPVF6yHhUvwAp7cvL6I2fFV7TzF1DuGPI8mBh4QQazf36xCKEHKTZKRysEoTQoQdKyP25J8MPJp7j5g== -prompts@^2.0.1: - version "2.3.2" - resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.3.2.tgz#480572d89ecf39566d2bd3fe2c9fccb7c4c0b068" - integrity sha512-Q06uKs2CkNYVID0VqwfAl9mipo99zkBv/n2JtWY89Yxa3ZabWSrs0e2KTudKVa3peLUvYXMefDqIleLPVUBZMA== +prop-types-extra@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/prop-types-extra/-/prop-types-extra-1.1.1.tgz#58c3b74cbfbb95d304625975aa2f0848329a010b" + integrity sha512-59+AHNnHYCdiC+vMwY52WmvP5dM3QLeoumYuEyceQDi9aEhtwN9zIQ2ZNo25sMyXnbh32h+P1ezDsUpUH3JAew== dependencies: - kleur "^3.0.3" - sisteransi "^1.0.4" + react-is "^16.3.2" + warning "^4.0.0" -prop-types@^15.5.6, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@~15.7.2: +prop-types@^15.6.1, prop-types@^15.6.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== @@ -9049,24 +3356,6 @@ prop-types@^15.5.6, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, 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" - integrity sha1-0/wRS6BplaRexok/SEzrHXj19HY= - -psl@^1.1.28: - version "1.8.0" - resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" - integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== - public-encrypt@^4.0.0: version "4.0.3" resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0" @@ -9079,14 +3368,6 @@ public-encrypt@^4.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" @@ -9095,15 +3376,6 @@ pump@^3.0.0: 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" @@ -9114,35 +3386,12 @@ punycode@^1.2.4: resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= -punycode@^2.1.0, punycode@^2.1.1: +punycode@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== -q@^1.1.2: - version "1.5.1" - resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" - integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc= - -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" - integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== - -query-string@^4.1.0: - version "4.3.4" - resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb" - integrity sha1-u7aTucqRXCMlFbIosaArYJBD2+s= - dependencies: - object-assign "^4.1.0" - strict-uri-encode "^1.0.0" - -querystring-es3@^0.2.0: +querystring-es3@0.2.1, querystring-es3@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" integrity sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM= @@ -9152,19 +3401,31 @@ querystring@0.2.0: resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= -querystringify@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.1.tgz#60e5a5fd64a7f8bfa4d2ab2ed6fdf4c85bad154e" - integrity sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA== +querystring@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.1.tgz#40d77615bb09d16902a85c3e38aa8b5ed761c2dd" + integrity sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg== + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +queue@6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/queue/-/queue-6.0.2.tgz#b91525283e2315c7553d2efa18d83e76432fed65" + integrity sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA== + dependencies: + inherits "~2.0.3" -raf@^3.4.0, raf@^3.4.1: +raf@^3.4.0: 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, randombytes@^2.1.0: +randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5: version "2.1.0" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== @@ -9179,282 +3440,125 @@ randomfill@^1.0.3: 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== +raw-body@2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.1.tgz#30ac82f98bb5ae8c152e67149dac8d55153b168c" + integrity sha512-9WmIKF6mkvA0SLmA2Knm9+qj89e+j1zqgyn8aXGd7+nAduPoqgI9lO57SAZNn/Byzo5P7JhXTyg9PzaJbH73bA== dependencies: bytes "3.1.0" - http-errors "1.7.2" + http-errors "1.7.3" iconv-lite "0.4.24" unpipe "1.0.0" -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== +react-dom@^17.0.2: + version "17.0.2" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23" + integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA== dependencies: - core-js "^3.5.0" + loose-envify "^1.1.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" + scheduler "^0.20.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== +react-dropzone@9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/react-dropzone/-/react-dropzone-9.0.0.tgz#4f5223cdcb4d3bd8a66e3298c4041eb0c75c4634" + integrity sha512-wZ2o9B2qkdE3RumWhfyZT9swgJYJPeU5qHEcMU8weYpmLex1eeWX0CC32/Y0VutB+BBi2D+iePV/YZIiB4kZGw== dependencies: - loose-envify "^1.1.0" - object-assign "^4.1.1" + attr-accept "^1.1.3" + file-selector "^0.1.8" 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== + prop-types-extra "^1.1.0" -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== +react-hotkeys@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/react-hotkeys/-/react-hotkeys-2.0.0.tgz#a7719c7340cbba888b0e9184f806a9ec0ac2c53f" + integrity sha512-3n3OU8vLX/pfcJrR3xJ1zlww6KS1kEJt0Whxc4FiGV+MJrQ1mYSYI3qS/11d2MJDFm8IhOXMTFQirfu6AVOF6Q== dependencies: - prop-types "^15.5.6" + prop-types "^15.6.1" -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.10.2: + version "16.10.2" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.10.2.tgz#984120fd4d16800e9a738208ab1fba422d23b5ab" + integrity sha512-INBT1QEgtcCCgvccr5/86CfD71fw9EPmDxgiJX4I2Ddr6ZsV6iFXsuby+qWJPtmNuMY0zByTsG4468P7nHuNWA== + +react-is@17.0.2: + version "17.0.2" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" + integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== -react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4: +react-is@^16.13.1, react-is@^16.3.2, react-is@^16.7.0, react-is@^16.8.1: 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== +react-konva@^17.0.2-5: + version "17.0.2-5" + resolved "https://registry.yarnpkg.com/react-konva/-/react-konva-17.0.2-5.tgz#e70b0acf323402de0a540f27b300fbe7ed151849" + integrity sha512-IyzdfqRDK8r1ulp/jbLPX18AuO+n5yNtL0+4T0QEUsgArRqIl/VRCG1imA5mYJBk0cBNC5+fWDHN+HWEW62ZEQ== dependencies: - react-reconciler "^0.25.1" - scheduler "^0.19.1" + react-reconciler "~0.26.2" + scheduler "^0.20.2" react-lifecycles-compat@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== -react-popper@^1.3.6: - version "1.3.7" - resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-1.3.7.tgz#f6a3471362ef1f0d10a4963673789de1baca2324" - integrity sha512-nmqYTx7QVjCm3WUZLeuOomna138R1luC4EqkW3hxJUrAe+3eNz3oFCLYdnPwILfn0mX1Ew2c3wctrjlUMYYUww== +react-query@^3.22.0: + version "3.22.0" + resolved "https://registry.yarnpkg.com/react-query/-/react-query-3.22.0.tgz#f59bbc48737c6f34070aee85037caf5a990de565" + integrity sha512-S1vv7N7np3N9MCCIE8vNGG7LpPhHDn4yQmY1sUZ+ABBuSU/h4Rtz+0qLpKh+Vs0/icvdsdrzI2LA2p0yqnLzRg== dependencies: - "@babel/runtime" "^7.1.2" - create-react-context "^0.3.0" - deep-equal "^1.1.1" - popper.js "^1.14.4" - prop-types "^15.6.1" - typed-styles "^0.0.7" - warning "^4.0.2" + "@babel/runtime" "^7.5.5" + broadcast-channel "^3.4.1" + match-sorter "^6.0.2" -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== +react-reconciler@~0.26.2: + version "0.26.2" + resolved "https://registry.yarnpkg.com/react-reconciler/-/react-reconciler-0.26.2.tgz#bbad0e2d1309423f76cf3c3309ac6c96e05e9d91" + integrity sha512-nK6kgY28HwrMNwDnMui3dvm3rCFjZrcGiuwLc5COUipBK5hWHLOxMJhSnSomirqWwjPBJKV1QcbkI0VJr7Gl1Q== dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" - prop-types "^15.6.2" - scheduler "^0.19.1" + scheduler "^0.20.2" -react-redux@~7.2.0: - version "7.2.2" - resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.2.tgz#03862e803a30b6b9ef8582dadcc810947f74b736" - integrity sha512-8+CQ1EvIVFkYL/vu6Olo7JFLWop1qRUeb46sGtIMDCSpgwPQq8fPLpirIB0iTqFe9XYEFPHssdX8/UwN6pAkEA== +react-redux@^7.2.5: + version "7.2.5" + resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.5.tgz#213c1b05aa1187d9c940ddfc0b29450957f6a3b8" + integrity sha512-Dt29bNyBsbQaysp6s/dN0gUodcq+dVKKER8Qv82UrpeygwYeX1raTtil7O/fftw/rFqzaf6gJhDZRkkZnn6bjg== dependencies: "@babel/runtime" "^7.12.1" + "@types/react-redux" "^7.1.16" hoist-non-react-statics "^3.3.2" loose-envify "^1.4.0" prop-types "^15.7.2" react-is "^16.13.1" -react-resize-detector@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/react-resize-detector/-/react-resize-detector-2.3.0.tgz#57bad1ae26a28a62a2ddb678ba6ffdf8fa2b599c" - integrity sha512-oCAddEWWeFWYH5FAcHdBYcZjAw9fMzRUK9sWSx6WvSSOPVRxcHd5zTIGy/mOus+AhN/u6T4TMiWxvq79PywnJQ== +react-refresh@0.8.3: + version "0.8.3" + resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f" + integrity sha512-X8jZHc7nCMjaCqoU+V2I0cOhNW+QMBwSUkeXnTi8IPe6zaRWfn60ZzvFDZqWPfmSJfjub7dDW1SP0jaHWLu/hg== + +react-resize-detector@^6.6.3: + version "6.7.6" + resolved "https://registry.yarnpkg.com/react-resize-detector/-/react-resize-detector-6.7.6.tgz#4416994e5ead7eba76606e3a248a1dfca49b67a3" + integrity sha512-/6RZlul1yePSoYJxWxmmgjO320moeLC/khrwpEVIL+D2EjLKhqOwzFv+H8laMbImVj7Zu4FlMa0oA7au3/ChjQ== dependencies: + "@types/resize-observer-browser" "^0.1.6" lodash.debounce "^4.0.8" lodash.throttle "^4.1.1" - prop-types "^15.6.0" - resize-observer-polyfill "^1.5.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.2.0" - resolved "https://registry.yarnpkg.com/react-side-effect/-/react-side-effect-1.2.0.tgz#0e940c78faba0c73b9b0eba9cd3dda8dfb7e7dae" - integrity sha512-v1ht1aHg5k/thv56DRcjw+WtojuuDHFUgGfc+bFHOWsF4ZK6C2V57DO0Or0GPsg6+LSTE0M6Ry/gfzhzSwbc5w== - dependencies: - shallowequal "^1.0.1" + resize-observer-polyfill "^1.5.1" -react-smooth@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/react-smooth/-/react-smooth-1.0.5.tgz#94ae161d7951cdd893ccb7099d031d342cb762ad" - integrity sha512-eW057HT0lFgCKh8ilr0y2JaH2YbNcuEdFpxyg7Gf/qDKk9hqGMyXryZJ8iMGJEuKH0+wxS0ccSsBBB3W8yCn8w== +react-smooth@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/react-smooth/-/react-smooth-2.0.0.tgz#561647b33e498b2e25f449b3c6689b2e9111bf91" + integrity sha512-wK4dBBR6P21otowgMT9toZk+GngMplGS1O5gk+2WSiHEXIrQgDvhR5IIlT74Vtu//qpTcipkgo21dD7a7AUNxw== dependencies: - lodash "~4.17.4" - prop-types "^15.6.0" + fast-equals "^2.0.0" raf "^3.4.0" - react-transition-group "^2.5.0" + react-transition-group "2.9.0" -react-transition-group@^2.3.1, react-transition-group@^2.5.0: +react-transition-group@2.9.0: version "2.9.0" resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.9.0.tgz#df9cdb025796211151a436c69a8f3b97b5b07c8d" integrity sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg== @@ -9464,51 +3568,22 @@ react-transition-group@^2.3.1, react-transition-group@^2.5.0: prop-types "^15.6.2" react-lifecycles-compat "^3.0.4" -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== +react@^17.0.2: + version "17.0.2" + resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037" + integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA== dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" - prop-types "^15.6.2" - -reactstrap@^8.6.0: - version "8.6.0" - resolved "https://registry.yarnpkg.com/reactstrap/-/reactstrap-8.6.0.tgz#baee0d12990c9fef3c82199fb05e84d9f0af1a26" - integrity sha512-03/UMbLPR6MhVStVUfCLuKh8xh4JOtNVkRxDB9/uHixN+cEQPOpSYa0K69YyK1/2YdZBs2qS6y0cQkK8NQKBHA== - dependencies: - "@babel/runtime" "^7.2.0" - classnames "^2.2.3" - prop-types "^15.5.8" - react-popper "^1.3.6" - react-transition-group "^2.3.1" -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" - integrity sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4= +read-pkg-up@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-3.0.0.tgz#3ed496685dba0f8fe118d0691dc51f4a1ff96f07" + integrity sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc= 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@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8" - integrity sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg= - 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" @@ -9518,7 +3593,7 @@ read-pkg@^3.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.0.1, readable-stream@^2.0.2, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6, readable-stream@~2.3.6: +readable-stream@^2.0.2, readable-stream@^2.3.3, 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== @@ -9531,7 +3606,7 @@ read-pkg@^3.0.0: string_decoder "~1.1.1" util-deprecate "~1.0.1" -readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.6.0: +readable-stream@^3.5.0, readable-stream@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== @@ -9540,22 +3615,6 @@ readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.6.0: 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" - integrity sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ== - 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" - readdirp@~3.5.0: version "3.5.0" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.5.0.tgz#9ba74c019b15d365278d2e91bb8c48d7b4d42c9e" @@ -9563,64 +3622,46 @@ readdirp@~3.5.0: 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== +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== dependencies: - util.promisify "^1.0.0" + picomatch "^2.2.1" -recharts-scale@^0.4.2: - version "0.4.3" - resolved "https://registry.yarnpkg.com/recharts-scale/-/recharts-scale-0.4.3.tgz#040b4f638ed687a530357292ecac880578384b59" - integrity sha512-t8p5sccG9Blm7c1JQK/ak9O8o95WGhNXD7TXg/BW5bYbVlr6eCeRBNpgyigD4p6pSSMehC5nSvBUPj6F68rbFA== +recharts-scale@^0.4.4: + version "0.4.5" + resolved "https://registry.yarnpkg.com/recharts-scale/-/recharts-scale-0.4.5.tgz#0969271f14e732e642fcc5bd4ab270d6e87dd1d9" + integrity sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w== dependencies: decimal.js-light "^2.4.1" -recharts@~1.8.5: - version "1.8.5" - resolved "https://registry.yarnpkg.com/recharts/-/recharts-1.8.5.tgz#ca94a3395550946334a802e35004ceb2583fdb12" - integrity sha512-tM9mprJbXVEBxjM7zHsIy6Cc41oO/pVYqyAsOHLxlJrbNBuLs0PHB3iys2M+RqCF0//k8nJtZF6X6swSkWY3tg== +recharts@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/recharts/-/recharts-2.1.2.tgz#ceeb01e53fb46da0d946a1e0f82d783ddf5b9d06" + integrity sha512-rwFQT6T4imhLzD1kYtg9ql8YOesbFRdSwZi95KWgi5udbBdLGRCR4SgaPO8kf0URHcC23mdRbLLTMYCnXng7zQ== dependencies: + "@types/d3-scale" "^3.0.0" + "@types/d3-shape" "^2.0.0" classnames "^2.2.5" - core-js "^2.6.10" - d3-interpolate "^1.3.0" - d3-scale "^2.1.0" - d3-shape "^1.2.0" - lodash "^4.17.5" - prop-types "^15.6.0" - react-resize-detector "^2.3.0" - react-smooth "^1.0.5" - recharts-scale "^0.4.2" - reduce-css-calc "^1.3.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" - -reduce-css-calc@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz#747c914e049614a4c9cfbba629871ad1d2927716" - integrity sha1-dHyRTgSWFKTJz7umKYca0dKSdxY= - dependencies: - balanced-match "^0.4.2" - math-expression-evaluator "^1.2.14" - reduce-function-call "^1.0.1" - -reduce-function-call@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/reduce-function-call/-/reduce-function-call-1.0.3.tgz#60350f7fb252c0a67eb10fd4694d16909971300f" - integrity sha512-Hl/tuV2VDgWgCSEeWMLwxLZqX7OK59eU1guxXsRKTAyeYimivsKdtcV4fu3r710tpG5GmDKDhQ0HSZLExnNmyQ== + d3-interpolate "^2.0.1" + d3-scale "^3.2.3" + d3-shape "^2.0.0" + eventemitter3 "^4.0.1" + lodash "^4.17.19" + react-is "16.10.2" + react-resize-detector "^6.6.3" + react-smooth "^2.0.0" + recharts-scale "^0.4.4" + reduce-css-calc "^2.1.8" + +reduce-css-calc@^2.1.8: + version "2.1.8" + resolved "https://registry.yarnpkg.com/reduce-css-calc/-/reduce-css-calc-2.1.8.tgz#7ef8761a28d614980dc0c982f772c93f7a99de03" + integrity sha512-8liAVezDmUcH+tdzoEGrhfbGcP7nOV4NkGE3a74+qqvE7nt9i4sKLGBuZNOnpI4WiGksiNPklZxva80061QiPg== dependencies: - balanced-match "^1.0.0" - -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= + css-unit-converter "^1.1.1" + postcss-value-parser "^3.3.0" redux-logger@~3.0.6: version "3.0.6" @@ -9641,262 +3682,65 @@ redux-thunk@~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== +redux@^4.0.0, redux@^4.0.4, redux@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/redux/-/redux-4.1.1.tgz#76f1c439bb42043f985fbd9bf21990e60bd67f47" + integrity sha512-hZQZdDEM25UY2P493kPYuKqviVwZ58lEmGQNeQ+gXa+U0gYPUBf7NKYazbe3m+bs/DzM/ahN12DbF+NG8i0CWw== 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" - integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== - -regenerator-runtime@^0.13.3: - version "0.13.5" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz#d878a1d094b4306d10b9096484b33ebd55e26697" - integrity sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA== + "@babel/runtime" "^7.9.2" regenerator-runtime@^0.13.4: - version "0.13.7" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55" - integrity sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew== - -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" - integrity sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A== - 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== + version "0.13.9" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" + integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA== -regexp.prototype.flags@^1.2.0, 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== +regexp.prototype.flags@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz#7ef352ae8d159e758c0eadca6f8fcb4eef07be26" + integrity sha512-JiBdRBq91WlY7uRJ0ds7R+dU02i6LKi8r3BuQhNXn+kmeLN+EfHhfjqMRis1zJxnlu88hq/4dx0P2OP3APRTOA== dependencies: + call-bind "^1.0.2" 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" - integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= - -renderkid@^2.0.1: - version "2.0.3" - resolved "https://registry.yarnpkg.com/renderkid/-/renderkid-2.0.3.tgz#380179c2ff5ae1365c522bf2fcfcff01c5b74149" - integrity sha512-z8CLQp7EZBPCwCnncgf9C4XAi3WR0dv+uWu/PjIyhhAb5d6IJ/QZqlHFprHeKT+59//V6BNUsLbvN8+2LarxGA== - 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" - integrity sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g== - -repeat-string@^1.6.1: - version "1.6.1" - resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" - integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= - -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.2" - resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" - integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== - 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.3" - 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.5.0" - 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" - integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= - -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" - integrity sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE= +regexpp@^3.1.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" + integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== -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== +remove-accents@0.4.2: + version "0.4.2" + resolved "https://registry.yarnpkg.com/remove-accents/-/remove-accents-0.4.2.tgz#0a43d3aaae1e80db919e07ae254b285d9e1c7bb5" + integrity sha1-CkPTqq4egNuRngeuJUsoXZ4ce7U= -requires-port@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" - integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= +require-from-string@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== -resize-observer-polyfill@^1.5.0: +resize-observer-polyfill@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464" integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg== -resolve-cwd@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a" - integrity sha1-AKn3OHVW4nA46uIyyqNypqWbZlo= - 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" - integrity sha1-six699nWiBvItuZTM17rywoYh0g= - 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" - integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= - -resolve@1.1.7: - version "1.1.7" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" - integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= - -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== +resolve@^1.10.0, resolve@^1.17.0, resolve@^1.20.0: + version "1.20.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" + integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== dependencies: + is-core-module "^2.2.0" path-parse "^1.0.6" -resolve@^1.10.0, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.15.1, resolve@^1.3.2, 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== +resolve@^2.0.0-next.3: + version "2.0.0-next.3" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-2.0.0-next.3.tgz#d41016293d4a8586a39ca5d9b5f15cbea1f55e46" + integrity sha512-W8LucSynKUIDu9ylraa7ueVZ7hc0uAgJBxVsQSKOXOyle8a93qXhcz+XAXZ8bIq2d6i4Ehddn6Evt+0/UwKk6Q== dependencies: + is-core-module "^2.2.0" path-parse "^1.0.6" restore-cursor@^3.1.0: @@ -9907,50 +3751,15 @@ restore-cursor@^3.1.0: 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" - integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== - -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.6.3: - version "2.6.3" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" - integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== - dependencies: - glob "^7.1.3" +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== -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== +rimraf@3.0.2, rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== dependencies: glob "^7.1.3" @@ -9962,254 +3771,87 @@ ripemd160@^2.0.0, ripemd160@^2.0.1: 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, 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= +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== dependencies: - aproba "^1.1.1" + queue-microtask "^1.2.2" -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== +rxjs@^6.6.7: + version "6.6.7" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" + integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ== dependencies: tslib "^1.9.0" -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" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - -safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0: +safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, 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" - integrity sha1-QKNmnzsHfR6UPURinhV91IAjvy4= - dependencies: - ret "~0.1.10" +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" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: +"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -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-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" - -sass@^1.32.12: - version "1.32.12" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.32.12.tgz#a2a47ad0f1c168222db5206444a30c12457abb9f" - integrity sha512-zmXn03k3hN0KaiVTjohgkg98C3UowhL1/VSGdj4/VAAiMKGQOE80PFPxFP2Kyq0OUskPKcY5lImkhBKEHlypJA== +sass@^1.39.0: + version "1.39.0" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.39.0.tgz#6c64695d1c437767c8f1a4e471288e831f81d035" + integrity sha512-F4o+RhJkNOIG0b6QudYU8c78ZADKZjKDk5cyrf8XTKWfrgbtyVVXImFstJrc+1pkQDCggyidIOytq6gS4gCCZg== dependencies: chokidar ">=3.0.0 <4.0.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" - integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== - -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== +scheduler@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.2.tgz#4baee39436e34aa93b4874bddcbf0fe8b8b50e91" + integrity sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ== 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" - seed-random@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/seed-random/-/seed-random-2.2.0.tgz#2a9b19e250a817099231a5b99a4daf80b7fbed54" integrity sha1-KpsZ4lCoFwmSMaW5mk2vgLf77VQ= -select-hose@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" - integrity sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo= - -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-regex@^3.1.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/semver-regex/-/semver-regex-3.1.3.tgz#b2bcc6f97f63269f286994e297e229b6245d0dc3" + integrity sha512-Aqi54Mk9uYTjVexLnR67rTyBusmwd04cLkHy9hNvk3+G3nT2Oyg7E0l4XVbOaNwIvQ3hHeYxGcyEy+mKreyBFQ== -"semver@2 || 3 || 4 || 5", semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0: +"semver@2 || 3 || 4 || 5": version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== -semver@6.3.0, semver@^6.0.0, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0: +semver@^6.0.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@^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== - -send@0.17.1: - version "0.17.1" - resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" - integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg== +semver@^7.2.1, semver@^7.3.5: + version "7.3.5" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" + integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== 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: - version "2.0.0" - resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" - integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= - -set-value@^2.0.0, set-value@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" - integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw== - dependencies: - extend-shallow "^2.0.1" - is-extendable "^0.1.1" - is-plain-object "^2.0.3" - split-string "^3.0.1" + lru-cache "^6.0.0" setimmediate@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= -setprototypeof@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" - integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ== - setprototypeof@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" @@ -10223,35 +3865,6 @@ sha.js@^2.4.0, sha.js@^2.4.8: 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" - integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ== - -shebang-command@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" - integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= - 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" @@ -10259,11 +3872,6 @@ shebang-command@^2.0.0: 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" - integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= - shebang-regex@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" @@ -10274,60 +3882,25 @@ shell-quote@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== -shellwords@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" - integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww== - -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== +side-channel@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" + integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== dependencies: - es-abstract "^1.17.0-next.1" - object-inspect "^1.7.0" + call-bind "^1.0.0" + get-intrinsic "^1.0.2" + object-inspect "^1.9.0" -signal-exit@^3.0.0, signal-exit@^3.0.2: +signal-exit@^3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== -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" - integrity sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU= - -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" @@ -10346,131 +3919,23 @@ slice-ansi@^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" - integrity sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw== - 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" - integrity sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ== - 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" - integrity sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg== - 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" - integrity sha512-hczmV6bDgdaEbVqhAeVMM/jfUfzuEZHsQg6eOmLgJht6G3mPKMxYm75w2+qhAQZ+4X+1+ATZ+QFKeOZD5riHng== - 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" - integrity sha512-V48klKZl8T6MzatbLlzzRNhMepEys9Y4oGFpypBFFn1gLI/QQ9HtLLyWJNbPlwGLelOVOEijUbTTJeLLI59jLw== - 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" - integrity sha1-RBttTTRnmPG05J6JIK37oOVD+a0= - 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" - integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== - -source-map-resolve@^0.5.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@0.7.3: + version "0.7.3" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" + integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== -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== +source-map@0.8.0-beta.0: + version "0.8.0-beta.0" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.8.0-beta.0.tgz#d4c1bb42c3f7ee925f005927ba10709e0d1d1f11" + integrity sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA== 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" - integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM= + whatwg-url "^7.0.0" -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: +source-map@^0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== -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" - integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= - spdx-correct@^3.0.0: version "3.1.1" resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9" @@ -10493,102 +3958,34 @@ spdx-expression-parse@^3.0.0: spdx-license-ids "^3.0.0" spdx-license-ids@^3.0.0: - version "3.0.5" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz#3694b5804567a458d3c8045842a6358632f62654" - integrity sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q== - -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" - integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw== - dependencies: - extend-shallow "^3.0.0" + version "3.0.10" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.10.tgz#0d9becccde7003d6c658d487dd48a32f0bf3014b" + integrity sha512-oie3/+gKf7QtpitB0LYLETe+k8SifzsX4KixvpOsbI6S0kRiRQ5MKOio8eMSAKQ17N06+wdEOXRiId+zOxo0hA== sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= -sshpk@^1.7.0: - version "1.16.1" - resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" - integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg== - 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== +stacktrace-parser@0.1.10: + version "0.1.10" + resolved "https://registry.yarnpkg.com/stacktrace-parser/-/stacktrace-parser-0.1.10.tgz#29fb0cae4e0d0b85155879402857a1639eb6051a" + integrity sha512-KJP1OCML99+8fhOHxwwzyWrlUuVX5GQ0ZpJTd1DFXhdkrvg1szxfHhawXUZ3g9TkXORQd4/WG68jMlQZ2p8wlg== dependencies: - figgy-pudding "^3.5.1" + type-fest "^0.7.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" - integrity sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY= - 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: +"statuses@>= 1.5.0 < 2": version "1.5.0" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= -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@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-3.0.0.tgz#22b0a2850cdf6503e73085da1fc7b7d0c2122f2f" + integrity sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA== + dependencies: + inherits "~2.0.4" + readable-stream "^3.5.0" stream-browserify@^2.0.1: version "2.0.2" @@ -10598,13 +3995,15 @@ stream-browserify@^2.0.1: 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== +stream-http@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-3.1.1.tgz#0370a8017cf8d050b9a8554afe608f043eaff564" + integrity sha512-S7OqaYu0EkFpgeGFb/NPOoPLxFko7TPqtEeFg5DXPB4v/KETHG0Ln6fRFrNezoelpaDKmycEmmZ81cC9DAwgYg== dependencies: - end-of-stream "^1.1.0" - stream-shift "^1.0.0" + builtin-status-codes "^3.0.0" + inherits "^2.0.4" + readable-stream "^3.6.0" + xtend "^4.0.2" stream-http@^2.7.2: version "2.8.3" @@ -10617,101 +4016,63 @@ stream-http@^2.7.2: 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" - integrity sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM= +stream-parser@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/stream-parser/-/stream-parser-0.3.1.tgz#1618548694420021a1182ff0af1911c129761773" + integrity sha1-FhhUhpRCACGhGC/wrxkRwSl2F3M= + dependencies: + debug "2" 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: - version "1.0.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" - integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= - dependencies: - code-point-at "^1.0.0" - is-fullwidth-code-point "^1.0.0" - strip-ansi "^3.0.0" - -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" - integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== - 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-hash@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/string-hash/-/string-hash-1.1.3.tgz#e8aafc0ac1855b4666929ed7dd1275df5d6c811b" + integrity sha1-6Kr8CsGFW0Zmkp7X3RJ1311sgRs= 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== + version "4.2.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.2.tgz#dafd4f9559a7585cfba529c6a0a4f73488ebd4c5" + integrity sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA== 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== +string.prototype.matchall@^4.0.5: + version "4.0.5" + resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.5.tgz#59370644e1db7e4c0c045277690cf7b01203c4da" + integrity sha512-Z5ZaXO0svs0M2xd/6By3qpeKpLKd9mO4v4q3oMEQrk8Ck4xOD5d5XeBOOjGrmVZZ/AHB1S0CgG4N5r1G9N3E2Q== dependencies: + call-bind "^1.0.2" 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.trimend@^1.0.1: - 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== + es-abstract "^1.18.2" + get-intrinsic "^1.1.1" + has-symbols "^1.0.2" + internal-slot "^1.0.3" + regexp.prototype.flags "^1.3.1" + side-channel "^1.0.4" + +string.prototype.trimend@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz#e75ae90c2942c63504686c18b287b4a0b1a45f80" + integrity sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A== dependencies: + call-bind "^1.0.2" define-properties "^1.1.3" - es-abstract "^1.17.5" -string.prototype.trimstart@^1.0.1: - 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== +string.prototype.trimstart@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz#b36399af4ab2999b4c9c648bd7a3fb2bb26feeed" + integrity sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw== dependencies: + call-bind "^1.0.2" define-properties "^1.1.3" - es-abstract "^1.17.5" -string_decoder@^1.0.0, string_decoder@^1.1.1: +string_decoder@1.3.0, string_decoder@^1.0.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== @@ -10741,76 +4102,44 @@ strip-ansi@6.0.0, strip-ansi@^6.0.0: 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" - integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= - 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" - integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= - 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@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= -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" - integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= - 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-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@^3.1.0, strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== -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== +styled-jsx@4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/styled-jsx/-/styled-jsx-4.0.1.tgz#ae3f716eacc0792f7050389de88add6d5245b9e9" + integrity sha512-Gcb49/dRB1k8B4hdK8vhW27Rlb2zujCk1fISrizCcToIs+55B4vmUM0N9Gi4nnVfFZWe55jRdWpAqH1ldAKWvQ== dependencies: - loader-utils "^1.1.0" - schema-utils "^1.0.0" + "@babel/plugin-syntax-jsx" "7.14.5" + "@babel/types" "7.15.0" + convert-source-map "1.7.0" + loader-utils "1.2.3" + source-map "0.7.3" + string-hash "1.1.3" + stylis "3.5.4" + stylis-rule-sheet "0.0.10" -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" +stylis-rule-sheet@0.0.10: + version "0.0.10" + resolved "https://registry.yarnpkg.com/stylis-rule-sheet/-/stylis-rule-sheet-0.0.10.tgz#44e64a2b076643f4b52e5ff71efc04d8c3c4a430" + integrity sha512-nTbZoaqoBnmK+ptANthb10ZRZOGC+EmTLLUxeYIuHNkEKcmKgXX1XWKkUBT2Ac4es3NybooPe0SmvKdhKJZAuw== -supports-color@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" - integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= +stylis@3.5.4: + version "3.5.4" + resolved "https://registry.yarnpkg.com/stylis/-/stylis-3.5.4.tgz#f665f25f5e299cf3d64654ab949a57c768b73fbe" + integrity sha512-8/3pSmthWM7lsPBKv7NXkzn2Uc9W7NotcwGNpJaa3k7WMM1XDCA4MgT5k/8BIexd5ydZdboXtU90XH9Ec4Bv/Q== supports-color@^5.3.0: version "5.5.0" @@ -10819,45 +4148,21 @@ supports-color@^5.3.0: 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== +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== dependencies: - has-flag "^3.0.0" + has-flag "^4.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== +supports-color@^8.0.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== 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: +svgsaver@^0.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/svgsaver/-/svgsaver-0.9.0.tgz#93d5dbb3f840953b8df0a14a942f4cc8d552335e" integrity sha1-k9Xbs/hAlTuN8KFKlC9MyNVSM14= @@ -10865,151 +4170,56 @@ svgsaver@~0.9.0: 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== - -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" +tabbable@^5.1.4: + version "5.2.1" + resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-5.2.1.tgz#e3fda7367ddbb172dcda9f871c0fdb36d1c4cd9c" + integrity sha512-40pEZ2mhjaZzK0BnI+QGNjJO8UYx9pP5v7BGe17SORTO0OEuuaAwQTkAp8whcZvqon44wKFOikD+Al11K3JICQ== -terser@^4.1.2, terser@^4.4.3, terser@^4.6.3: - version "4.8.0" - resolved "https://registry.yarnpkg.com/terser/-/terser-4.8.0.tgz#63056343d7c70bb29f3af665865a46fe03a0df17" - integrity sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw== +table@^6.0.9: + version "6.7.1" + resolved "https://registry.yarnpkg.com/table/-/table-6.7.1.tgz#ee05592b7143831a8c94f3cee6aae4c1ccef33e2" + integrity sha512-ZGum47Yi6KOOFDE8m223td53ath2enHcYLgOCjGr5ngu8bdIARQk6mN/wRMv4yMRcHnCSnHbCEha4sobQx5yWg== dependencies: - commander "^2.20.0" - source-map "~0.6.1" - source-map-support "~0.5.12" + ajv "^8.0.1" + lodash.clonedeep "^4.5.0" + lodash.truncate "^4.4.2" + slice-ansi "^4.0.0" + string-width "^4.2.0" + strip-ansi "^6.0.0" -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" +tapable@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.0.tgz#5c373d281d9c672848213d0e037d1c4165ab426b" + integrity sha512-FBk4IesMV1rBxX2tfiK8RAmogtWn53puLOQlvO8XuwlgxcYbP4mVPS9Ph4aeamSyyVjOl24aYWAuc8U5kCVwMw== -text-table@0.2.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" integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= -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: +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.1.0" - resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d" - integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA== - -timers-browserify@^2.0.4: - version "2.0.11" - resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.11.tgz#800b1f3eee272e5bc53ee465a04d0e804c31211f" - integrity sha512-60aV6sgJ5YEbzUdn9c8kYGIqOubPoUdqQCul3SBAsRCZ40s6Y5cMcrW4dt3/k/EsbLVJNl9n6Vz3fTc+k2GeKQ== +timers-browserify@2.0.12, timers-browserify@^2.0.4: + version "2.0.12" + resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.12.tgz#44a45c11fbf407f34f97bccd1577c652361b00ee" + integrity sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ== 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-emitter@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423" integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q== -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.3: - 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" - integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== +tippy.js@5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/tippy.js/-/tippy.js-5.1.2.tgz#5ac91233c59ab482ef5988cffe6e08bd26528e66" + integrity sha512-Qtrv2wqbRbaKMUb6bWWBQWPayvcDKNrGlvihxtsyowhT7RLGEh1STWuy6EMXC6QLkfKPB2MLnf8W2mzql9VDAw== 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" - integrity sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE= - -to-array@0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/to-array/-/to-array-0.1.4.tgz#17e6c11f73dd4f3d74cda7a4ff3238e9ad9bf890" - integrity sha1-F+bBH3PdTz10zaek/zI46a2b+JA= + popper.js "^1.16.0" to-arraybuffer@^1.0.0: version "1.0.1" @@ -11021,21 +4231,6 @@ to-fast-properties@^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" - integrity sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68= - 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" - integrity sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg= - 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" @@ -11043,29 +4238,11 @@ to-regex-range@^5.0.1: 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" - integrity sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw== - 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, 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" - tr46@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09" @@ -11073,30 +4250,35 @@ tr46@^1.0.1: dependencies: punycode "^2.1.0" -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== +tsconfig-paths@^3.11.0, tsconfig-paths@^3.9.0: + version "3.11.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.11.0.tgz#954c1fe973da6339c78e06b03ce2e48810b65f36" + integrity sha512-7ecdYDnIdmv639mmDwslG6KQg1Z9STTz1j7Gcz0xa+nshh/gKDAHcPxRbWOsA3SPp0tXP2leTcY9Kw+NAkfZzA== + dependencies: + "@types/json5" "^0.0.29" + json5 "^1.0.1" + minimist "^1.2.0" + strip-bom "^3.0.0" -tslib@^1.9.3: +tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tsutils@^3.17.1: - version "3.17.1" - resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759" - integrity sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g== +tslib@^2.0.0, tslib@^2.0.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" + integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== + +tsutils@^3.21.0: + version "3.21.0" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" + integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== dependencies: tslib "^1.8.1" @@ -11105,68 +4287,38 @@ tty-browserify@0.0.0: resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" integrity sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY= -tunnel-agent@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" - integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= - 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" - integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= +tty-browserify@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.1.tgz#3f05251ee17904dfd0677546670db9651682b811" + integrity sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw== -type-check@~0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" - integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== 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== + prelude-ls "^1.2.1" -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-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== -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" - -type@^1.0.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0" - integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg== +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== -type@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/type/-/type-2.0.0.tgz#5f16ff6ef2eb44f260494dae271033b29c09a9c3" - integrity sha512-KBt58xCHry4Cejnc2ISQAF7QY+ORngsWfxezO68+12hKV6lQY8P/psIkcbjeHWn7MqcgciWJyCCevFMJdIXpow== +type-fest@^0.7.1: + version "0.7.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.7.1.tgz#8dda65feaf03ed78f0a3f9678f1869147f7c5c48" + integrity sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg== typed-function@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/typed-function/-/typed-function-2.0.0.tgz#15ab3825845138a8b1113bd89e60cd6a435739e8" integrity sha512-Hhy1Iwo/e4AtLZNK10ewVVcP2UEs408DS35ubP825w/YgSBK1KVLwALvvIG4yX75QJrxjCpcWkzkVRB0BwwYlA== -typed-styles@^0.0.7: - version "0.0.7" - resolved "https://registry.yarnpkg.com/typed-styles/-/typed-styles-0.0.7.tgz#93392a008794c4595119ff62dde6809dbc40a3d9" - integrity sha512-pzP0PWoZUhsECYjABgCGQlRGL1n7tOHsgwYv3oIiEpJwGhFTuty/YNeduxQYzXXa3Ge5BdT6sHYIQYpl4uJ+5Q== - -typedarray@^0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" - integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= - typescript-compare@^0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/typescript-compare/-/typescript-compare-0.0.2.tgz#7ee40a400a406c2ea0a7e551efd3309021d5f425" @@ -11186,120 +4338,41 @@ typescript-tuple@^2.2.1: 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: +unbox-primitive@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" - integrity sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg== + resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471" + integrity sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw== dependencies: - arr-union "^3.1.0" - get-value "^2.0.6" - is-extendable "^0.1.1" - set-value "^2.0.1" - -uniq@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff" - integrity sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8= - -uniqs@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/uniqs/-/uniqs-2.0.0.tgz#ffede4b36b25290696e6e165d4a59edb998e6b02" - integrity sha1-/+3ks2slKQaW5uFl1KWe25mOawI= + function-bind "^1.1.1" + has-bigints "^1.0.1" + has-symbols "^1.0.2" + which-boxed-primitive "^1.0.2" -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" +unfetch@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/unfetch/-/unfetch-4.2.0.tgz#7e21b0ef7d363d8d9af0fb929a5555f6ef97a3be" + integrity sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA== -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== +unload@2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/unload/-/unload-2.2.0.tgz#ccc88fdcad345faa06a92039ec0f80b488880ef7" + integrity sha512-B60uB5TNBLtN6/LsgAf3udH9saB5p7gqJwcFfbOEZ8BcBHnGwCf6G/TGiEqkRAxX7zAFIUtzdrXQSdL3Q/wqNA== 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" - integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== + "@babel/runtime" "^7.6.2" + detect-node "^2.0.4" -unpipe@1.0.0, unpipe@~1.0.0: +unpipe@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= -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" - integrity sha1-g3aHP30jNRef+x5vw6jtDfyKtVk= - 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" - integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ== + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== 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" - integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= - -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.7" - resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.7.tgz#a8a83535e8c00a316e403a5db4ac1b9b853ae278" - integrity sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg== - dependencies: - querystringify "^2.1.1" - 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" @@ -11308,34 +4381,25 @@ url@^0.11.0: 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" - integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== +use-resize-observer@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/use-resize-observer/-/use-resize-observer-8.0.0.tgz#69bd80c1ddd94f3758563fe107efb25fed85067a" + integrity sha512-n0iKSeiQpJCyaFh5JA0qsVLBIovsF4EIIR1G6XiBwKJN66ZrD4Oj62bjcuTAATPKiSp6an/2UZZxCf/67fk3sQ== + dependencies: + "@juggle/resize-observer" "^3.3.1" + +use-subscription@1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/use-subscription/-/use-subscription-1.5.1.tgz#73501107f02fad84c6dd57965beb0b75c68c42d1" + integrity sha512-Xv2a1P/yReAjAbhylMfFplFKj9GssgTwN7RlcTxBujFQcloStWNDQdc4g4NRWH9xS4i/FDk04vQBptAXoF3VcA== + dependencies: + object-assign "^4.1.1" 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" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= -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" @@ -11343,6 +4407,18 @@ util@0.10.3: dependencies: inherits "2.0.1" +util@0.12.4, util@^0.12.0: + version "0.12.4" + resolved "https://registry.yarnpkg.com/util/-/util-0.12.4.tgz#66121a31420df8f01ca0c464be15dfa1d1850253" + integrity sha512-bxZ9qtSlGUWSOy9Qa9Xgk11kSslpuZwaxCg4sNIDj6FLucDab2JxnHwyNTCpHMtK1MjoQiWQ6DiUMZYbSrO+Sw== + dependencies: + inherits "^2.0.3" + is-arguments "^1.0.4" + is-generator-function "^1.0.7" + is-typed-array "^1.1.3" + safe-buffer "^5.1.2" + which-typed-array "^1.1.2" + util@^0.11.0: version "0.11.1" resolved "https://registry.yarnpkg.com/util/-/util-0.11.1.tgz#3236733720ec64bb27f6e26f421aaa2e1b588d61" @@ -11350,38 +4426,23 @@ util@^0.11.0: 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" - integrity sha1-ihagXURWV6Oupe7MWxKk+lN5dyw= +uuid@8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== -utils-merge@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" - integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= - -uuid@8.2.0: - version "8.2.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.2.0.tgz#cb10dd6b118e2dada7d0cd9730ba7417c93d920e" - integrity sha512-CYpGiFTUrmI6OBMkAdjSDM0k5h8SkkiTP4WAjQgDgNB1S3Ou9VBEvr6q0Kv2H1mMk7IWfxYGpMH5sd5AvcIV2Q== - -uuid@^3.0.1, uuid@^3.3.2: - version "3.4.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" - integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== - -uuidv4@~6.1.1: - version "6.1.1" - resolved "https://registry.yarnpkg.com/uuidv4/-/uuidv4-6.1.1.tgz#6565b4f2be7d6f841c14106f420fdb701eae5c81" - integrity sha512-ZplGb1SHFMVH3l7PUQl2Uwo+FpJQV6IPOoU+MjjbqrNYQolqbGwv+/sn9F+AGMsMOgGz3r9JN3ztGUi0VzMxmw== +uuidv4@^6.2.12: + version "6.2.12" + resolved "https://registry.yarnpkg.com/uuidv4/-/uuidv4-6.2.12.tgz#e8c1d1d733c3fa4963d4610b8a3a09b4ec58ca96" + integrity sha512-UnN4ThIYWhv3ZUE8UwDnnCvh4JafCNu+sQkxmLyjCVwK3rjLfkg3DYiEv6oCMDIAIVEDP4INg4kX/C5hKaRzZA== dependencies: - "@types/uuid" "8.0.0" - uuid "8.2.0" + "@types/uuid" "8.3.1" + uuid "8.3.2" 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== + version "2.3.0" + resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" + integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== validate-npm-package-license@^3.0.1: version "3.0.4" @@ -11391,240 +4452,31 @@ validate-npm-package-license@^3.0.1: 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" - integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= - -vendors@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.4.tgz#e2b800a53e7a29b93506c3cf41100d16c4c4ad8e" - integrity sha512-/juG65kTL4Cy2su4P8HjtkTxk6VmJDiOPBufWniqQ6wknac6jNiXS9vU+hO3wgusiyqWlzTbVHi0dyJqRONg3w== - -verror@1.10.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" - integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA= - dependencies: - assert-plus "^1.0.0" - core-util-is "1.0.2" - extsprintf "^1.2.0" - -vm-browserify@^1.0.1: +vm-browserify@1.1.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" - -warning@^4.0.2, warning@^4.0.3: +warning@^4.0.0: version "4.0.3" resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3" integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w== dependencies: loose-envify "^1.0.0" -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== +watchpack@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.1.1.tgz#e99630550fca07df9f90a06056987baa40a689c7" + integrity sha512-Oo7LXCmc1eE1AjyuSBmtC3+Wy4HcV8PxWh2kP6fOl8yTlNS7r0K9l1ao2lrrUza7V39Y3D/BbJgY8VeSlc5JKw== dependencies: + glob-to-regexp "^0.4.1" 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" - integrity sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA== - 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.4" - resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.4.tgz#89ad5295bbf64b480abcba31e4953aca706f5760" - integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg== - dependencies: - http-parser-js ">=0.5.1" - safe-buffer ">=5.1.0" - websocket-extensions ">=0.1.1" - -websocket-extensions@>=0.1.1: - version "0.1.4" - resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" - integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== - -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" - integrity sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw== - dependencies: - iconv-lite "0.4.24" - -whatwg-fetch@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.1.0.tgz#49d630cdfa308dba7f2819d49d09364f540dbcc6" - integrity sha512-pgmbsVWKpH9GxLXZmtdowDIqtb/rvPyjjQv3z9wLcmgWKFHilKnZD3ldgrOlwJoPGOUluQsRPWd52yVkPfmI1A== - -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" @@ -11634,22 +4486,33 @@ whatwg-url@^7.0.0: tr46 "^1.0.1" webidl-conversions "^4.0.2" -which-module@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" - integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= +which-boxed-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" + integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== + dependencies: + is-bigint "^1.0.1" + is-boolean-object "^1.1.0" + is-number-object "^1.0.4" + is-string "^1.0.5" + is-symbol "^1.0.3" 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.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" - integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== +which-typed-array@^1.1.2: + version "1.1.7" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.7.tgz#2761799b9a22d4b8660b3c1b40abaa7739691793" + integrity sha512-vjxaB4nfDqwKI0ws7wZpxIlde1XrLX5uB0ZjpfshgmapJMD7jJWhZI+yToJTqaFByF0eNBcYxbjmCzoRP7CfEw== dependencies: - isexe "^2.0.0" + available-typed-arrays "^1.0.5" + call-bind "^1.0.2" + es-abstract "^1.18.5" + foreach "^2.0.5" + has-tostringtag "^1.0.0" + is-typed-array "^1.1.7" which@^2.0.1: version "2.0.2" @@ -11658,177 +4521,11 @@ which@^2.0.1: dependencies: isexe "^2.0.0" -word-wrap@~1.2.3: +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== -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" - integrity sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU= - 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" @@ -11838,146 +4535,36 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + 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" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= -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.4" - resolved "https://registry.yarnpkg.com/ws/-/ws-6.1.4.tgz#5b5c8800afab925e94ccb29d153c8d02c1776ef9" - integrity sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA== - 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" - integrity sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4= - -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, xtend@~4.0.1: +xtend@^4.0.0, xtend@^4.0.2: 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 || ^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@^3.0.2: - version "3.1.1" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" - integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== - 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@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" - -yeast@0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419" - integrity sha1-AI4G2AlDIMNy28L47XagymyKxBk= +yaml@^1.10.0: + version "1.10.2" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" + integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== |
