From 05c0008aead9c2affb284bc6d3005f75579f9545 Mon Sep 17 00:00:00 2001 From: Michael Sekamanya <86433807+mawandm@users.noreply.github.com> Date: Fri, 24 May 2024 19:42:12 -0700 Subject: [PATCH] feat(frontend): integrate external apps with nesis (#91) This PR adds a frontend UI to manage integration apps with nesis. Closes #83 --- docs/README.md | 2 +- docs/mkdocs.yml | 5 + docs/requirements.txt | 1 + docs/src/apps/example.ipynb | 357 +++++++++++++ nesis/api/core/controllers/apps_controller.py | 28 + nesis/api/core/models/entities.py | 3 + nesis/api/core/services/__init__.py | 17 +- nesis/api/core/services/app_service.py | 34 +- .../core/controllers/test_apps_controller.py | 48 +- nesis/frontend/client/package-lock.json | 481 +++--------------- nesis/frontend/client/src/SessionContext.js | 2 +- .../client/src/components/inputs/Checkbox.js | 1 + .../src/pages/Settings/Apps/AppDetailPage.js | 187 +++++++ .../src/pages/Settings/Apps/AppsPage.js | 363 +++++++++++++ .../client/src/pages/Settings/SettingPage.js | 11 + .../pages/Settings/Users/UserDetailPage.js | 10 +- nesis/frontend/package-lock.json | 285 +---------- nesis/frontend/server/api/apps.js | 166 ++++++ nesis/frontend/server/api/apps.spec.js | 187 +++++++ nesis/frontend/server/api/index.js | 1 + nesis/frontend/server/main.js | 8 + nesis/frontend/server/util/test-util.js | 14 + version.txt | 2 +- 23 files changed, 1515 insertions(+), 698 deletions(-) create mode 100644 docs/src/apps/example.ipynb create mode 100644 nesis/frontend/client/src/pages/Settings/Apps/AppDetailPage.js create mode 100644 nesis/frontend/client/src/pages/Settings/Apps/AppsPage.js create mode 100644 nesis/frontend/server/api/apps.js create mode 100644 nesis/frontend/server/api/apps.spec.js diff --git a/docs/README.md b/docs/README.md index b581a25..b1eb755 100644 --- a/docs/README.md +++ b/docs/README.md @@ -7,7 +7,7 @@ We use mkdocs to create Nesis documentation. 1. Install dependencies with ```commandline - pip install -r requirements-docs.txt + pip install -r requirements.txt ``` 2. Serve the documentation on your local with ```commandline diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 0659767..f6372c4 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -44,10 +44,15 @@ nav: - 'installing/helm.md' - 'installing/ametnes.md' - 'Access Control': 'rbac.md' + - 'App Integration': 'apps/example.ipynb' - 'Development Guide': - 'Local Development': 'dev-guide/local.md' - 'Architecture': 'dev-guide/architecture.md' - 'Contributing': 'dev-guide/contributing.md' + +plugins: + - mkdocs-jupyter + extra_css: - css/extra.css extra_javascript: diff --git a/docs/requirements.txt b/docs/requirements.txt index 93141b0..4e099bf 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -3,3 +3,4 @@ jinja2==3.1.3 mkdocs-material==9.0.5 mkdocs-bootstrap==1.1 mkdocs-bootswatch==1.1 +mkdocs-jupyter==0.24.7 diff --git a/docs/src/apps/example.ipynb b/docs/src/apps/example.ipynb new file mode 100644 index 0000000..56a9a0a --- /dev/null +++ b/docs/src/apps/example.ipynb @@ -0,0 +1,357 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Developing Apps\n", + "\n", + "The Nesis' API gives you the ability to develop apps (new apps or extend existing apps) that interact with your private document and data repositories. Using Nesis' security model, you are able to apply role based access control to your data such that users are only able to seamlessly access data tied to their security profile.\n", + "\n", + "Some use cases for the Nesis API.\n", + "1. You already have a mobile banking app and want to give it generative AI capabilities to help you customers to interract with their bank statements from within your mobile banking app.\n", + "2. You have an existing internal legacy customer service application that you'd like to extend and give AI capabilities.\n", + "3. You want to add generative AI powered conversation interraction to your website so that your users have tailored responses based on their personalized data.\n", + "\n", + "\n", + "## Create a Nesis App\n", + "In the Nesis frontend,\n", + "\n", + "Navigate to **Settings** -> **Roles** and create a role with these properties\n", + "1. Name: `nesis-admin-app`\n", + "2. Policy:\n", + "\n", + " ```\n", + " {\n", + " \"items\": [\n", + " {\n", + " \"action\": \"create\",\n", + " \"resource\": \"users/*\"\n", + " },\n", + " {\n", + " \"action\": \"update\",\n", + " \"resource\": \"users/*\"\n", + " },\n", + " {\n", + " \"action\": \"update\",\n", + " \"resource\": \"roles/*\"\n", + " },\n", + " {\n", + " \"action\": \"create\",\n", + " \"resource\": \"roles/*\"\n", + " },\n", + " {\n", + " \"action\": \"update\",\n", + " \"resource\": \"datasources/*\"\n", + " },\n", + " {\n", + " \"action\": \"create\",\n", + " \"resource\": \"datasources/*\"\n", + " }\n", + " ]\n", + " }\n", + " ```\n", + "\n", + "Navigate to **Settings** -> **Apps** and then create an app with the following details\n", + "1. Name: `nesis-admin-app`\n", + "2. Description: `Nesis admin application`\n", + "3. Enabled: `Checked`\n", + "4. Role: `nesis-admin-app`\n", + "\n", + "Note down the API key generated" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Navigate to **Settings** -> **Apps** and then create another app with the following details\n", + "1. Name: `nesis-prediction-app`\n", + "2. Description: `Nesis prediction application`\n", + "3. Enabled: `Checked`\n", + "4. Role: Do not select a role\n", + "\n", + "Note down the API key generated\n", + "\n", + "> This is all you need to do in the UI. The rest is all API driven using the API keys." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create a datasource" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import json\n", + "import requests\n", + "\n", + "nesis_api_endpoint = \"http://nesis-endpoint.com/v1\"\n", + "admin_api_key = \"the-admin-api-key\"\n", + "predictions_api_key = \"the-prediction-app-api-key\"" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "admin_api_headers = {'Content-Type': 'application/json', 'Authorization': f'Bearer {admin_api_key}'}\n", + "prediction_api_header = {'Content-Type': 'application/json', 'Authorization': f'Bearer {predictions_api_key}'}" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "datasource = {\"type\": \"minio\", \"name\": \"documents\", \"connection\": {\"user\": \"your_username\",\"password\":\"your_password\",\"endpoint\": \"http://minio:port\",\"dataobjects\": \"bucket-name\"}}" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "datasource_response = requests.post(f'{nesis_api_endpoint}/datasources', headers=admin_api_headers, json=datasource)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "datasource = json.loads(datasource_response.text)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create a role to be used to query the datasource" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "role = {\n", + " \"name\": \"data-reader\",\n", + " \"policy\": {\n", + " \"items\": [\n", + " {\n", + " \"action\": \"create\",\n", + " \"resource\": \"predictions/*\"\n", + " },\n", + " {\n", + " \"action\": \"read\",\n", + " \"resource\": \"datasources/*\"\n", + " }\n", + " ]\n", + " }\n", + "}\n" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "role_response = requests.post(f'{nesis_api_endpoint}/roles', headers=admin_api_headers, json=role)" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{\"create_date\":\"2024-05-24 18:45:28\",\"id\":\"f0e63ee3-d1af-4d56-9b99-e28bf308db45\",\"name\":\"data-reader\"}\n", + "\n" + ] + } + ], + "source": [ + "print(role_response.text)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "role = json.loads(role_response.text)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create a user and assign the above role" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "user_response = requests.post(f'{nesis_api_endpoint}/users', headers=admin_api_headers, json={'email': 'test.email@domain.com', 'name': 'Da Zone', 'password': 'P@ssword', 'roles': [role['id']]})" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{\"attributes\":null,\"create_date\":\"2024-05-24 18:47:01\",\"email\":\"some.email@domain.com\",\"enabled\":true,\"id\":\"b919c24d-7574-43ec-9889-200584e44d6b\",\"name\":\"Da Zone\",\"root\":false}\n", + "\n" + ] + } + ], + "source": [ + "print(user_response.text)" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [], + "source": [ + "user = json.loads(user_response.text)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Query using the prediction API key. \n", + "\n", + "> This fails because no role was attached to the prediction app." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [], + "source": [ + "prediction_response = requests.post(f'{nesis_api_endpoint}/modules/qanda/predictions', headers=prediction_api_header, json={'query': 'summarize the only office agreement'})" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], + "source": [ + "print(prediction_response)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Query using the prediction API key and user header\n", + "> This succeeds because the prediction app inherits permissions from the user (who has the right permission) using the `X-Nesis-Request-UserKey` header." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [], + "source": [ + "prediction_response = requests.post(f'{nesis_api_endpoint}/modules/qanda/predictions', headers={**prediction_api_header, 'X-Nesis-Request-UserKey': user['id']}, json={'query': 'summarize the only office agreement'})" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{\"create_date\":\"2024-05-24 18:59:17\",\"data\":{\"choices\":[{\"delta\":null,\"finish_reason\":\"stop\",\"index\":0,\"message\":{\"content\":\"The Only Office Agreement is a legal document that outlines the terms and conditions of leasing office space. It typically includes details such as the duration of the lease, rental payments, maintenance responsibilities, and any other specific provisions agreed upon by the landlord and tenant. This agreement serves to protect the rights and obligations of both parties involved in the leasing of office space.\",\"role\":\"assistant\"},\"sources\":[]}],\"created\":1716577157,\"id\":\"a0bc9a93-a3e5-45e8-9d4f-090f652e8dc2\",\"model\":\"rag\",\"object\":\"completion\"},\"id\":null,\"input\":\"summarize the only office agreement\",\"module\":\"qanda\"}\n", + "\n" + ] + } + ], + "source": [ + "print(prediction_response.text)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Conclusion\n", + "This notebook shows you a simplistic workflow of how to programatically interract with Nesis. We see how to create an app and assign it **CREATE** permissions on **USERS**, **DATASOURCES** and **ROLES**. With this app, we are able to programmatically create multiple users, datasources and roles.\n", + "\n", + "By doing this you are able to integrate Nesis programically into your existing worklows, automating the creation of Nesis users and roles inline with your existing enterprise user management system and do this at scale.\n", + "\n", + "Lastly, we see how Nesis can be easily integrated into your existing application such as a website and easily tailored responses to your logged in website users, thus giving your website generative AI capabilities.\n", + "\n", + "Next steps;\n", + "\n", + "1. View the Nesis repository https://github.com/ametnes/nesis/\n", + "2. View the documentation https://ametnes.github.io/nesis/.\n", + "3. Join our [Slack community](https://join.slack.com/t/nesiscommunity/shared_invite/zt-2gpp38ts2-tAfea6R_q9RHudhPEcBJSA) and let us know how you get on or ask any questions." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.5" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/nesis/api/core/controllers/apps_controller.py b/nesis/api/core/controllers/apps_controller.py index edaa9e4..feb3912 100644 --- a/nesis/api/core/controllers/apps_controller.py +++ b/nesis/api/core/controllers/apps_controller.py @@ -76,3 +76,31 @@ def operate_app(app_id): except: _LOG.exception("Error getting app") return jsonify(error_message("Server error")), 500 + + +@app.route("/v1/apps//roles", methods=[controllers.POST, controllers.GET]) +def operate_app_roles(app_id): + token = get_bearer_token(request.headers.get("Authorization")) + try: + match request.method: + case controllers.POST: + result = services.app_service.update( + token=token, + app_id=app_id, + app={"roles": [request.json.get("id")]}, + ) + return jsonify(result.to_dict()) + case controllers.GET: + results = services.app_service.get_roles(token=token, app_id=app_id) + return jsonify({"items": [item.to_dict() for item in results]}) + except util.ServiceException as se: + return jsonify(error_message(str(se))), 400 + except util.ConflictException as se: + return jsonify(error_message(str(se))), 409 + except util.UnauthorizedAccess: + return jsonify(error_message("Unauthorized access")), 401 + except util.PermissionException as ex: + return jsonify(error_message(str(ex))), 403 + except: + _LOG.exception("Error getting user") + return jsonify(error_message("Server error")), 500 diff --git a/nesis/api/core/models/entities.py b/nesis/api/core/models/entities.py index 46ff553..fb96b22 100644 --- a/nesis/api/core/models/entities.py +++ b/nesis/api/core/models/entities.py @@ -541,6 +541,7 @@ def __init__( self.secret = secret self.description = description self.create_date = create_date + self.roles = [] def to_dict(self, **kwargs) -> Dict[str, Any]: result = { @@ -552,6 +553,8 @@ def to_dict(self, **kwargs) -> Dict[str, Any]: } if isinstance(self.secret, str) and "secret" in (kwargs.get("include") or []): result["secret"] = self.secret + if hasattr(self, "roles") and self.roles: + result["roles"] = [role.to_dict() for role in self.roles or []] return result diff --git a/nesis/api/core/services/__init__.py b/nesis/api/core/services/__init__.py index 2d07c32..9492dc7 100644 --- a/nesis/api/core/services/__init__.py +++ b/nesis/api/core/services/__init__.py @@ -184,7 +184,6 @@ def authorized_resources( user_id=user_id, ) - # If root, return all actions def get_enabled_datasources(): return session.query(Datasource).filter(Datasource.enabled.is_(True)).all() @@ -194,6 +193,7 @@ def get_enabled_tasks(): def get_enabled_apps(): return session.query(App).filter(App.enabled.is_(True)).all() + # If root, return all actions if session_user is not None and session_user.get("root") or False: match resource_type: case ResourceType.DATASOURCES: @@ -235,7 +235,10 @@ def get_enabled_apps(): action_list = [] dss = None tasks = None + apps = None + # If not root, return only actions permitted for role_action in query.all(): + # If wildcard, then we return all resources if role_action.resource and role_action.resource.strip() == "*": match resource_type: case ResourceType.DATASOURCES: @@ -262,6 +265,18 @@ def get_enabled_apps(): ) for ds in tasks ] + case ResourceType.APPS: + if apps is None: + apps = get_enabled_apps() + action_list += [ + RoleAction( + action=Action[role_action.action.name], + resource_type=resource_type, + resource=app.uuid, + role=None, + ) + for app in apps + ] case _: raise util.PermissionException("Unauthorized resource type") else: diff --git a/nesis/api/core/services/app_service.py b/nesis/api/core/services/app_service.py index 33072a6..b3f26b3 100644 --- a/nesis/api/core/services/app_service.py +++ b/nesis/api/core/services/app_service.py @@ -90,7 +90,7 @@ def get(self, **kwargs) -> list[AppRole]: return ( session.query(Role) .filter(Role.id == AppRole.role) - .filter(AppRole.user == App.id) + .filter(AppRole.app == App.id) .filter(App.uuid == app_id) .all() ) @@ -235,6 +235,19 @@ def get(self, **kwargs): query = session.query(App).filter(App.uuid.in_(apps)) if app_id: query = query.filter(App.uuid == app_id) + + results = query.all() + + if app_id: + roles = ( + session.query(AppRole) + .filter(AppRole.app == App.id) + .filter(App.uuid == app_id) + .all() + ) + if len(roles) > 0 and len(results) > 0: + results[0].roles = roles + return query.all() except Exception as e: self._LOG.exception(f"Error when fetching apps") @@ -349,6 +362,25 @@ def update(self, **kwargs): if session: session.close() + def __get_rbac_resource(self, app_id): + return f"{self._resource_type}/{app_id}" + + def get_roles(self, **kwargs): + uuid = kwargs["app_id"] + + session = DBSession() + + services.authorized( + session_service=self._session_service, + session=session, + token=kwargs.get("token"), + action=Action.READ, + resource_type=self._resource_type, + resource=self.__get_rbac_resource(app_id=uuid), + ) + + return self._app_role_service.get(**kwargs) + class AppSessionService(ServiceOperation): """ diff --git a/nesis/api/tests/core/controllers/test_apps_controller.py b/nesis/api/tests/core/controllers/test_apps_controller.py index 0fa2d0c..57eba68 100644 --- a/nesis/api/tests/core/controllers/test_apps_controller.py +++ b/nesis/api/tests/core/controllers/test_apps_controller.py @@ -143,9 +143,7 @@ def test_app_as_user(client, http_client, tc): "dataobjects": "initdb", }, } - datasource_result = tests.create_datasource( - client=client, session=admin_session, datasource=datasource - ) + tests.create_datasource(client=client, session=admin_session, datasource=datasource) role_result = tests.create_role(client=client, session=admin_session, role=role) app = create_app(client=client, session=admin_session) @@ -186,3 +184,47 @@ def test_app_as_user(client, http_client, tc): assert 200 == response.status_code, response.text assert response.json["input"] == "what do you know?" tc.assertDictEqual(response.json["data"], {"response": "the response"}) + + +def test_operate_app_roles(client, http_client, tc): + """ + Test operations on a specific app. + 1. Add a role to an app + 2. Get an apps roles + """ + admin_session = tests.get_admin_session(app=client) + role = { + "name": f"user-admin-{uuid.uuid4()}", + "policy": { + "items": [ + {"action": "create", "resource": "predictions/*"}, + {"action": "read", "resource": "datasources/*"}, + ] + }, + } + + app = create_app(client=client, session=admin_session) + + # Get roles assigned to the app, expect count == 0 + response = client.get( + f"/v1/apps/{app['id']}/roles", + headers=tests.get_header(token=admin_session["token"]), + ) + assert 200 == response.status_code, response.text + assert 0 == len(response.json["items"]) + + role_result = tests.create_role(client=client, session=admin_session, role=role) + response = client.post( + f"/v1/apps/{app['id']}/roles", + headers=tests.get_header(token=admin_session["token"]), + data=json.dumps(role_result), + ) + assert 200 == response.status_code, response.text + + # Get roles assigned to the app, expect count == 1 + response = client.get( + f"/v1/apps/{app['id']}/roles", + headers=tests.get_header(token=admin_session["token"]), + ) + assert 200 == response.status_code, response.text + assert 1 == len(response.json["items"]) diff --git a/nesis/frontend/client/package-lock.json b/nesis/frontend/client/package-lock.json index 9652d46..3242e0f 100644 --- a/nesis/frontend/client/package-lock.json +++ b/nesis/frontend/client/package-lock.json @@ -4159,12 +4159,6 @@ "integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==", "dev": true }, - "node_modules/@kurkle/color": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz", - "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==", - "peer": true - }, "node_modules/@leichtgewicht/ip-codec": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", @@ -10783,6 +10777,7 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, "funding": [ { "type": "github", @@ -10847,14 +10842,6 @@ "node": "*" } }, - "node_modules/bignumber.js": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", - "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==", - "engines": { - "node": "*" - } - }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -11092,11 +11079,6 @@ "node": "*" } }, - "node_modules/buffer-equal-constant-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" - }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -11276,18 +11258,6 @@ "node": ">=10" } }, - "node_modules/chart.js": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.2.tgz", - "integrity": "sha512-6GD7iKwFpP5kbSD4MeRRRlTnQvxfQREy36uEtm1hzHzcOqwWx0YEHuspuoNlslu+nciLIB7fjjsHkUv/FzFcOg==", - "peer": true, - "dependencies": { - "@kurkle/color": "^0.3.0" - }, - "engines": { - "pnpm": ">=8" - } - }, "node_modules/check-types": { "version": "11.2.3", "resolved": "https://registry.npmjs.org/check-types/-/check-types-11.2.3.tgz", @@ -12801,14 +12771,6 @@ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "dev": true }, - "node_modules/ecdsa-sig-formatter": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", - "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", - "dependencies": { - "safe-buffer": "^5.0.1" - } - }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -14017,7 +13979,8 @@ "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true }, "node_modules/extract-zip": { "version": "1.7.0", @@ -14737,56 +14700,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/gaxios": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.6.0.tgz", - "integrity": "sha512-bpOZVQV5gthH/jVCSuYuokRo2bTKOcuBiVWpjmTn6C5Agl5zclGfTljuGsQZxwwDBkli+YhZhP4TdlqTnhOezQ==", - "dependencies": { - "extend": "^3.0.2", - "https-proxy-agent": "^7.0.1", - "is-stream": "^2.0.0", - "node-fetch": "^2.6.9", - "uuid": "^9.0.1" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/gaxios/node_modules/agent-base": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", - "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", - "dependencies": { - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/gaxios/node_modules/https-proxy-agent": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", - "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", - "dependencies": { - "agent-base": "^7.0.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/gcp-metadata": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.0.tgz", - "integrity": "sha512-Jh/AIwwgaxan+7ZUUmRLCjtchyDiqh4KjBJ5tW3plBZb5iL/BPcso8A5DlzeD9qlw0duCamnNdpFjxwaT0KyKg==", - "dependencies": { - "gaxios": "^6.0.0", - "json-bigint": "^1.0.0" - }, - "engines": { - "node": ">=14" - } - }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -15046,22 +14959,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/google-auth-library": { - "version": "9.10.0", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.10.0.tgz", - "integrity": "sha512-ol+oSa5NbcGdDqA+gZ3G3mev59OHBZksBTxY/tYwjtcp1H/scAFwJfSQU9/1RALoyZ7FslNbke8j4i3ipwlyuQ==", - "dependencies": { - "base64-js": "^1.3.0", - "ecdsa-sig-formatter": "^1.0.11", - "gaxios": "^6.1.1", - "gcp-metadata": "^6.1.0", - "gtoken": "^7.0.0", - "jws": "^4.0.0" - }, - "engines": { - "node": ">=14" - } - }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -15083,18 +14980,6 @@ "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==" }, - "node_modules/gtoken": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz", - "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==", - "dependencies": { - "gaxios": "^6.0.0", - "jws": "^4.0.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, "node_modules/gunzip-maybe": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/gunzip-maybe/-/gunzip-maybe-1.4.2.tgz", @@ -18584,14 +18469,6 @@ "node": ">=4" } }, - "node_modules/json-bigint": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", - "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", - "dependencies": { - "bignumber.js": "^9.0.0" - } - }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -18683,33 +18560,6 @@ "node": ">=4.0" } }, - "node_modules/jwa": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", - "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", - "dependencies": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/jws": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", - "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", - "dependencies": { - "jwa": "^2.0.0", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/jwt-decode": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", - "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", - "engines": { - "node": ">=18" - } - }, "node_modules/keyv": { "version": "4.5.3", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.3.tgz", @@ -19486,6 +19336,7 @@ "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, "dependencies": { "whatwg-url": "^5.0.0" }, @@ -19510,17 +19361,20 @@ "node_modules/node-fetch/node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true }, "node_modules/node-fetch/node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true }, "node_modules/node-fetch/node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" @@ -25112,19 +24966,6 @@ "is-typedarray": "^1.0.0" } }, - "node_modules/typescript": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", - "peer": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=4.2.0" - } - }, "node_modules/uglify-js": { "version": "3.17.4", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", @@ -25878,7 +25719,7 @@ "version": "2.25.4", "resolved": "https://registry.npmjs.org/webpack-hot-middleware/-/webpack-hot-middleware-2.25.4.tgz", "integrity": "sha512-IRmTspuHM06aZh98OhBJtqLpeWFM8FXJS5UYpKYxCJzyFoyWj1w6VGFfomZU7OPA55dMLrQK0pRT1eQ3PACr4w==", - "devOptional": true, + "dev": true, "dependencies": { "ansi-html-community": "0.0.8", "html-entities": "^2.1.0", @@ -26683,8 +26524,7 @@ "@azure/msal-react": { "version": "2.0.16", "resolved": "https://registry.npmjs.org/@azure/msal-react/-/msal-react-2.0.16.tgz", - "integrity": "sha512-fToFi/wG5e81Dir7Lw3/a2rZRRxvyD89ufI/GeVxcK3rblzJ6BT7fUoYnQsYTB6VR+z3MzRHpbJ3XgfESRk5ng==", - "requires": {} + "integrity": "sha512-fToFi/wG5e81Dir7Lw3/a2rZRRxvyD89ufI/GeVxcK3rblzJ6BT7fUoYnQsYTB6VR+z3MzRHpbJ3XgfESRk5ng==" }, "@babel/cli": { "version": "7.23.0", @@ -27152,8 +26992,7 @@ "@babel/plugin-proposal-private-property-in-object": { "version": "7.21.0-placeholder-for-preset-env.2", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", - "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", - "requires": {} + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==" }, "@babel/plugin-syntax-async-generators": { "version": "7.8.4", @@ -28286,14 +28125,12 @@ "@csstools/postcss-unset-value": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@csstools/postcss-unset-value/-/postcss-unset-value-1.0.2.tgz", - "integrity": "sha512-c8J4roPBILnelAsdLr4XOAR/GsTm0GJi4XpcfvoWk3U6KiTCqiFYc63KhRMQQX35jYMp4Ao8Ij9+IZRgMfJp1g==", - "requires": {} + "integrity": "sha512-c8J4roPBILnelAsdLr4XOAR/GsTm0GJi4XpcfvoWk3U6KiTCqiFYc63KhRMQQX35jYMp4Ao8Ij9+IZRgMfJp1g==" }, "@csstools/selector-specificity": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.2.0.tgz", - "integrity": "sha512-+OJ9konv95ClSTOJCmMZqpd5+YGsB2S+x6w3E1oaM8UuR5j8nTNHYSz8c9BEPGDOCMQYIEEGlVPj/VY64iTbGw==", - "requires": {} + "integrity": "sha512-+OJ9konv95ClSTOJCmMZqpd5+YGsB2S+x6w3E1oaM8UuR5j8nTNHYSz8c9BEPGDOCMQYIEEGlVPj/VY64iTbGw==" }, "@discoveryjs/json-ext": { "version": "0.5.7", @@ -28431,8 +28268,7 @@ "@emotion/use-insertion-effect-with-fallbacks": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz", - "integrity": "sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==", - "requires": {} + "integrity": "sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==" }, "@emotion/utils": { "version": "1.2.1", @@ -29333,12 +29169,6 @@ "integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==", "dev": true }, - "@kurkle/color": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz", - "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==", - "peer": true - }, "@leichtgewicht/ip-codec": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", @@ -29446,8 +29276,7 @@ "@mui/types": { "version": "7.2.12", "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.12.tgz", - "integrity": "sha512-3kaHiNm9khCAo0pVe0RenketDSFoZGAlVZ4zDjB/QNZV0XiCj+sh1zkX0VVhQPgYJDlBEzAag+MHJ1tU3vf0Zw==", - "requires": {} + "integrity": "sha512-3kaHiNm9khCAo0pVe0RenketDSFoZGAlVZ4zDjB/QNZV0XiCj+sh1zkX0VVhQPgYJDlBEzAag+MHJ1tU3vf0Zw==" }, "@mui/utils": { "version": "5.15.3", @@ -29920,8 +29749,7 @@ "@react-oauth/google": { "version": "0.12.1", "resolved": "https://registry.npmjs.org/@react-oauth/google/-/google-0.12.1.tgz", - "integrity": "sha512-qagsy22t+7UdkYAiT5ZhfM4StXi9PPNvw0zuwNmabrWyMKddczMtBIOARflbaIj+wHiQjnMAsZmzsUYuXeyoSg==", - "requires": {} + "integrity": "sha512-qagsy22t+7UdkYAiT5ZhfM4StXi9PPNvw0zuwNmabrWyMKddczMtBIOARflbaIj+wHiQjnMAsZmzsUYuXeyoSg==" }, "@restart/hooks": { "version": "0.4.11", @@ -29950,8 +29778,7 @@ "uncontrollable": { "version": "8.0.4", "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-8.0.4.tgz", - "integrity": "sha512-ulRWYWHvscPFc0QQXvyJjY6LIXU56f0h8pQFvhxiKk5V1fcI8gp9Ht9leVAhrVjzqMw0BgjspBINx9r6oyJUvQ==", - "requires": {} + "integrity": "sha512-ulRWYWHvscPFc0QQXvyJjY6LIXU56f0h8pQFvhxiKk5V1fcI8gp9Ht9leVAhrVjzqMw0BgjspBINx9r6oyJUvQ==" } } }, @@ -31355,8 +31182,7 @@ "version": "8.14.2", "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz", "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==", - "dev": true, - "requires": {} + "dev": true } } }, @@ -31684,8 +31510,7 @@ "version": "7.4.6", "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-7.4.6.tgz", "integrity": "sha512-DSq8l9FDocUF1ooVI+TF83pddj1LynE/Hv0/y8XZhc3IgJ/HkuOQuUmfz29ezgfAi9gFYUR8raTIBi3/xdoRmw==", - "dev": true, - "requires": {} + "dev": true }, "@storybook/react-webpack5": { "version": "7.4.6", @@ -31821,8 +31646,7 @@ "version": "14.5.1", "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.5.1.tgz", "integrity": "sha512-UCcUKrUYGj7ClomOo2SpNVvx4/fkd/2BbIHDCle8A0ax+P3bU7yJwDBDrS6ZwdTMARWTGODX1hEsCcO+7beJjg==", - "dev": true, - "requires": {} + "dev": true } } }, @@ -33340,14 +33164,12 @@ "acorn-import-assertions": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", - "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", - "requires": {} + "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==" }, "acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "requires": {} + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==" }, "acorn-walk": { "version": "7.2.0", @@ -33426,8 +33248,7 @@ "ajv-keywords": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "requires": {} + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==" }, "ansi-escapes": { "version": "4.3.2", @@ -33726,8 +33547,7 @@ "version": "7.0.0-bridge.0", "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz", "integrity": "sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==", - "dev": true, - "requires": {} + "dev": true }, "babel-jest": { "version": "27.5.1", @@ -33854,8 +33674,7 @@ "babel-plugin-named-asset-import": { "version": "0.3.8", "resolved": "https://registry.npmjs.org/babel-plugin-named-asset-import/-/babel-plugin-named-asset-import-0.3.8.tgz", - "integrity": "sha512-WXiAc++qo7XcJ1ZnTYGtLxmBCVbddAml3CEXgWaBzNzLNoxtQ8AiGEFDMOhot9XjTCQbvP5E77Fj9Gk924f00Q==", - "requires": {} + "integrity": "sha512-WXiAc++qo7XcJ1ZnTYGtLxmBCVbddAml3CEXgWaBzNzLNoxtQ8AiGEFDMOhot9XjTCQbvP5E77Fj9Gk924f00Q==" }, "babel-plugin-named-exports-order": { "version": "0.0.2", @@ -33983,7 +33802,8 @@ "base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true }, "batch": { "version": "0.6.1", @@ -34022,11 +33842,6 @@ "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==" }, - "bignumber.js": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", - "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==" - }, "binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -34114,8 +33929,7 @@ "bootstrap": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.2.tgz", - "integrity": "sha512-D32nmNWiQHo94BKHLmOrdjlL05q1c8oxbtBphQFb9Z5to6eGRDCm0QgeaZ4zFBHzfg2++rqa2JkqCcxDy0sH0g==", - "requires": {} + "integrity": "sha512-D32nmNWiQHo94BKHLmOrdjlL05q1c8oxbtBphQFb9Z5to6eGRDCm0QgeaZ4zFBHzfg2++rqa2JkqCcxDy0sH0g==" }, "bplist-parser": { "version": "0.2.0", @@ -34198,11 +34012,6 @@ "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", "dev": true }, - "buffer-equal-constant-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" - }, "buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -34325,15 +34134,6 @@ "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==" }, - "chart.js": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.2.tgz", - "integrity": "sha512-6GD7iKwFpP5kbSD4MeRRRlTnQvxfQREy36uEtm1hzHzcOqwWx0YEHuspuoNlslu+nciLIB7fjjsHkUv/FzFcOg==", - "peer": true, - "requires": { - "@kurkle/color": "^0.3.0" - } - }, "check-types": { "version": "11.2.3", "resolved": "https://registry.npmjs.org/check-types/-/check-types-11.2.3.tgz", @@ -34767,8 +34567,7 @@ "css-declaration-sorter": { "version": "6.4.1", "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.4.1.tgz", - "integrity": "sha512-rtdthzxKuyq6IzqX6jEcIzQF/YqccluefyCYheovBOLhFT/drQA9zj/UbRAa9J7C0o6EG6u3E6g+vKkay7/k3g==", - "requires": {} + "integrity": "sha512-rtdthzxKuyq6IzqX6jEcIzQF/YqccluefyCYheovBOLhFT/drQA9zj/UbRAa9J7C0o6EG6u3E6g+vKkay7/k3g==" }, "css-has-pseudo": { "version": "3.0.4", @@ -34851,8 +34650,7 @@ "css-prefers-color-scheme": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-6.0.3.tgz", - "integrity": "sha512-4BqMbZksRkJQx2zAjrokiGMd07RqOa2IxIrrN10lyBe9xhn9DEvjUK79J6jkeiv9D9hQFXKb6g1jwU62jziJZA==", - "requires": {} + "integrity": "sha512-4BqMbZksRkJQx2zAjrokiGMd07RqOa2IxIrrN10lyBe9xhn9DEvjUK79J6jkeiv9D9hQFXKb6g1jwU62jziJZA==" }, "css-select": { "version": "4.3.0", @@ -34967,8 +34765,7 @@ "cssnano-utils": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-3.1.0.tgz", - "integrity": "sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==", - "requires": {} + "integrity": "sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==" }, "csso": { "version": "4.2.0", @@ -35469,14 +35266,6 @@ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "dev": true }, - "ecdsa-sig-formatter": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", - "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", - "requires": { - "safe-buffer": "^5.0.1" - } - }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -36096,8 +35885,7 @@ "eslint-plugin-react-hooks": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", - "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", - "requires": {} + "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==" }, "eslint-plugin-storybook": { "version": "0.6.14", @@ -36386,7 +36174,8 @@ "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true }, "extract-zip": { "version": "1.7.0", @@ -36930,46 +36719,6 @@ "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==" }, - "gaxios": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.6.0.tgz", - "integrity": "sha512-bpOZVQV5gthH/jVCSuYuokRo2bTKOcuBiVWpjmTn6C5Agl5zclGfTljuGsQZxwwDBkli+YhZhP4TdlqTnhOezQ==", - "requires": { - "extend": "^3.0.2", - "https-proxy-agent": "^7.0.1", - "is-stream": "^2.0.0", - "node-fetch": "^2.6.9", - "uuid": "^9.0.1" - }, - "dependencies": { - "agent-base": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", - "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", - "requires": { - "debug": "^4.3.4" - } - }, - "https-proxy-agent": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", - "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", - "requires": { - "agent-base": "^7.0.2", - "debug": "4" - } - } - } - }, - "gcp-metadata": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.0.tgz", - "integrity": "sha512-Jh/AIwwgaxan+7ZUUmRLCjtchyDiqh4KjBJ5tW3plBZb5iL/BPcso8A5DlzeD9qlw0duCamnNdpFjxwaT0KyKg==", - "requires": { - "gaxios": "^6.0.0", - "json-bigint": "^1.0.0" - } - }, "gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -37155,19 +36904,6 @@ "slash": "^3.0.0" } }, - "google-auth-library": { - "version": "9.10.0", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.10.0.tgz", - "integrity": "sha512-ol+oSa5NbcGdDqA+gZ3G3mev59OHBZksBTxY/tYwjtcp1H/scAFwJfSQU9/1RALoyZ7FslNbke8j4i3ipwlyuQ==", - "requires": { - "base64-js": "^1.3.0", - "ecdsa-sig-formatter": "^1.0.11", - "gaxios": "^6.1.1", - "gcp-metadata": "^6.1.0", - "gtoken": "^7.0.0", - "jws": "^4.0.0" - } - }, "gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -37186,15 +36922,6 @@ "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==" }, - "gtoken": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz", - "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==", - "requires": { - "gaxios": "^6.0.0", - "jws": "^4.0.0" - } - }, "gunzip-maybe": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/gunzip-maybe/-/gunzip-maybe-1.4.2.tgz", @@ -37307,8 +37034,7 @@ "highcharts-react-official": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/highcharts-react-official/-/highcharts-react-official-3.2.1.tgz", - "integrity": "sha512-hyQTX7ezCxl7JqumaWiGsroGWalzh24GedQIgO3vJbkGOZ6ySRAltIYjfxhrq4HszJOySZegotEF7v+haQ75UA==", - "requires": {} + "integrity": "sha512-hyQTX7ezCxl7JqumaWiGsroGWalzh24GedQIgO3vJbkGOZ6ySRAltIYjfxhrq4HszJOySZegotEF7v+haQ75UA==" }, "history": { "version": "4.10.1", @@ -37534,8 +37260,7 @@ "icss-utils": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", - "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", - "requires": {} + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==" }, "idb": { "version": "7.1.1", @@ -38757,8 +38482,7 @@ "jest-pnp-resolver": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", - "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", - "requires": {} + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==" }, "jest-regex-util": { "version": "27.5.1", @@ -39691,14 +39415,6 @@ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" }, - "json-bigint": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", - "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", - "requires": { - "bignumber.js": "^9.0.0" - } - }, "json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -39771,30 +39487,6 @@ "object.values": "^1.1.6" } }, - "jwa": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", - "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", - "requires": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "jws": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", - "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", - "requires": { - "jwa": "^2.0.0", - "safe-buffer": "^5.0.1" - } - }, - "jwt-decode": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", - "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==" - }, "keyv": { "version": "4.5.3", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.3.tgz", @@ -40084,8 +39776,7 @@ "version": "7.3.2", "resolved": "https://registry.npmjs.org/markdown-to-jsx/-/markdown-to-jsx-7.3.2.tgz", "integrity": "sha512-B+28F5ucp83aQm+OxNrPkS8z0tMKaeHiy0lHJs3LqCyDQFtWuenaIrkaVTgAm1pf1AU85LXltva86hlaT17i8Q==", - "dev": true, - "requires": {} + "dev": true }, "mdast-util-definitions": { "version": "4.0.0", @@ -40385,6 +40076,7 @@ "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, "requires": { "whatwg-url": "^5.0.0" }, @@ -40392,17 +40084,20 @@ "tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true }, "webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true }, "whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, "requires": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" @@ -41045,8 +40740,7 @@ "postcss-browser-comments": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/postcss-browser-comments/-/postcss-browser-comments-4.0.0.tgz", - "integrity": "sha512-X9X9/WN3KIvY9+hNERUqX9gncsgBA25XaeR+jshHz2j8+sYyHktHw1JdKuMjeLpGktXidqDhA7b/qm1mrBDmgg==", - "requires": {} + "integrity": "sha512-X9X9/WN3KIvY9+hNERUqX9gncsgBA25XaeR+jshHz2j8+sYyHktHw1JdKuMjeLpGktXidqDhA7b/qm1mrBDmgg==" }, "postcss-calc": { "version": "8.2.4", @@ -41144,26 +40838,22 @@ "postcss-discard-comments": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.1.2.tgz", - "integrity": "sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==", - "requires": {} + "integrity": "sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==" }, "postcss-discard-duplicates": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz", - "integrity": "sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==", - "requires": {} + "integrity": "sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==" }, "postcss-discard-empty": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz", - "integrity": "sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==", - "requires": {} + "integrity": "sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==" }, "postcss-discard-overridden": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz", - "integrity": "sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==", - "requires": {} + "integrity": "sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==" }, "postcss-double-position-gradients": { "version": "3.1.2", @@ -41185,8 +40875,7 @@ "postcss-flexbugs-fixes": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/postcss-flexbugs-fixes/-/postcss-flexbugs-fixes-5.0.2.tgz", - "integrity": "sha512-18f9voByak7bTktR2QgDveglpn9DTbBWPUzSOe9g0N4WR/2eSt6Vrcbf0hmspvMI6YWGywz6B9f7jzpFNJJgnQ==", - "requires": {} + "integrity": "sha512-18f9voByak7bTktR2QgDveglpn9DTbBWPUzSOe9g0N4WR/2eSt6Vrcbf0hmspvMI6YWGywz6B9f7jzpFNJJgnQ==" }, "postcss-focus-visible": { "version": "6.0.4", @@ -41207,14 +40896,12 @@ "postcss-font-variant": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz", - "integrity": "sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==", - "requires": {} + "integrity": "sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==" }, "postcss-gap-properties": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-3.0.5.tgz", - "integrity": "sha512-IuE6gKSdoUNcvkGIqdtjtcMtZIFyXZhmFd5RUlg97iVEvp1BZKV5ngsAjCjrVy+14uhGBQl9tzmi1Qwq4kqVOg==", - "requires": {} + "integrity": "sha512-IuE6gKSdoUNcvkGIqdtjtcMtZIFyXZhmFd5RUlg97iVEvp1BZKV5ngsAjCjrVy+14uhGBQl9tzmi1Qwq4kqVOg==" }, "postcss-image-set-function": { "version": "4.0.7", @@ -41237,8 +40924,7 @@ "postcss-initial": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/postcss-initial/-/postcss-initial-4.0.1.tgz", - "integrity": "sha512-0ueD7rPqX8Pn1xJIjay0AZeIuDoF+V+VvMt/uOnn+4ezUKhZM/NokDeP6DwMNyIoYByuN/94IQnt5FEkaN59xQ==", - "requires": {} + "integrity": "sha512-0ueD7rPqX8Pn1xJIjay0AZeIuDoF+V+VvMt/uOnn+4ezUKhZM/NokDeP6DwMNyIoYByuN/94IQnt5FEkaN59xQ==" }, "postcss-js": { "version": "4.0.1", @@ -41286,14 +40972,12 @@ "postcss-logical": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-5.0.4.tgz", - "integrity": "sha512-RHXxplCeLh9VjinvMrZONq7im4wjWGlRJAqmAVLXyZaXwfDWP73/oq4NdIp+OZwhQUMj0zjqDfM5Fj7qby+B4g==", - "requires": {} + "integrity": "sha512-RHXxplCeLh9VjinvMrZONq7im4wjWGlRJAqmAVLXyZaXwfDWP73/oq4NdIp+OZwhQUMj0zjqDfM5Fj7qby+B4g==" }, "postcss-media-minmax": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/postcss-media-minmax/-/postcss-media-minmax-5.0.0.tgz", - "integrity": "sha512-yDUvFf9QdFZTuCUg0g0uNSHVlJ5X1lSzDZjPSFaiCWvjgsvu8vEVxtahPrLMinIDEEGnx6cBe6iqdx5YWz08wQ==", - "requires": {} + "integrity": "sha512-yDUvFf9QdFZTuCUg0g0uNSHVlJ5X1lSzDZjPSFaiCWvjgsvu8vEVxtahPrLMinIDEEGnx6cBe6iqdx5YWz08wQ==" }, "postcss-merge-longhand": { "version": "5.1.7", @@ -41354,8 +41038,7 @@ "postcss-modules-extract-imports": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", - "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", - "requires": {} + "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==" }, "postcss-modules-local-by-default": { "version": "4.0.3", @@ -41413,8 +41096,7 @@ "postcss-normalize-charset": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz", - "integrity": "sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==", - "requires": {} + "integrity": "sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==" }, "postcss-normalize-display-values": { "version": "5.1.0", @@ -41485,8 +41167,7 @@ "postcss-opacity-percentage": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/postcss-opacity-percentage/-/postcss-opacity-percentage-1.1.3.tgz", - "integrity": "sha512-An6Ba4pHBiDtyVpSLymUUERMo2cU7s+Obz6BTrS+gxkbnSBNKSuD0AVUc+CpBMrpVPKKfoVz0WQCX+Tnst0i4A==", - "requires": {} + "integrity": "sha512-An6Ba4pHBiDtyVpSLymUUERMo2cU7s+Obz6BTrS+gxkbnSBNKSuD0AVUc+CpBMrpVPKKfoVz0WQCX+Tnst0i4A==" }, "postcss-ordered-values": { "version": "5.1.3", @@ -41508,8 +41189,7 @@ "postcss-page-break": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-3.0.4.tgz", - "integrity": "sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==", - "requires": {} + "integrity": "sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==" }, "postcss-place": { "version": "7.0.5", @@ -41603,8 +41283,7 @@ "postcss-replace-overflow-wrap": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz", - "integrity": "sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==", - "requires": {} + "integrity": "sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==" }, "postcss-selector-not": { "version": "6.0.1", @@ -42068,15 +41747,13 @@ "react-chartjs-2": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-5.2.0.tgz", - "integrity": "sha512-98iN5aguJyVSxp5U3CblRLH67J8gkfyGNbiK3c+l1QI/G4irHMPQw44aEPmjVag+YKTyQ260NcF82GTQ3bdscA==", - "requires": {} + "integrity": "sha512-98iN5aguJyVSxp5U3CblRLH67J8gkfyGNbiK3c+l1QI/G4irHMPQw44aEPmjVag+YKTyQ260NcF82GTQ3bdscA==" }, "react-colorful": { "version": "5.6.1", "resolved": "https://registry.npmjs.org/react-colorful/-/react-colorful-5.6.1.tgz", "integrity": "sha512-1exovf0uGTGyq5mXQT0zgQ80uvj2PCwvF8zY1RN9/vbJVSjSo3fsB/4L3ObbF7u70NduSiK4xu4Y6q1MHoUGEw==", - "dev": true, - "requires": {} + "dev": true }, "react-confetti": { "version": "6.1.0", @@ -42203,8 +41880,7 @@ "version": "2.2.2", "resolved": "https://registry.npmjs.org/react-docgen-typescript/-/react-docgen-typescript-2.2.2.tgz", "integrity": "sha512-tvg2ZtOpOi6QDwsb3GZhOjDkkX0h8Z2gipvTg6OVMUyoYoURhEiRNePT8NZItTVCDh39JJHnLdfCOkzoLbFnTg==", - "dev": true, - "requires": {} + "dev": true }, "react-dom": { "version": "18.2.0", @@ -42247,15 +41923,13 @@ "react-icons": { "version": "4.11.0", "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.11.0.tgz", - "integrity": "sha512-V+4khzYcE5EBk/BvcuYRq6V/osf11ODUM2J8hg2FDSswRrGvqiYUYPRy4OdrWaQOBj4NcpJfmHZLNaD+VH0TyA==", - "requires": {} + "integrity": "sha512-V+4khzYcE5EBk/BvcuYRq6V/osf11ODUM2J8hg2FDSswRrGvqiYUYPRy4OdrWaQOBj4NcpJfmHZLNaD+VH0TyA==" }, "react-inspector": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/react-inspector/-/react-inspector-6.0.2.tgz", "integrity": "sha512-x+b7LxhmHXjHoU/VrFAzw5iutsILRoYyDq97EDYdFpPLcvqtEzk4ZSZSQjnFPbr5T57tLXnHcqFYoN1pI6u8uQ==", - "dev": true, - "requires": {} + "dev": true }, "react-is": { "version": "17.0.2", @@ -43643,8 +43317,7 @@ "style-loader": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.3.tgz", - "integrity": "sha512-53BiGLXAcll9maCYtZi2RCQZKa8NQQai5C4horqKyRmHj9H7QmcUyucrH+4KW/gBQbXM2AsB0axoEcFZPlfPcw==", - "requires": {} + "integrity": "sha512-53BiGLXAcll9maCYtZi2RCQZKa8NQQai5C4horqKyRmHj9H7QmcUyucrH+4KW/gBQbXM2AsB0axoEcFZPlfPcw==" }, "styled-components": { "version": "6.0.8", @@ -43871,8 +43544,7 @@ "version": "0.2.3", "resolved": "https://registry.npmjs.org/swc-loader/-/swc-loader-0.2.3.tgz", "integrity": "sha512-D1p6XXURfSPleZZA/Lipb3A8pZ17fP4NObZvFCDjK/OKljroqDpPmsBdTraWhVBqUNpcWBQY1imWdoPScRlQ7A==", - "dev": true, - "requires": {} + "dev": true }, "symbol-tree": { "version": "3.2.4", @@ -44383,12 +44055,6 @@ "is-typedarray": "^1.0.0" } }, - "typescript": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", - "peer": true - }, "uglify-js": { "version": "3.17.4", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", @@ -44585,8 +44251,7 @@ "use-isomorphic-layout-effect": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz", - "integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==", - "requires": {} + "integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==" }, "use-resize-observer": { "version": "9.1.0", @@ -44927,8 +44592,7 @@ "ws": { "version": "8.14.2", "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz", - "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==", - "requires": {} + "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==" } } }, @@ -44936,7 +44600,7 @@ "version": "2.25.4", "resolved": "https://registry.npmjs.org/webpack-hot-middleware/-/webpack-hot-middleware-2.25.4.tgz", "integrity": "sha512-IRmTspuHM06aZh98OhBJtqLpeWFM8FXJS5UYpKYxCJzyFoyWj1w6VGFfomZU7OPA55dMLrQK0pRT1eQ3PACr4w==", - "devOptional": true, + "dev": true, "requires": { "ansi-html-community": "0.0.8", "html-entities": "^2.1.0", @@ -45464,8 +45128,7 @@ "ws": { "version": "7.5.9", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", - "requires": {} + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==" }, "xml-name-validator": { "version": "3.0.0", diff --git a/nesis/frontend/client/src/SessionContext.js b/nesis/frontend/client/src/SessionContext.js index 612c986..2425ae1 100644 --- a/nesis/frontend/client/src/SessionContext.js +++ b/nesis/frontend/client/src/SessionContext.js @@ -82,7 +82,7 @@ async function logoutMicrosoft(config) { clientId: config?.auth?.OAUTH_AZURE_CLIENT_ID, authority: config?.auth?.OAUTH_AZURE_AUTHORITY, redirectUri: config?.auth?.OAUTH_AZURE_REDIRECTURI, - postLogoutRedirectUri: 'http://localhost:3000/', + postLogoutRedirectUri: config?.auth?.OAUTH_AZURE_REDIRECTURI, }, }); await msalInstance.initialize(); diff --git a/nesis/frontend/client/src/components/inputs/Checkbox.js b/nesis/frontend/client/src/components/inputs/Checkbox.js index acf09ae..e31277e 100644 --- a/nesis/frontend/client/src/components/inputs/Checkbox.js +++ b/nesis/frontend/client/src/components/inputs/Checkbox.js @@ -4,6 +4,7 @@ import styled from 'styled-components/macro'; const Wrapper = styled.div` display: flex; align-items: center; + margin-top: 8px; `; const StyledInput = styled.input` diff --git a/nesis/frontend/client/src/pages/Settings/Apps/AppDetailPage.js b/nesis/frontend/client/src/pages/Settings/Apps/AppDetailPage.js new file mode 100644 index 0000000..35c8d35 --- /dev/null +++ b/nesis/frontend/client/src/pages/Settings/Apps/AppDetailPage.js @@ -0,0 +1,187 @@ +import React from 'react'; +import { useHistory, useRouteMatch, useLocation } from 'react-router-dom'; +import useClient from '../../../utils/useClient'; +import parseApiErrorMessage from '../../../utils/parseApiErrorMessage'; +import { Formik, Form as FormikForm, FieldArray } from 'formik'; +import { Checkbox, TextField } from '../../../components/form'; +import { Field } from 'formik'; +import { required } from '../../../components/form/validators'; +import FormControls from '../../../components/FormControls'; +import { useAddToast } from '../../../ToasterContext'; +import { ModalTitle } from '../../../components/Modal'; +import MessageRow from '../../../components/MessageRow'; +import Table from '../../../components/Table'; +import styled from 'styled-components/macro'; + +const StyledTable = styled(Table)``; + +export default function AppDetailsPage({ roles, appRoles, onSuccess }) { + const match = useRouteMatch(); + const [errorMessage, setErrorMessage] = React.useState(''); + const [secret, setSecret] = React.useState(''); + + return ( + <> + {errorMessage && {errorMessage}} + {secret && ( +
+ + Safely save this app secret, it will not be shown again: + {' '} + {secret} +
+ )} + + + ); +} + +function CreateApp({ onSuccess, roles, appRoles, onError }) { + const location = useLocation(); + + return ( + <> + {location?.state?.id ? `Edit` : `Create`} app + + + ); +} + +function AppForm({ + app, + roles, + onSuccess, + appRoles, + onError, + submitButtonText = 'Submit', + initialValues = { + id: app?.id, + name: app?.name, + roles: appRoles?.items?.map((role) => role.id), + }, +}) { + const history = useHistory(); + const client = useClient(); + const addToast = useAddToast(); + const location = useLocation(); + + function handleSubmit(values, actions) { + client + .post(`apps`, values) + .then((res, err) => { + console.log(JSON.stringify(res.body)); + values.secret = res.body.secret; + onSuccess(res.body.secret); + values.id = res.body.id; + actions.setSubmitting(false); + actions.resetForm(); + // onSuccess(); + addToast({ + title: `App Created`, + content: 'Operation is successful', + }); + }) + .catch((error) => { + actions.setSubmitting(false); + onError(parseApiErrorMessage(error)); + }); + } + + initialValues.id = location?.state?.id; + initialValues.name = location?.state?.name; + initialValues.description = location?.state?.description; + initialValues.enabled = location?.state?.enabled; + + return ( +
+ + {({ isSubmitting, resetForm, values }) => ( + + + + + + + + Role + Attached + + + + {(roles || []).map((role, index) => ( + + {role.name} + + + + + ))} + + + + { + resetForm(); + history.push('/settings/apps'); + }} + /> + + )} + +
+ ); +} diff --git a/nesis/frontend/client/src/pages/Settings/Apps/AppsPage.js b/nesis/frontend/client/src/pages/Settings/Apps/AppsPage.js new file mode 100644 index 0000000..193ecfc --- /dev/null +++ b/nesis/frontend/client/src/pages/Settings/Apps/AppsPage.js @@ -0,0 +1,363 @@ +import React from 'react'; +import { + LightSquareButton, + OutlinedSquareButton, + EditOutlinedSquareButton, +} from '../../../components/inputs/SquareButton'; +import styled from 'styled-components/macro'; +import { device } from '../../../utils/breakpoints'; +import Table, { DeleteItemButton } from '../../../components/Table'; +import { useHistory, useRouteMatch, useLocation } from 'react-router-dom'; +import { getSortFunction } from '../../../utils/paginationUtils'; +import { filterItems } from '../../../utils/filterUtils'; +import { usePagination } from '../../../components/PaginationRow'; +import SearchInput from '../../../components/table/SearchInput'; +import Modal from '../../../components/Modal'; +import ResponsiveAddButton from '../../../components/layout/ResponsiveAddButton'; +import client from '../../../utils/httpClient'; +import useConfirmationModal from '../../../components/useConfirmationModal'; +import useApiGet from '../../../utils/useApiGet'; +import { useAddToast } from '../../../ToasterContext'; +import { ReactComponent as RefreshIcon } from '../../../images/RefreshIcon.svg'; +import MessageRow from '../../../components/MessageRow'; +import { PlusSquare } from 'react-bootstrap-icons'; +import StatusIcon from '../../../components/StatusIcon'; +import { + MobileList, + EditButton, + DeleteButton, +} from '../../../components/layout/MobileListItem'; +import MobileListItem from '../../../components/layout/MobileListItem'; +import Spinner from '../../../components/Spinner'; +import AttributeField from '../../../components/layout/AttributeField'; +import { ReactComponent as BinIcon } from '../../../images/BinIcon.svg'; +import AppDetailPage from './AppDetailPage'; + +const PageHeader = styled.div` + display: flex; + justify-content: end; + flex-direction: row; + padding-bottom: 3px; +`; + +const StyledTable = styled(Table)` + & th { + background: #f1f1f1; + color: ${(props) => props.theme.black}; + } + + @media ${device.tablet} { + display: none; + } +`; + +const RefreshButton = styled(OutlinedSquareButton)` + min-width: auto; + & > svg { + margin-right: 4px; + @media ${device.tablet} { + margin-right: 0; + } + & > path { + fill: #089fdf; + } + } +`; + +const RefreshText = styled.span` + display: inline; + @media ${device.tablet} { + display: none; + } +`; + +const AppsMobileHeader = styled.span` + margin: 8px 0; + display: flex; + flex-direction: row; + align-items: center; + + & > div { + margin-left: 10px; + display: flex; + flex-direction: column; + } +`; + +const AppsIndex = styled.div` + width: 26px; + height: 26px; + border-radius: 50%; + font-weight: 600; + color: ${(props) => props.theme.white}; + background: #089fdf; + display: flex; + align-items: center; + justify-content: center; + margin-right: 12px; +`; + +const AppsTitle = styled.span` + font-weight: 600; + line-height: 160%; +`; + +const Page = () => { + const [appsResponse, appsLoading, appsErrors, appsActions] = + useApiGet('apps'); + + const [rolesResponse, rolesLoading, rolesErrors, rolesActions] = + useApiGet('roles'); + + const match = useRouteMatch(); + const isNew = match.url?.endsWith('/apps/new'); + const isEdit = match.url?.endsWith('/edit'); + const history = useHistory(); + const [searchText, setSearchText] = React.useState(); + const [currentSort, setCurrentSort] = React.useState(null); + + const sortFn = getSortFunction(sortConfig, currentSort, sortByName); + + const filteredItems = React.useMemo(() => { + const matchedData = appsResponse?.items; + const items = filterItems( + matchedData || [], + {}, + (data) => + !searchText || + data.name?.toLowerCase().includes(searchText.toLocaleLowerCase()) || + data.description + ?.toLowerCase() + .includes(searchText.toLocaleLowerCase()), + ); + items.sort(sortFn); + return items; + }, [appsResponse, searchText, sortFn]); + + const [paginatedApps, paging] = usePagination(filteredItems, 20); + + const addToast = useAddToast(); + + const [confirmModal, showConfirmModal, setCurrentItem] = useConfirmationModal( + async (id) => { + await client.delete(`apps/${id}`); + appsActions.repeat(); + addToast({ + title: `App deleted`, + content: 'Operation is successful', + }); + }, + ); + + return ( + <> + {confirmModal} + { + setSearchText(''); + appsActions.repeat(); + }} + /> + { + setSearchText(''); + appsActions.repeat(); + history.replace('/settings/apps'); + }} + /> +
+ +
+ appsActions.repeat()} + style={{ marginRight: 12 }} + > + + Refresh + + history.push(`/settings/apps/new`)} + > + + New + +
+
+ +
+ {/* + {AppsErrors} + */} + + {appsLoading && } + {paginatedApps.map((app, index) => ( + + {index + 1}{' '} +
+ {app.enabled} + {app.name} +
+ + } + expandContent={ +
+ + history.push({ + pathname: `/settings/apps/${app.id}/edit`, + state: app, + }) + } + > + Edit + + { + setCurrentItem(app.id); + showConfirmModal(); + }} + > + Delete + +
+ } + /> + ))} +
+ + {!appsLoading && ( + + {!paginatedApps.length && ( + + + + No apps found + + + + )} + {paginatedApps.map((app) => ( + + {app.name} + {app.description} + {app?.create_date} + + + history.push({ + pathname: `/settings/apps/${app.id}/edit`, + state: app, + }) + } + > + Edit + + { + setCurrentItem(app.id); + showConfirmModal(); + }} + /> + + + ))} + + )} + +
+ {paging} +
+ + ); +}; + +function CreateModal({ visible, roles, onSuccess }) { + const history = useHistory(); + + function closeModal() { + history.push('/settings/apps'); + } + + return ( + + + + ); +} + +function EditModal({ visible, roles, onSuccess }) { + const history = useHistory(); + const location = useLocation(); + + function closeModal() { + history.push('/settings/apps'); + } + + const [appRolesResponse, roleLoading, roleErrors, roleActions] = useApiGet( + `apps/${location?.state?.id}/roles`, + ); + + return ( + + + + ); +} + +const sortByName = (r1, r2) => + r1.name !== null && r2.name !== null && r2.name.localeCompare(r1.name); +const sortByCreateDate = (r1, r2) => + r1.create_date && + r2.create_date && + r2.create_date.localeCompare(r1.create_date); + +const sortConfig = { + name: sortByName, + create_date: sortByCreateDate, +}; + +export default Page; diff --git a/nesis/frontend/client/src/pages/Settings/SettingPage.js b/nesis/frontend/client/src/pages/Settings/SettingPage.js index bc81148..778cf68 100644 --- a/nesis/frontend/client/src/pages/Settings/SettingPage.js +++ b/nesis/frontend/client/src/pages/Settings/SettingPage.js @@ -5,6 +5,7 @@ import { GearFill } from 'react-bootstrap-icons'; import DatasourcesPage from './Datasources/DatasourcesPage'; import UsersPage from './Users/UsersPage'; import RolesPage from './Roles/RolesPage'; +import AppsPage from './Apps/AppsPage'; import Nesis from '../../components/Menu'; import styled from 'styled-components/macro'; @@ -34,6 +35,7 @@ const SettingsPage = () => { Datasources Users Roles + Apps
@@ -59,6 +61,7 @@ const SettingsPage = () => { component={UsersPage} /> + {/* Roles settings */} { component={RolesPage} /> + {/* Apps settings */} + + +
diff --git a/nesis/frontend/client/src/pages/Settings/Users/UserDetailPage.js b/nesis/frontend/client/src/pages/Settings/Users/UserDetailPage.js index 1bde291..5d2bd5a 100644 --- a/nesis/frontend/client/src/pages/Settings/Users/UserDetailPage.js +++ b/nesis/frontend/client/src/pages/Settings/Users/UserDetailPage.js @@ -147,6 +147,7 @@ function DocumentForm({ - + diff --git a/nesis/frontend/package-lock.json b/nesis/frontend/package-lock.json index cbb1b00..5337fa4 100644 --- a/nesis/frontend/package-lock.json +++ b/nesis/frontend/package-lock.json @@ -17,7 +17,6 @@ "express": "^4.18.2", "google-auth-library": "^9.10.0", "jsonwebtoken": "^9.0.2", - "mindsdb-js-sdk": "^2.2.2", "superagent": "^8.1.2", "winston": "^3.10.0" }, @@ -630,17 +629,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, - "node_modules/agentkeepalive": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz", - "integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==", - "dependencies": { - "humanize-ms": "^1.2.1" - }, - "engines": { - "node": ">= 8.0.0" - } - }, "node_modules/aggregate-error": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", @@ -761,16 +749,6 @@ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, - "node_modules/axios": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.5.1.tgz", - "integrity": "sha512-Q28iYCWzNHjAm+yEAot5QaAMxhMghWLFVf7rRdwhUI+c2jix2DUXjAHXVi+s1ibs3mjPO/cCgbA++3BjD0vP/A==", - "dependencies": { - "follow-redirects": "^1.15.0", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -1188,11 +1166,6 @@ "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==" }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" - }, "node_modules/cors": { "version": "2.8.5", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", @@ -1560,25 +1533,6 @@ "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" }, - "node_modules/follow-redirects": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", - "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, "node_modules/foreground-child": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", @@ -2008,14 +1962,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, - "node_modules/humanize-ms": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", - "dependencies": { - "ms": "^2.0.0" - } - }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -2171,11 +2117,6 @@ "node": ">=0.10.0" } }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" - }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -2326,7 +2267,8 @@ "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true }, "node_modules/js-yaml": { "version": "3.14.1", @@ -2571,18 +2513,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "peer": true, - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, "node_modules/loupe": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", @@ -2678,16 +2608,6 @@ "node": ">= 0.6" } }, - "node_modules/mindsdb-js-sdk": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/mindsdb-js-sdk/-/mindsdb-js-sdk-2.2.2.tgz", - "integrity": "sha512-Bx12YwGrbJeGy46QFukTnb/KMcikFPS7pxitd/DVEu9mgUOcmcdKSKzVQjRtVNxq6cCx4QiNNTiA2KilQNIsPQ==", - "dependencies": { - "agentkeepalive": "^4.2.1", - "axios": "^1.3.2", - "mysql": "^2.18.1" - } - }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -3039,47 +2959,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, - "node_modules/mysql": { - "version": "2.18.1", - "resolved": "https://registry.npmjs.org/mysql/-/mysql-2.18.1.tgz", - "integrity": "sha512-Bca+gk2YWmqp2Uf6k5NFEurwY/0td0cpebAucFpY/3jhrwrVGuxU2uQFCHjU19SJfje0yQvi+rVWdq78hR5lig==", - "dependencies": { - "bignumber.js": "9.0.0", - "readable-stream": "2.3.7", - "safe-buffer": "5.1.2", - "sqlstring": "2.3.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mysql/node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/mysql/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "node_modules/mysql/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, "node_modules/nanoid": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", @@ -3442,11 +3321,6 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" - }, "node_modules/process-on-spawn": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", @@ -3471,11 +3345,6 @@ "node": ">= 0.10" } }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" - }, "node_modules/qs": { "version": "6.11.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", @@ -3521,18 +3390,6 @@ "node": ">= 0.8" } }, - "node_modules/react": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", - "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", - "peer": true, - "dependencies": { - "loose-envify": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -3815,14 +3672,6 @@ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true }, - "node_modules/sqlstring": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz", - "integrity": "sha512-ooAzh/7dxIG5+uDik1z/Rd1vli0+38izZhGzSa34FwR7IbelPWCCKSNIl8jlL/F7ERvy8CB2jNeM1E9i9mXMAQ==", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/stack-trace": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", @@ -4390,8 +4239,7 @@ "@azure/msal-react": { "version": "2.0.16", "resolved": "https://registry.npmjs.org/@azure/msal-react/-/msal-react-2.0.16.tgz", - "integrity": "sha512-fToFi/wG5e81Dir7Lw3/a2rZRRxvyD89ufI/GeVxcK3rblzJ6BT7fUoYnQsYTB6VR+z3MzRHpbJ3XgfESRk5ng==", - "requires": {} + "integrity": "sha512-fToFi/wG5e81Dir7Lw3/a2rZRRxvyD89ufI/GeVxcK3rblzJ6BT7fUoYnQsYTB6VR+z3MzRHpbJ3XgfESRk5ng==" }, "@babel/code-frame": { "version": "7.23.5", @@ -4833,14 +4681,6 @@ } } }, - "agentkeepalive": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz", - "integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==", - "requires": { - "humanize-ms": "^1.2.1" - } - }, "aggregate-error": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", @@ -4937,16 +4777,6 @@ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, - "axios": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.5.1.tgz", - "integrity": "sha512-Q28iYCWzNHjAm+yEAot5QaAMxhMghWLFVf7rRdwhUI+c2jix2DUXjAHXVi+s1ibs3mjPO/cCgbA++3BjD0vP/A==", - "requires": { - "follow-redirects": "^1.15.0", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -5252,11 +5082,6 @@ "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==" }, - "core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" - }, "cors": { "version": "2.8.5", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", @@ -5548,11 +5373,6 @@ "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" }, - "follow-redirects": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", - "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==" - }, "foreground-child": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", @@ -5864,14 +5684,6 @@ } } }, - "humanize-ms": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", - "requires": { - "ms": "^2.0.0" - } - }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -5982,11 +5794,6 @@ "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", "dev": true }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" - }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -6105,7 +5912,8 @@ "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true }, "js-yaml": { "version": "3.14.1", @@ -6313,15 +6121,6 @@ } } }, - "loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "peer": true, - "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" - } - }, "loupe": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", @@ -6389,16 +6188,6 @@ "mime-db": "1.52.0" } }, - "mindsdb-js-sdk": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/mindsdb-js-sdk/-/mindsdb-js-sdk-2.2.2.tgz", - "integrity": "sha512-Bx12YwGrbJeGy46QFukTnb/KMcikFPS7pxitd/DVEu9mgUOcmcdKSKzVQjRtVNxq6cCx4QiNNTiA2KilQNIsPQ==", - "requires": { - "agentkeepalive": "^4.2.1", - "axios": "^1.3.2", - "mysql": "^2.18.1" - } - }, "minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -6658,46 +6447,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, - "mysql": { - "version": "2.18.1", - "resolved": "https://registry.npmjs.org/mysql/-/mysql-2.18.1.tgz", - "integrity": "sha512-Bca+gk2YWmqp2Uf6k5NFEurwY/0td0cpebAucFpY/3jhrwrVGuxU2uQFCHjU19SJfje0yQvi+rVWdq78hR5lig==", - "requires": { - "bignumber.js": "9.0.0", - "readable-stream": "2.3.7", - "safe-buffer": "5.1.2", - "sqlstring": "2.3.1" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, "nanoid": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", @@ -6972,11 +6721,6 @@ "integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==", "dev": true }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" - }, "process-on-spawn": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", @@ -6995,11 +6739,6 @@ "ipaddr.js": "1.9.1" } }, - "proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" - }, "qs": { "version": "6.11.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", @@ -7033,15 +6772,6 @@ "unpipe": "1.0.0" } }, - "react": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", - "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", - "peer": true, - "requires": { - "loose-envify": "^1.1.0" - } - }, "readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -7257,11 +6987,6 @@ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true }, - "sqlstring": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz", - "integrity": "sha512-ooAzh/7dxIG5+uDik1z/Rd1vli0+38izZhGzSa34FwR7IbelPWCCKSNIl8jlL/F7ERvy8CB2jNeM1E9i9mXMAQ==" - }, "stack-trace": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", diff --git a/nesis/frontend/server/api/apps.js b/nesis/frontend/server/api/apps.js new file mode 100644 index 0000000..ed9be0a --- /dev/null +++ b/nesis/frontend/server/api/apps.js @@ -0,0 +1,166 @@ +const logger = require('../util/logger'); + +const getAll = (requests, profile) => async (request, response) => { + const url = profile.SERVICE_ENDPOINT; + const endpoint = `${url}/apps`; + logger.info(`Getting apps endpoint ${endpoint}`); + requests + .get(endpoint) + .set('Content-Type', 'application/json') + .set( + 'Authorization', + `${request.headers ? request.headers.authorization : ''}`, + ) + .then((res) => { + response.status(res.status).send(res.body); + }) + .catch((err) => { + logger.error( + `Fail getting to ${url}: ${ + err.response ? JSON.stringify(err.response.body) : '' + }`, + ); + response.status(err.status).send(err.response ? err.response.body : ''); + }); +}; + +const getById = (requests, profile) => async (request, response) => { + const url = profile.SERVICE_ENDPOINT; + const appId = request.params.id; + if (!appId) { + return response.status(400).send({ + message: 'Invalid request. App id not supplied', + }); + } + const endpoint = `${url}/apps/${appId}`; + logger.info(`Getting endpoint ${endpoint}`); + requests + .get(endpoint) + .set('Content-Type', 'application/json') + .set( + 'Authorization', + `${request.headers ? request.headers.authorization : ''}`, + ) + .then((res) => { + response.status(res.status).send(res.body); + }) + .catch((err) => { + logger.error( + `Fail getting to ${url}: ${ + err.response ? JSON.stringify(err.response.body) : '' + }`, + ); + response.status(err.status).send(err.response ? err.response.body : ''); + }); +}; + +const post = (requests, profile) => async (request, response) => { + const url = profile.SERVICE_ENDPOINT; + const app = request.body; + if (!app) { + return response.status(400).send({ + message: 'Invalid request. App not supplied', + }); + } + + const endpoint = app.id ? `${url}/apps/${app.id}` : `${url}/apps`; + + logger.info(`Posting endpoint ${endpoint}`); + + const post = app.id === null || app.id === undefined; + const backEndRequest = ( + post ? requests.post(endpoint) : requests.put(endpoint) + ) + .set('Accept', 'application/json') + .set('Content-Type', 'application/json'); + if (request.headers && request.headers.authorization) { + backEndRequest.set('Authorization', request.headers.authorization); + } + + backEndRequest + .send(app) + .then((res) => { + console.log(JSON.stringify(res)); + response.status(res.status).send(res.body); + }) + .catch((err) => { + logger.error( + `Fail posting to ${url}: ${ + err.response ? JSON.stringify(err.response.body) : '' + }`, + ); + response.status(err.status).send(err.response ? err.response.body : ''); + }); +}; + +const getRoles = (requests, profile) => async (request, response) => { + const url = profile.SERVICE_ENDPOINT; + const appId = request.params.id; + if (!appId || appId == undefined || appId === 'undefined') { + return response.status(400).send({ + message: 'Invalid request. Role name not supplied', + }); + } + const endpoint = `${url}/apps/${appId}/roles`; + logger.info(`Getting endpoint ${endpoint}`); + requests + .get(endpoint) + .set('Content-Type', 'application/json') + .set( + 'Authorization', + `${request.headers ? request.headers.authorization : ''}`, + ) + .then((res) => { + response.status(res.status).send(res.body); + }) + .catch((err) => { + logger.error( + `Fail getting to ${url}: ${ + err.response ? JSON.stringify(err.response.body) : '' + }`, + ); + response.status(err.status).send(err.response ? err.response.body : ''); + }); +}; + +const _delete = (requests, profile) => (request, response) => { + const url = profile.SERVICE_ENDPOINT; + const appId = request.params.id; + if (!appId || appId == 'undefined') { + return response.status(400).send({ + message: 'Invalid request. app Name not supplied', + }); + } + const endpoint = `${url}/apps/${appId}`; + logger.info(`Getting endpoint ${endpoint}`); + requests + .delete(endpoint) + .set('Content-Type', 'application/json') + .set( + 'Authorization', + `${ + request.headers && request.headers.authorization + ? request.headers.authorization + : '' + }`, + ) + .then((res) => { + response.status(res.status).send(res.body); + }) + .catch((err) => { + logger.error( + `Fail deleting to ${url}: ${ + err.response ? JSON.stringify(err.response.body) : '' + }`, + ); + response.status(err.status).send(err.response ? err.response.body : ''); + }); +}; + +module.exports = { + getAll: getAll, + getById: getById, + post: post, + delete: _delete, + getRoles: getRoles, +}; diff --git a/nesis/frontend/server/api/apps.spec.js b/nesis/frontend/server/api/apps.spec.js new file mode 100644 index 0000000..433c4a1 --- /dev/null +++ b/nesis/frontend/server/api/apps.spec.js @@ -0,0 +1,187 @@ +const api = require('./apps'); +const config = require('../profile'); +const sinon = require('sinon'); +const { Request } = require('../util/test-util'); + +describe('Apps', () => { + it('can be created', () => { + const requests = new Request(); + const post = api.post(requests, config.profile.DEV); + const request = { + headers: { + authorization: 'Usdz3323233eeewe', + }, + body: { + name: 'app name', + description: 'app description', + }, + }; + + const responseStab = sinon.stub(); + const statusStab = sinon.stub().returns({ send: responseStab }); + const response = { + status: statusStab, + }; + + post(request, response); + sinon.assert.calledWith(statusStab, 200); + sinon.assert.called(responseStab); + const args = responseStab.getCall(0).args[0]; + sinon.assert.match(args, request.body); + sinon.assert.match( + `${config.profile.DEV.SERVICE_ENDPOINT}/apps`, + requests.url, + ); + sinon.assert.match('POST', requests.method); + sinon.assert.match( + request.headers.authorization, + requests.headers['Authorization'], + ); + }); + + it('can be updated', () => { + const requests = new Request(); + const post = api.post(requests, config.profile.DEV); + const request = { + headers: { + authorization: 'Usdz3323233eeewe', + }, + body: { + name: 'app name', + description: 'app description', + id: 'some.id', + }, + }; + + const responseStab = sinon.stub(); + const statusStab = sinon.stub().returns({ send: responseStab }); + const response = { + status: statusStab, + }; + + post(request, response); + sinon.assert.calledWith(statusStab, 200); + sinon.assert.called(responseStab); + const args = responseStab.getCall(0).args[0]; + sinon.assert.match(args, request.body); + sinon.assert.match( + `${config.profile.DEV.SERVICE_ENDPOINT}/apps/some.id`, + requests.url, + ); + sinon.assert.match('PUT', requests.method); + sinon.assert.match( + request.headers.authorization, + requests.headers['Authorization'], + ); + }); + + it('can be retrieved', () => { + const requests = new Request(); + const apps = [ + { + name: 'app name-1', + description: 'app description', + }, + { + name: 'app name-2', + description: 'app description', + }, + ]; + + requests.apps = apps; + const getAll = api.getAll(requests, config.profile.DEV); + const request = { + headers: { + authorization: 'Usdz3323233eeewe', + }, + }; + + const responseStab = sinon.stub(); + const statusStab = sinon.stub().returns({ send: responseStab }); + const response = { + status: statusStab, + }; + + getAll(request, response); + sinon.assert.calledWith(statusStab, 200); + sinon.assert.called(responseStab); + const args = responseStab.getCall(0).args[0]; + sinon.assert.match( + `${config.profile.DEV.SERVICE_ENDPOINT}/apps`, + requests.url, + ); + sinon.assert.match(args, apps); + }); + + it('can be retrieved by id', () => { + const requests = new Request(); + const app = { + name: 'app name', + description: 'app description', + }; + + requests.appById = app; + const getById = api.getById(requests, config.profile.DEV); + const request = { + headers: { + authorization: 'Usdz3323233eeewe', + }, + params: { + id: '123456789', + }, + }; + + const responseStab = sinon.stub(); + const statusStab = sinon.stub().returns({ send: responseStab }); + const response = { + status: statusStab, + }; + + getById(request, response); + sinon.assert.calledWith(statusStab, 200); + sinon.assert.called(responseStab); + const args = responseStab.getCall(0).args[0]; + sinon.assert.match( + `${config.profile.DEV.SERVICE_ENDPOINT}/apps/${request.params.id}`, + requests.url, + ); + sinon.assert.match(args, app); + }); + + it('can be deleted', () => { + const requests = new Request(); + const targets = [ + { + name: 'test', + engine: 'sqlserver', + }, + ]; + + requests.targets = targets; + const _delete = api.delete(requests, config.profile.DEV); + const request = { + headers: { + authorization: 'Usdz3323233eeewe', + }, + params: { + id: '123456789', + }, + }; + + const responseStab = sinon.stub(); + const statusStab = sinon.stub().returns({ send: responseStab }); + const response = { + status: statusStab, + }; + + _delete(request, response); + sinon.assert.calledWith(statusStab, 200); + sinon.assert.called(responseStab); + const args = responseStab.getCall(0).args[0]; + sinon.assert.match( + `${config.profile.DEV.SERVICE_ENDPOINT}/apps/${request.params.id}`, + requests.url, + ); + sinon.assert.match(args, 'Deleted'); + }); +}); diff --git a/nesis/frontend/server/api/index.js b/nesis/frontend/server/api/index.js index de48da4..11089fc 100644 --- a/nesis/frontend/server/api/index.js +++ b/nesis/frontend/server/api/index.js @@ -6,3 +6,4 @@ module.exports.roles = require('./roles'); module.exports.users = require('./users'); module.exports.tasks = require('./tasks'); module.exports.config = require('./config'); +module.exports.apps = require('./apps'); diff --git a/nesis/frontend/server/main.js b/nesis/frontend/server/main.js index ccbfb43..5818226 100644 --- a/nesis/frontend/server/main.js +++ b/nesis/frontend/server/main.js @@ -28,6 +28,7 @@ async function init() { ROLES: '/api/roles', USERS: '/api/users', TASKS: '/api/tasks', + APPS: '/api/apps', DATA_SOURCES: '/api/datasources', CONFIG: '/api/config', QANDA_PREDICTIONS: '/api/qanda/predictions', @@ -65,6 +66,13 @@ async function init() { app.post(API.TASKS, api.tasks.post(requests, profile)); app.delete(`${API.TASKS}/:id`, api.tasks.delete(requests, profile)); + //Apps + app.get(`${API.APPS}/:id`, api.apps.getById(requests, profile)); + app.get(API.APPS, api.apps.getAll(requests, profile)); + app.get(`${API.APPS}/:id/roles`, api.apps.getRoles(requests, profile)); + app.post(API.APPS, api.apps.post(requests, profile)); + app.delete(`${API.APPS}/:id`, api.apps.delete(requests, profile)); + //Data source app.get( `${API.DATA_SOURCES}/:id`, diff --git a/nesis/frontend/server/util/test-util.js b/nesis/frontend/server/util/test-util.js index 8c45eeb..dd1d71c 100644 --- a/nesis/frontend/server/util/test-util.js +++ b/nesis/frontend/server/util/test-util.js @@ -18,7 +18,9 @@ class Request { this.models = []; this.dataobjects = []; this.tasks = []; + this.apps = []; this.taskById = {}; + this.appById = {}; this.targets = {}; this.azureUserData = {}; } @@ -89,6 +91,12 @@ class Request { status: 200, }; this.tasks.push(res.body); + } else if (this.url.endsWith('/apps')) { + res = { + body: Object.assign({}, this.data, { id: util.guid() }), + status: 200, + }; + this.apps.push(res.body); } } else if (this.method === 'PUT') { if (this.url.includes('/rules/')) { @@ -99,6 +107,8 @@ class Request { res = { body: this.data, status: 200 }; } else if (this.url.includes('/tasks/')) { res = { body: this.data, status: 200 }; + } else if (this.url.includes('/apps/')) { + res = { body: this.data, status: 200 }; } } else if (this.method === 'GET') { if (this.url.endsWith('/rules')) { @@ -113,8 +123,12 @@ class Request { res = { body: this.datasourceByName, status: 200 }; } else if (this.url.endsWith('/tasks')) { res = { body: this.tasks, status: 200 }; + } else if (this.url.endsWith('/apps')) { + res = { body: this.apps, status: 200 }; } else if (this.url.includes('/tasks/')) { res = { body: this.taskById, status: 200 }; + } else if (this.url.includes('/apps/')) { + res = { body: this.appById, status: 200 }; } else if (this.url.endsWith('/v1.0/me')) { res = { body: this.azureUserData, status: 200 }; } diff --git a/version.txt b/version.txt index 4e379d2..17e51c3 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -0.0.2 +0.1.1