From ae588021e8ab208922309efe0569932a02f97fe4 Mon Sep 17 00:00:00 2001 From: rajivbb Date: Fri, 21 Nov 2025 14:51:42 +0545 Subject: [PATCH 1/7] feat: added pre-commit config and update .pre-commit.config file --- .github/workflows/pre-commit.yaml | 116 ++++++++++++++++++++++++++++++ .pre-commit-config.yaml | 80 +++++++++++++++++---- 2 files changed, 184 insertions(+), 12 deletions(-) create mode 100644 .github/workflows/pre-commit.yaml diff --git a/.github/workflows/pre-commit.yaml b/.github/workflows/pre-commit.yaml new file mode 100644 index 0000000..3a2702d --- /dev/null +++ b/.github/workflows/pre-commit.yaml @@ -0,0 +1,116 @@ +name: Pre-Commit Checks + +on: + push: + branches: + - main + - develop + - 'feat/**' + - 'feature/**' + - 'test/**' + - 'chore/**' + - 'fix/**' + - 'hotfix/**' + - 'docs/**' + pull_request: + types: [opened, synchronize, reopened] + +jobs: + precommit: + runs-on: ubuntu-latest + + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install pre-commit + run: | + python -m pip install --upgrade pip + pip install pre-commit + + - name: Load Pre-commit Config + run: | + if [ ! -f ".pre-commit-config.yaml" ]; then + echo " No .pre-commit-config.yaml found — downloading BerryBytes global config..." + curl -sSL \ + https://raw.githubusercontent.com/BerryBytes/precommit-util/main/global/precommitFile/.pre-commit-config.yaml \ + -o .pre-commit-config.yaml + else + echo "✔ Using project's existing .pre-commit-config.yaml" + fi + + - name: Inject temporary Stylelint config for CI + run: | + if [ ! -f ".stylelintrc.json" ]; then + echo " Creating temporary .stylelintrc.json for CI..." + cat < .stylelintrc.json + { + "extends": "stylelint-config-standard", + "rules": { + "no-duplicate-selectors": true, + "color-hex-length": "short", + "selector-no-qualifying-type": true, + "selector-max-id": 0 + } + } + EOF + else + echo "✔ .stylelintrc.json already exists — skipping" + fi + + # -------------------------------------------------------------------- + # STEP 1: Run pre-commit (capture full logs and exit code safely) + # -------------------------------------------------------------------- + - name: Run pre-commit (full logs) + id: runprecommit + run: | + echo "🔍 Running full pre-commit checks..." + + set +e # allow failure + pre-commit run --all-files --verbose --show-diff-on-failure --color never \ + | tee full_precommit.log + exit_code=${PIPESTATUS[0]} + + echo "Pre-commit exit code: $exit_code" + echo "$exit_code" > precommit_exit_code.txt + + # -------------------------------------------------------------------- + # STEP 2: Summary of FAILED hooks + # -------------------------------------------------------------------- + - name: Pre-commit summary of failed hooks + run: | + echo "=====================================================" + echo " PRE-COMMIT SUMMARY" + echo "=====================================================" + + exit_code=$(cat precommit_exit_code.txt) + + if [ "$exit_code" = "0" ]; then + echo " All hooks passed!" + exit 0 + fi + + echo " Hooks failed — showing summary:" + echo "" + + echo " FAILED HOOKS:" + grep -E "^\w.*\.{3,}Failed" full_precommit.log || echo " None" + echo "-----------------------------------------------------" + + echo " FILES WITH ISSUES:" + grep -E "files were modified by this hook" -A3 full_precommit.log \ + | sed 's/^/ - /' || echo " None" + echo "-----------------------------------------------------" + + echo " ERROR DETAILS:" + grep -Ei "(error|failed|violation|missing|line too long|could not|warning)" full_precommit.log \ + | grep -Ev "^(---|\+\+\+|@@|diff --git|index )" \ + | sed 's/^/ • /' || echo " None" + echo "-----------------------------------------------------" + + exit $exit_code \ No newline at end of file diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 31c35fc..8e24f5e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,25 +1,81 @@ repos: + ########## Precommit Hooks ######### + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 + hooks: + - id: check-yaml + - id: end-of-file-fixer + - id: trailing-whitespace + - id: check-added-large-files + - id: check-vcs-permalinks + - id: check-symlinks + - id: destroyed-symlinks + - id: pretty-format-json + + ########## golang fmt and go tidey ######### + - repo: https://github.com/TekWizely/pre-commit-golang + rev: v1.0.0-rc.1 + hooks: + - id: go-fmt + args: [-w] + - id: go-mod-tidy + - repo: https://github.com/psf/black rev: 23.9.1 hooks: - id: black args: [--line-length=88] - language_version: python3.13 + # Import sorter (runs before Black) + - repo: https://github.com/PyCQA/isort + rev: 5.12.0 + hooks: + - id: isort + args: ["--profile=black"] + # Linter (flake8 for code quality) + - repo: https://github.com/pycqa/flake8 + rev: 6.1.0 + hooks: + - id: flake8 + args: + - --max-line-length=88 + - --extend-ignore=E203,W503 + additional_dependencies: + - flake8-bugbear + - flake8-comprehensions + - flake8-docstrings + # Static code analysis for Python (pylint optional) + - repo: https://github.com/pycqa/pylint + rev: v3.2.6 + hooks: + - id: pylint + args: ["--disable=C0114,C0115,C0116"] + additional_dependencies: + - pylint-django + - pylint-flask + # Stylelint + - repo: https://github.com/thibaudcolas/pre-commit-stylelint + rev: v15.10.3 + hooks: + - id: stylelint + files: \.(css|scss)$ + exclude: "node_modules/" + additional_dependencies: + - stylelint + - stylelint-config-standard + args: ['--config', '.stylelintrc.json', '--fix'] + + # Codespell - repo: https://github.com/codespell-project/codespell rev: v2.2.5 hooks: - id: codespell files: ^.*\.(py|c|h|md|rst|yml|go|sh|sql|tf|yaml)$ - args: ["--write-changes", "--ignore-words-list", "hist,nd"] - - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + args: ["--ignore-words-list", "hist,nd"] + # Gitleaks + - repo: https://github.com/gitleaks/gitleaks + rev: v8.21.0 hooks: - - id: end-of-file-fixer - - id: check-yaml - - id: check-added-large-files - - id: debug-statements - - id: trailing-whitespace - - id: html-validate - - id: json-validate + - id: gitleaks + args: ["detect", "--verbose"] + verbose: true From 92646f29f6ae9cb7cd891e4222ca14c4412abb2a Mon Sep 17 00:00:00 2001 From: rajivbb Date: Wed, 26 Nov 2025 14:30:01 +0545 Subject: [PATCH 2/7] feat: added the github mergeable configuration --- .github/mergeable.yml | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 .github/mergeable.yml diff --git a/.github/mergeable.yml b/.github/mergeable.yml new file mode 100644 index 0000000..c4bda8c --- /dev/null +++ b/.github/mergeable.yml @@ -0,0 +1,34 @@ +version: 2 +mergeable: + - when: pull_request.*, pull_request_review.* + validate: + # Validate PR title + - do: title + must_include: + regex: '^(feat|docs|chore|fix|refactor|test|style|perf)(\(\w+\))?: .{5,}' + message: "Semantic release conventions must be followed. Example: feat(auth): add login page. Title must be at least 5 characters after the prefix." + + # Ensure PR description is provided + - do: description + must_include: + regex: "[\\s\\S]{20,}" # At least 20 characters + message: "Please provide a meaningful description of the PR (minimum 20 characters)." + + # Ensure PR references an associated issue + - do: description + must_include: + regex: "(Closes|Fixes|Resolves|Addresses)\\s+#[0-9]+(,?\\s*#[0-9]+)*" + message: "PR must reference at least one issue (e.g., Closes #123, Fixes #123, #124)." + + # Ensure at least one required label is applied + - do: label + must_include: + regex: "^(bug|enhancement|documentation|feature|refactor|performance|chore|wip|test|ci|security|dependencies)$" + message: "PR must include at least one valid label." + + pass: + - do: labels + add: + - "validated" + - do: checks + status: "success" From e0a6c687579d1c8f590b0ab8437e83a54fb59f98 Mon Sep 17 00:00:00 2001 From: Subash Nagarkoti Date: Fri, 5 Dec 2025 10:09:41 +0545 Subject: [PATCH 3/7] fix: pre-commit hook issues --- .codespell-ignore-words | 1 + .flake8 | 24 + .gitignore | 3 +- .pre-commit-config.yaml | 40 +- .prettierrc | 22 + .pylintrc | 34 ++ .stylelintrc.json | 9 + cluster-api/dto/cluster_request.py | 19 +- cluster-api/dto/cluster_response.py | 12 +- cluster-api/dto/cluster_status_request.py | 18 +- cluster-api/dto/cluster_upgrade.py | 16 +- cluster-api/dto/host_cluster_request.py | 13 +- cluster-api/dto/kube_version_request.py | 11 +- cluster-api/middleware/__init__.py | 1 + cluster-api/middleware/dependency.py | 34 +- cluster-api/middleware/middleware.py | 87 ++- cluster-api/models/cluster.py | 15 +- cluster-api/models/common_response.py | 9 +- cluster-api/models/generate_kubeconfig.py | 11 +- cluster-api/models/host_cluster.py | 15 +- cluster-api/models/kube_version.py | 9 +- cluster-api/models/subscription.py | 31 +- cluster-api/models/user.py | 24 +- cluster-api/routes/__init__.py | 1 + cluster-api/routes/cluster.py | 548 ++++++++++++++---- cluster-api/routes/host_cluster.py | 132 ++++- cluster-api/routes/kube_list.py | 172 +++++- cluster-api/routes/public.py | 107 ++-- cluster-api/routes/subscription.py | 79 ++- cluster-api/routes/user.py | 182 ++++-- cluster-api/routes/websocket.py | 93 +-- cluster-api/schemas/cluster_schema.py | 32 +- cluster-api/schemas/host_cluster_schema.py | 42 +- cluster-api/schemas/subscription_schema.py | 26 +- cluster-api/schemas/user_schema.py | 17 +- cluster-api/utills/__init__.py | 1 + cluster-api/utills/common_response.py | 40 +- cluster-api/utills/common_utills.py | 56 +- cluster-api/utills/seeder.py | 126 +++- cluster-service/src/controller/routes.py | 43 +- .../src/models/client_node_metrix.py | 14 +- cluster-service/src/models/subscription.py | 58 +- cluster-service/src/usecases/__init__.py | 1 + cluster-service/src/usecases/use_cases.py | 239 +++++--- cluster-service/src/utils/__init__.py | 1 + cluster-service/src/utils/app_constant.py | 2 + .../src/utils/best_cluster_utils.py | 18 +- cluster-service/src/utils/cluster_utils.py | 49 +- cluster-service/src/utils/common_utils.py | 100 +++- cluster-service/src/utils/secret_utils.py | 23 +- cluster-service/tests/__init__.py | 1 + .../tests/test_best_cluster_utils.py | 100 +--- 52 files changed, 2042 insertions(+), 719 deletions(-) create mode 100644 .codespell-ignore-words create mode 100644 .flake8 create mode 100644 .prettierrc create mode 100644 .pylintrc create mode 100644 .stylelintrc.json create mode 100644 cluster-api/middleware/__init__.py create mode 100644 cluster-api/routes/__init__.py create mode 100644 cluster-api/utills/__init__.py create mode 100644 cluster-service/src/utils/__init__.py diff --git a/.codespell-ignore-words b/.codespell-ignore-words new file mode 100644 index 0000000..11fec86 --- /dev/null +++ b/.codespell-ignore-words @@ -0,0 +1 @@ +ser diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..5b280e1 --- /dev/null +++ b/.flake8 @@ -0,0 +1,24 @@ +[flake8] +max-line-length = 120 + +ignore = + F401 + F541 + F841 + F821 + E501 + D202 + D205 + D400 + D401 + D403 + B008 + B006 + C417 + +exclude = + venv/ + __pycache__/ + migrations/ + .git/ + .idea/ diff --git a/.gitignore b/.gitignore index a7c2351..bb4aa8d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ # Distribution / packaging .Pytho -.vscode \ No newline at end of file +.vscode +venv/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8e24f5e..ca98d10 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,18 +1,21 @@ repos: ########## Precommit Hooks ######### - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v4.5.0 hooks: - id: check-yaml + exclude: ^manifest/.*\.yaml$ - id: end-of-file-fixer - id: trailing-whitespace - id: check-added-large-files - id: check-vcs-permalinks + exclude: ^manifest/charts/ - id: check-symlinks - id: destroyed-symlinks - id: pretty-format-json + args: ["--autofix"] - ########## golang fmt and go tidey ######### + ######### golang fmt and go tidey ######### - repo: https://github.com/TekWizely/pre-commit-golang rev: v1.0.0-rc.1 hooks: @@ -25,14 +28,15 @@ repos: hooks: - id: black args: [--line-length=88] - # Import sorter (runs before Black) + +# # Import sorter (runs before Black) - repo: https://github.com/PyCQA/isort rev: 5.12.0 hooks: - id: isort args: ["--profile=black"] - # Linter (flake8 for code quality) +# # Linter (flake8 for code quality) - repo: https://github.com/pycqa/flake8 rev: 6.1.0 hooks: @@ -44,35 +48,47 @@ repos: - flake8-bugbear - flake8-comprehensions - flake8-docstrings - # Static code analysis for Python (pylint optional) + +# # Static code analysis for Python (pylint optional) - repo: https://github.com/pycqa/pylint - rev: v3.2.6 + rev: v3.0.0 hooks: - id: pylint args: ["--disable=C0114,C0115,C0116"] additional_dependencies: - pylint-django - - pylint-flask - # Stylelint + - flask + - fastapi + - pydantic + - pymongo + - python-jose + - python-keycloak + - pyparsing + - websockets + - kubernetes + - dapr + - PyYAML +# # Stylelint - repo: https://github.com/thibaudcolas/pre-commit-stylelint rev: v15.10.3 hooks: - id: stylelint files: \.(css|scss)$ - exclude: "node_modules/" + # exclude: "node_modules/" additional_dependencies: - stylelint - stylelint-config-standard args: ['--config', '.stylelintrc.json', '--fix'] - # Codespell +# # Codespell - repo: https://github.com/codespell-project/codespell rev: v2.2.5 hooks: - id: codespell files: ^.*\.(py|c|h|md|rst|yml|go|sh|sql|tf|yaml)$ - args: ["--ignore-words-list", "hist,nd"] - # Gitleaks + args: ['--ignore-words=.codespell-ignore-words'] + +# # Gitleaks - repo: https://github.com/gitleaks/gitleaks rev: v8.21.0 hooks: diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..5902ac3 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,22 @@ +{ + "singleQuote": true, + "trailingComma": "es5", + "tabWidth": 2, + "semi": true, + "printWidth": 100, + "overrides": [ + { + "files": "*.html", + "options": { + "parser": "html" + } + }, + { + "files": "*.css", + "options": { + "parser": "css" + } + } + ], + "ignore": ["node_modules"] +} diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 0000000..1495954 --- /dev/null +++ b/.pylintrc @@ -0,0 +1,34 @@ +[MESSAGES CONTROL] +disable= + broad-exception-caught, + redefined-outer-name, + too-many-locals, + no-else-return, + logging-fstring-interpolation, + redefined-builtin, + logging-not-lazy, + dangerous-default-value, + inconsistent-return-statements, + missing-timeout, + too-few-public-methods, + invalid-name, + raise-missing-from, + consider-using-f-string, + too-many-return-statements, + R0801, + line-too-long, + no-member, + R1733, + unused-import, + unused-variable, + unused-argument, + f-string-without-interpolation, + unspecified-encoding, + unexpected-keyword-arg, + no-value-for-parameter, + too-many-instance-attributes, + too-many-arguments, + unnecessary-pass, + logging-too-many-args, + no-else-break, + deprecated-module, diff --git a/.stylelintrc.json b/.stylelintrc.json new file mode 100644 index 0000000..98e6f06 --- /dev/null +++ b/.stylelintrc.json @@ -0,0 +1,9 @@ +{ + "extends": "stylelint-config-standard", + "rules": { + "color-hex-length": "short", + "no-duplicate-selectors": true, + "selector-max-id": 0, + "selector-no-qualifyin`g-type": true + } +} diff --git a/cluster-api/dto/cluster_request.py b/cluster-api/dto/cluster_request.py index 3b9d629..5a1ae9f 100644 --- a/cluster-api/dto/cluster_request.py +++ b/cluster-api/dto/cluster_request.py @@ -1,20 +1,25 @@ -from pydantic import BaseModel,Field +"""DTOs for cluster API: request models.""" from typing import Optional +from pydantic import BaseModel, Field class ClusterRequest(BaseModel): - name: str + """Request model for creating a Kubernetes cluster.""" + + name: str subscriptionId: str = Field(alias="subscription_id") region: str = Field(alias="region") - kube_version: Optional[str] = Field(alias="kube_version",default="v1.30.0") + kube_version: Optional[str] = Field(alias="kube_version", default="v1.30.0") class Config: + """Pydantic configuration for cluster_request, including schema examples.""" + schema_extra = { - "example":{ - "name":"cluster-name", + "example": { + "name": "cluster-name", "subscription_id": "066de609-b04a-4b30-b46c-32537c7f1f6e", - "region":"us-east-1", - "kube_version":"v1.27.0" + "region": "us-east-1", + "kube_version": "v1.27.0", } } diff --git a/cluster-api/dto/cluster_response.py b/cluster-api/dto/cluster_response.py index 006b5be..cd7ee1b 100644 --- a/cluster-api/dto/cluster_response.py +++ b/cluster-api/dto/cluster_response.py @@ -1,16 +1,18 @@ -from pydantic import BaseModel +"""DTOs for cluster API: response models.""" from models.host_cluster import HostCluster from models.subscription import Subscription - from models.user import User +from pydantic import BaseModel class ClusterResponse(BaseModel): - id:str + """Response model representing a Kubernetes cluster.""" + + id: str name: str - status:str + status: str created: str kube_version: str user: User subscription: Subscription - hostCluster: HostCluster \ No newline at end of file + hostCluster: HostCluster diff --git a/cluster-api/dto/cluster_status_request.py b/cluster-api/dto/cluster_status_request.py index 3225f88..8137c94 100644 --- a/cluster-api/dto/cluster_status_request.py +++ b/cluster-api/dto/cluster_status_request.py @@ -1,15 +1,21 @@ +"""DTOs for cluster API: cluster status request models.""" from pydantic import BaseModel + class ClusterStatusRequest(BaseModel): - name: str - id: str - status: str + """Request model for updating or querying the status of a Kubernetes cluster.""" + + name: str + id: str + status: str class Config: + """Pydantic configuration for ClusterStatusRequest,including schema example.""" + schema_extra = { - "example":{ - "name":"cluster-name", + "example": { + "name": "cluster-name", "id": "066de609-b04a-4b30-b46c-32537c7f1f6e", - "status":"Running" + "status": "Running", } } diff --git a/cluster-api/dto/cluster_upgrade.py b/cluster-api/dto/cluster_upgrade.py index d7e9fcb..197c9e4 100644 --- a/cluster-api/dto/cluster_upgrade.py +++ b/cluster-api/dto/cluster_upgrade.py @@ -1,14 +1,18 @@ -from pydantic import BaseModel,Field +"""DTOs for cluster API: cluster upgrade request models.""" from typing import Optional +from pydantic import BaseModel, Field class ClusterUpgradeRequest(BaseModel): + """Request model for upgrading a Kubernetes cluster.""" + kube_version: str = Field(alias="kube_version") class Config: - schema_extra = { - "example":{ - "kube_version":"v1.27.0" - } - } + """ + Pydantic configuration for cluster_upgrade_request, + including schema example. + """ + + schema_extra = {"example": {"kube_version": "v1.27.0"}} diff --git a/cluster-api/dto/host_cluster_request.py b/cluster-api/dto/host_cluster_request.py index e186703..0cc2480 100644 --- a/cluster-api/dto/host_cluster_request.py +++ b/cluster-api/dto/host_cluster_request.py @@ -1,10 +1,13 @@ +"""DTOs for cluster API: host cluster request models.""" import uuid from datetime import datetime -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field class HostClusterRequest(BaseModel): + """Request model for creating or updating a host cluster.""" + name: str = Field(...) region: str = Field(...) provider: str = Field(...) @@ -13,14 +16,16 @@ class HostClusterRequest(BaseModel): version: str = Field(...) class Config: + """Pydantic configuration for host_cluster_request,including schema example.""" + allow_population_by_field_name = True schema_extra = { - "example":{ + "example": { "name": "cluster 1", "region": "us-east-1", "provider": "aws", "nodes": 1, "active": False, - "version": "1.25" + "version": "1.25", } - } \ No newline at end of file + } diff --git a/cluster-api/dto/kube_version_request.py b/cluster-api/dto/kube_version_request.py index 6b15576..a822134 100644 --- a/cluster-api/dto/kube_version_request.py +++ b/cluster-api/dto/kube_version_request.py @@ -1,16 +1,23 @@ -from pydantic import BaseModel, Field +"""DTOs for cluster API: kube version request models.""" from datetime import datetime +from pydantic import BaseModel, Field + + class KubeVersionRequest(BaseModel): + """Request model for specifying a Kubernetes version for a cluster.""" + name: str kube_version: str active: bool = Field(default=True) class Config: + """Pydantic configuration for KubeVersionRequest,including schema example.""" + json_schema_extra = { "example": { "name": "example_name", "kube_version": "1.30.1", - "active": True + "active": True, } } diff --git a/cluster-api/middleware/__init__.py b/cluster-api/middleware/__init__.py new file mode 100644 index 0000000..fae6326 --- /dev/null +++ b/cluster-api/middleware/__init__.py @@ -0,0 +1 @@ +"""Test package initialization.""" diff --git a/cluster-api/middleware/dependency.py b/cluster-api/middleware/dependency.py index d680a99..765f193 100644 --- a/cluster-api/middleware/dependency.py +++ b/cluster-api/middleware/dependency.py @@ -1,19 +1,33 @@ +"""Dependency utilities for FastAPI authentication and access control.""" import logging + +from fastapi import Depends, HTTPException +from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer from jose import jwt from jose.exceptions import JOSEError -from fastapi import HTTPException, Depends -from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer security = HTTPBearer() -async def has_access(credentials: HTTPAuthorizationCredentials= Depends(security)): + +async def has_access(credentials: HTTPAuthorizationCredentials = Depends(security)): + """ + Validate JWT token from Authorization header. + + Raises: + HTTPException: If token is invalid or cannot be decoded. + """ + token = credentials.credentials try: - payload = jwt.decode(token, key='secret', options={"verify_signature": False, - "verify_aud": False, - "verify_iss": False}) - logging.info("Token payload :: %s",str(payload)) + payload = jwt.decode( + token, + key="secret", + options={ + "verify_signature": False, + "verify_aud": False, + "verify_iss": False, + }, + ) + logging.info("Token payload :: %s", str(payload)) except JOSEError as e: # catches any exception - raise HTTPException( - status_code=401, - detail="Unauthorized") \ No newline at end of file + raise HTTPException(status_code=401, detail="Unauthorized") diff --git a/cluster-api/middleware/middleware.py b/cluster-api/middleware/middleware.py index bc74e9c..018c606 100644 --- a/cluster-api/middleware/middleware.py +++ b/cluster-api/middleware/middleware.py @@ -1,30 +1,32 @@ -from fastapi.encoders import jsonable_encoder -from pyparsing import Any +"""Middleware for Keycloak authentication and user verification.""" import logging -from fastapi import Request, HTTPException, status -from fastapi.responses import JSONResponse -from keycloak import KeycloakOpenID -import jose import os +import jose +from fastapi import HTTPException, Request, status +from fastapi.encoders import jsonable_encoder +from fastapi.responses import JSONResponse +from keycloak import KeycloakOpenID +from pyparsing import Any from schemas.user_schema import user_from_keycloak_dict, user_from_user_dict from utills.common_response import debug_response, generate_response # Check if REQUESTS_CA_BUNDLE environment variable exists -ca_bundle = os.getenv('REQUESTS_CA_BUNDLE') +ca_bundle = os.getenv("REQUESTS_CA_BUNDLE") if ca_bundle: - os.environ['REQUESTS_CA_BUNDLE'] = ca_bundle + os.environ["REQUESTS_CA_BUNDLE"] = ca_bundle print(f"REQUESTS_CA_BUNDLE is set to: {ca_bundle}") else: print("Warning: REQUESTS_CA_BUNDLE environment variable is not set") # Initialize KeycloakOpenID try: - keycloak_openid = KeycloakOpenID(server_url=os.getenv('KEYCLOAK_URL'), - client_id=os.getenv('CLIENT_ID'), - realm_name=os.getenv('REALM_NAME'), - verify=True - ) + keycloak_openid = KeycloakOpenID( + server_url=os.getenv("KEYCLOAK_URL"), + client_id=os.getenv("CLIENT_ID"), + realm_name=os.getenv("REALM_NAME"), + verify=True, + ) except Exception as e: print(f"Error initializing KeycloakOpenID: {e}") @@ -46,11 +48,24 @@ # Middleware for Keycloak authentication async def validate_keycloak_token(request: Request, call_next): # Check if the current route is public, if so, skip authentication + """FastAPI middleware to validate Keycloak JWT token for protected routes. + + Args: + request (Request): Incoming FastAPI request. + call_next: Function to call the next middleware or route. + + Returns: + Response: The response from the next handler if token is valid, + or error response. + """ if request.url.path in public_routes: return await call_next(request) authorization_header = request.headers.get("Authorization", "") if not authorization_header or not authorization_header.startswith("Bearer "): - return JSONResponse(status_code=401, content={'detail': "Missing or invalid Authorization header"}) + return JSONResponse( + status_code=401, + content={"detail": "Missing or invalid Authorization header"}, + ) token = authorization_header.split("Bearer ")[1] try: @@ -60,22 +75,37 @@ async def validate_keycloak_token(request: Request, call_next): except HTTPException as http_exception: raise http_exception except jose.exceptions.ExpiredSignatureError: - return generate_response(False, status.HTTP_401_UNAUTHORIZED, "Token has expired", None) + return generate_response( + False, status.HTTP_401_UNAUTHORIZED, "Token has expired", None + ) except jose.exceptions.JWTError: - return generate_response(False, status.HTTP_401_UNAUTHORIZED, "Token is invalid", None) + return generate_response( + False, status.HTTP_401_UNAUTHORIZED, "Token is invalid", None + ) except Exception as e: debug_response(e, "Error occurs on validating token", "error") - return generate_response(False, status.HTTP_500_INTERNAL_SERVER_ERROR, "Internal Server Error", None) - + return generate_response( + False, status.HTTP_500_INTERNAL_SERVER_ERROR, "Internal Server Error", None + ) def is_authenticated(credential: str): + """Verify the JWT token using Keycloak public key. + + Args: + credential (str): Bearer token from the request. + + Returns: + dict: Decoded token payload if valid. + + Raises: + ValueError: If the public key is invalid or token cannot be decoded. + """ token = credential try: # Verify the token and return the token claims if valid - options = {"verify_signature": True, - "verify_aud": False, "verify_exp": True} - + options = {"verify_signature": True, "verify_aud": False, "verify_exp": True} + # Ensure keycloak_public_key is valid keycloak_public_key = ( "-----BEGIN PUBLIC KEY-----\n" @@ -91,7 +121,7 @@ def is_authenticated(credential: str): token, key=keycloak_public_key, options=options ) return token_info - + except ValueError as ve: debug_response(ve, "Error occurs on checking user", "error") raise @@ -101,8 +131,17 @@ def is_authenticated(credential: str): def check_user(userInfo: Any, request: Request): + """Ensure the user exists in the database and attach it to request.state.user. + + Args: + user_info (Any): Decoded token claims. + request (Request): FastAPI request object with database access. + + Raises: + Exception: If user processing or DB operations fail. + """ try: - user = request.app.database["user"].find_one({"email": userInfo['email']}) + user = request.app.database["user"].find_one({"email": userInfo["email"]}) user_obj = user_from_keycloak_dict(userInfo) if user is None: # User does not exist, insert the user into the database @@ -114,5 +153,3 @@ def check_user(userInfo: Any, request: Request): except Exception as e: debug_response(e, "Error occurs on checking user", "error") raise - - diff --git a/cluster-api/models/cluster.py b/cluster-api/models/cluster.py index 5b7deae..346a171 100644 --- a/cluster-api/models/cluster.py +++ b/cluster-api/models/cluster.py @@ -1,12 +1,17 @@ -from datetime import datetime +"""Cluster model definitions.""" import uuid +from datetime import datetime + from pydantic import BaseModel, Field + class Cluster(BaseModel): + """Represents a Kubernetes cluster with metadata and configuration.""" + id: str = Field(default_factory=lambda: str(uuid.uuid4()), alias="_id") name: str = Field(...) userId: str = Field(alias="user_id") - status:str = Field(...) + status: str = Field(...) kube_version: str = Field(alias="kube_version") hostClusterId: str = Field(alias="host_cluster_id") subscriptionId: str = Field(alias="subscription_id") @@ -14,12 +19,14 @@ class Cluster(BaseModel): updated: datetime = Field(default_factory=datetime.now) class Config: + """Pydantic configuration for Cluster model.""" + allow_population_by_field_name = True schema_extra = { - "example":{ + "example": { "name": "one", "user_id": "066de609-b04a-4b30-b46c-32537c7f1f6e", "host_cluster_id": "066de609-b04a-4b30-b46c-32537c7f1f6e", "subscription_id": "066de609-b04a-4b30-b46c-32537c7f1f6e", } - } + } diff --git a/cluster-api/models/common_response.py b/cluster-api/models/common_response.py index 4ef860d..6df7416 100644 --- a/cluster-api/models/common_response.py +++ b/cluster-api/models/common_response.py @@ -1,8 +1,13 @@ +"""Response model for API outputs.""" from typing import Any, List, Optional, Union -from pydantic import BaseModel # type: ignore + +from pydantic import BaseModel # type: ignore + class ResponseModel(BaseModel): + """Standard API response structure.""" + code: int error_code: int message: str - data: Union[List[Any], Any] = [] \ No newline at end of file + data: Union[List[Any], Any] = [] diff --git a/cluster-api/models/generate_kubeconfig.py b/cluster-api/models/generate_kubeconfig.py index 7ce7f32..080e961 100644 --- a/cluster-api/models/generate_kubeconfig.py +++ b/cluster-api/models/generate_kubeconfig.py @@ -1,15 +1,20 @@ +"""Model for generating a kubeconfig with expiry time.""" from pydantic import BaseModel class GenerateKubeconfig(BaseModel): + """Request model for generating a temporary kubeconfig.""" + expiryTime: str clusterId: str class Config: + """Pydantic configuration for field population and schema examples.""" + allow_population_by_field_name = True schema_extra = { - "example":{ + "example": { "expiryTime": "1h20m", - "clusterId": "066de609-b04a-4b30-b46c-32537c7f1f6e", + "clusterId": "066de609-b04a-4b30-b46c-32537c7f1f6e", } - } \ No newline at end of file + } diff --git a/cluster-api/models/host_cluster.py b/cluster-api/models/host_cluster.py index 22866e4..8bc6622 100644 --- a/cluster-api/models/host_cluster.py +++ b/cluster-api/models/host_cluster.py @@ -1,11 +1,14 @@ +"""Model representing a host Kubernetes cluster.""" import uuid from datetime import datetime -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field class HostCluster(BaseModel): - id: str = Field(default_factory=uuid.uuid4,alias="_id") + """Represents the details of a host cluster.""" + + id: str = Field(default_factory=uuid.uuid4, alias="_id") name: str = Field(...) region: str = Field(...) provider: str = Field(...) @@ -17,14 +20,16 @@ class HostCluster(BaseModel): updated: datetime = Field(default_factory=datetime.now) class Config: + """Pydantic configuration for field population and schema examples.""" + allow_population_by_field_name = True schema_extra = { - "example":{ + "example": { "name": "cluster 1", "region": "us-east-1", "provider": "aws", "nodes": 1, "active": False, - "version": "1.25" + "version": "1.25", } - } \ No newline at end of file + } diff --git a/cluster-api/models/kube_version.py b/cluster-api/models/kube_version.py index 81ebf2d..44e472c 100644 --- a/cluster-api/models/kube_version.py +++ b/cluster-api/models/kube_version.py @@ -1,12 +1,19 @@ +"""Model representing a Kubernetes version.""" import uuid from datetime import datetime + from pydantic import BaseModel, Field + class KubeVersion(BaseModel): + """Represents a Kubernetes version entry.""" + name: str kube_version: str - id: str = Field(default_factory=uuid.uuid4,alias="_id") + id: str = Field(default_factory=uuid.uuid4, alias="_id") active: bool class Config: + """Pydantic configuration for ORM compatibility.""" + orm_mode = True diff --git a/cluster-api/models/subscription.py b/cluster-api/models/subscription.py index cddbed6..ce7e276 100644 --- a/cluster-api/models/subscription.py +++ b/cluster-api/models/subscription.py @@ -1,10 +1,14 @@ -from datetime import datetime +"""Model representing a subscription plan and its resource limits.""" import uuid +from datetime import datetime + from pydantic import BaseModel, Field class Subscription(BaseModel): - id: str = Field(default_factory=uuid.uuid4,alias="_id") + """Represents a subscription with resource quotas and utility methods.""" + + id: str = Field(default_factory=uuid.uuid4, alias="_id") name: str = Field(...) pods: int = Field(...) service: int = Field(...) @@ -18,6 +22,7 @@ class Subscription(BaseModel): updated: datetime = Field(default_factory=datetime.now) def is_subscription_upgrade(self, new_plan): + """Determine if the new plan exceeds current resource allocations.""" if new_plan.pods > self.pods: return True if new_plan.service > self.service: @@ -37,17 +42,19 @@ def is_subscription_upgrade(self, new_plan): return False class Config: + """Pydantic configuration for field population and schema examples.""" + allow_population_by_field_name = True schema_extra = { - "example":{ + "example": { "name": "basic", - "pods":5, - "service":5, - "config_map":5, - "persistance_vol_claims":5, - "replication_ctl":5, - "secrets":5, - "loadbalancer":5, - "node_port":5 + "pods": 5, + "service": 5, + "config_map": 5, + "persistance_vol_claims": 5, + "replication_ctl": 5, + "secrets": 5, + "loadbalancer": 5, + "node_port": 5, } - } \ No newline at end of file + } diff --git a/cluster-api/models/user.py b/cluster-api/models/user.py index 85140d1..84ee81b 100644 --- a/cluster-api/models/user.py +++ b/cluster-api/models/user.py @@ -1,32 +1,42 @@ +"""Models for user information and login credentials.""" import uuid + from pydantic import BaseModel, Field # Id, name, username, email + class User(BaseModel): + """Represents a user with basic profile information.""" + id: str = Field(default_factory=uuid.uuid4, alias="_id") name: str = Field(...) email: str = Field(...) userName: str = Field(...) class Config: + """Pydantic configuration for field population and schema examples.""" + allow_population_by_field_name = True schema_extra = { - "example":{ + "example": { "name": "user one", "email": "example@example.com", - "userName": "testUserName" + "userName": "testUserName", } } + + class UserLogin(BaseModel): + """Represents login credentials for a user.""" + userName: str = Field(...) password: str = Field(...) class Config: + """Pydantic configuration for field population and schema examples.""" + allow_population_by_field_name = True schema_extra = { - "example":{ - "userName": "testUserName", - "password": "testPassword" - } - } \ No newline at end of file + "example": {"userName": "testUserName", "password": "testPassword"} + } diff --git a/cluster-api/routes/__init__.py b/cluster-api/routes/__init__.py new file mode 100644 index 0000000..0ef3a98 --- /dev/null +++ b/cluster-api/routes/__init__.py @@ -0,0 +1 @@ +"""Routes package initialization.""" diff --git a/cluster-api/routes/cluster.py b/cluster-api/routes/cluster.py index 8eada99..091d31f 100644 --- a/cluster-api/routes/cluster.py +++ b/cluster-api/routes/cluster.py @@ -1,62 +1,108 @@ +""" +Cluster API router for managing clusters. + +Includes endpoints for: +- Creating, updating, deleting clusters +- Generating kubeconfig +- Starting, stopping clusters +- Retrieving cluster info and status +""" + import json import logging import os -from fastapi import APIRouter, Body, HTTPException, Request, Response, status -from fastapi.encoders import jsonable_encoder -from dapr.clients import DaprClient -import requests from typing import List +import requests +from dapr.clients import DaprClient from dto.cluster_request import ClusterRequest from dto.cluster_response import ClusterResponse from dto.cluster_upgrade import ClusterUpgradeRequest +from fastapi import APIRouter, Body, HTTPException, Request, Response, status +from fastapi.encoders import jsonable_encoder from middleware.middleware import is_authenticated from models.cluster import Cluster from models.common_response import ResponseModel from models.generate_kubeconfig import GenerateKubeconfig +from models.user import User from schemas.cluster_schema import clusters_serializer, is_valid_url_name from schemas.host_cluster_schema import host_clusters_serializer_test from schemas.subscription_schema import subscription_from_dict -from utills.common_utills import extract_time_components, get_best_cluster -from models.user import User from utills.common_response import debug_response, generate_response +from utills.common_utills import extract_time_components, get_best_cluster router = APIRouter() -@router.post("", response_description="Create a cluster", status_code=status.HTTP_201_CREATED, response_model={}) + +@router.post( + "", + response_description="Create a cluster", + status_code=status.HTTP_201_CREATED, + response_model={}, +) def create_Cluster(request: Request, clusterRequest: ClusterRequest = Body(...)): + """ + Create a new cluster in the system. + + Validates the cluster name, user permissions, subscription, + and selects the best host cluster based on region. Publishes + cluster creation event via Dapr. + """ name_status, error_message = is_valid_url_name(clusterRequest.name) authorization_header = request.headers.get("Authorization", "") token = authorization_header.split("Bearer ")[1] user_info = is_authenticated(token) # checking create 'create-cluster' role is assign to the user. - if user_info and 'realm_access' in user_info: - realm_roles = user_info['realm_access']['roles'] - if 'create-cluster' not in realm_roles: + if user_info and "realm_access" in user_info: + realm_roles = user_info["realm_access"]["roles"] + if "create-cluster" not in realm_roles: # If the user does not have the 'create-cluster' role - raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="User is not allowed to create clusters") + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="User is not allowed to create clusters", + ) if name_status is False and error_message is not None: - raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=error_message) - createdCluster = request.app.database["cluster"].find_one({"name": clusterRequest.name}) + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, detail=error_message + ) + createdCluster = request.app.database["cluster"].find_one( + {"name": clusterRequest.name} + ) if createdCluster is not None: - raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail=f"Cluster with name {clusterRequest.name} already exist") - subscription = request.app.database["subscription"].find_one({"_id": clusterRequest.subscriptionId}) + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, + detail=f"Cluster with name {clusterRequest.name} already exist", + ) + subscription = request.app.database["subscription"].find_one( + {"_id": clusterRequest.subscriptionId} + ) if subscription is None: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Subscription with ID {clusterRequest.subscriptionId} not found") - host_cluster_reponse = request.app.database["hostCluster"].find({"region": clusterRequest.region}) + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=f"Subscription with ID {clusterRequest.subscriptionId} not found", + ) + host_cluster_reponse = request.app.database["hostCluster"].find( + {"region": clusterRequest.region} + ) hostClusters = host_clusters_serializer_test(json.dumps(list(host_cluster_reponse))) if len(list(hostClusters)) == 0: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"hostCluster not found within the region {clusterRequest.region}") - - host_cluster_ids = list(map(lambda x: x['id'], hostClusters)) + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=f"hostCluster not found within the region {clusterRequest.region}", + ) + + host_cluster_ids = list(map(lambda x: x["id"], hostClusters)) best_cluster_id = get_best_cluster(host_cluster_ids) best_cluster_name = "" for obj in hostClusters: - if obj['id'] == best_cluster_id: - best_cluster_name = obj['name'] + if obj["id"] == best_cluster_id: + best_cluster_name = obj["name"] if best_cluster_name == "": - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"hostCluster not found within the region {clusterRequest.region}") + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=f"hostCluster not found within the region {clusterRequest.region}", + ) user_obj: User = request.state.user @@ -81,7 +127,7 @@ def create_Cluster(request: Request, clusterRequest: ClusterRequest = Body(...)) "subscription": subscription, "host_cluster_id": best_cluster_id, "host_cluster_name": best_cluster_name, - "cluster": created_cluster + "cluster": created_cluster, } client.publish_event( pubsub_name="messagebus", @@ -90,39 +136,78 @@ def create_Cluster(request: Request, clusterRequest: ClusterRequest = Body(...)) data_content_type="application/json", ) logging.info("Published data from create cluster :: %s", str(payload)) - return{"success": True, - "code": status.HTTP_200_OK, - "message": "Clusters listed successfully", - "data": created_cluster + return { + "success": True, + "code": status.HTTP_200_OK, + "message": "Clusters listed successfully", + "data": created_cluster, } except Exception as e: debug_response(e, "Error occurs on creating cluster", "error") - return generate_response(False, status.HTTP_500_INTERNAL_SERVER_ERROR, "Failed to create cluster", None) + return generate_response( + False, + status.HTTP_500_INTERNAL_SERVER_ERROR, + "Failed to create cluster", + None, + ) + -@router.put("/upgrade/{id}", response_description="Update a cluster", status_code=status.HTTP_200_OK, response_model=ResponseModel) -def update_cluster(id: str, request: Request, clusterUpgradeRequest: ClusterUpgradeRequest = Body(...)): +@router.put( + "/upgrade/{id}", + response_description="Update a cluster", + status_code=status.HTTP_200_OK, + response_model=ResponseModel, +) +def update_cluster( + id: str, request: Request, clusterUpgradeRequest: ClusterUpgradeRequest = Body(...) +): + """ + Upgrade a cluster's Kubernetes version. + + Checks that the new version is higher than the current one, + updates the cluster, and publishes the upgrade event. + """ kube_version = clusterUpgradeRequest.kube_version cluster = request.app.database["cluster"].find_one({"_id": id}) if cluster is None: - return generate_response(False, status.HTTP_404_NOT_FOUND, f"Cluster with ID {id} not found", None) + return generate_response( + False, status.HTTP_404_NOT_FOUND, f"Cluster with ID {id} not found", None + ) - current_version_parts = list(map(int, cluster["kube_version"].split("v")[1].split("."))) + current_version_parts = list( + map(int, cluster["kube_version"].split("v")[1].split(".")) + ) new_version_parts = list(map(int, kube_version.split("v")[1].split("."))) if new_version_parts <= current_version_parts: - return generate_response(False, status.HTTP_400_BAD_REQUEST, "Kube version can only be upgraded", None) + return generate_response( + False, + status.HTTP_400_BAD_REQUEST, + "Kube version can only be upgraded", + None, + ) - host_data = request.app.database["hostCluster"].find_one({"_id": cluster["host_cluster_id"]}) + host_data = request.app.database["hostCluster"].find_one( + {"_id": cluster["host_cluster_id"]} + ) if host_data is None: - return generate_response(False, status.HTTP_404_NOT_FOUND, "Host cluster data not found", None) + return generate_response( + False, status.HTTP_404_NOT_FOUND, "Host cluster data not found", None + ) - subscription = request.app.database["subscription"].find_one({"_id": cluster["subscription_id"]}) + subscription = request.app.database["subscription"].find_one( + {"_id": cluster["subscription_id"]} + ) if subscription is None: - return generate_response(False, status.HTTP_404_NOT_FOUND, f"Subscription with ID {cluster['subscription_id']} not found", None) + return generate_response( + False, + status.HTTP_404_NOT_FOUND, + f"Subscription with ID {cluster['subscription_id']} not found", + None, + ) request.app.database["cluster"].update_one( - {"_id": id}, - {"$set": {"kube_version": kube_version, "status": "Updating"}} + {"_id": id}, {"$set": {"kube_version": kube_version, "status": "Updating"}} ) find_cluster = request.app.database["cluster"].find_one({"_id": id}) @@ -144,26 +229,58 @@ def update_cluster(id: str, request: Request, clusterUpgradeRequest: ClusterUpgr data_content_type="application/json", ) logging.info("Published data for cluster update: %s", payload) - return generate_response(True, status.HTTP_200_OK, "Cluster updated successfully", update_cluster) + return generate_response( + True, status.HTTP_200_OK, "Cluster updated successfully", update_cluster + ) except Exception as e: debug_response(e, "Error occurs on updating cluster", "error") - return generate_response(False, status.HTTP_500_INTERNAL_SERVER_ERROR, "Failed to update cluster", None) + return generate_response( + False, + status.HTTP_500_INTERNAL_SERVER_ERROR, + "Failed to update cluster", + None, + ) -@router.post("/generate-config", response_description="Generate KubeConfig", status_code=status.HTTP_200_OK) -def generate_kube_config(request: Request, generateKubeconfig: GenerateKubeconfig = Body(...)): + +@router.post( + "/generate-config", + response_description="Generate KubeConfig", + status_code=status.HTTP_200_OK, +) +def generate_kube_config( + request: Request, generateKubeconfig: GenerateKubeconfig = Body(...) +): + """ + Generate a KubeConfig file for a cluster. + + Validates expiration time, fetches cluster data, and requests + kubeconfig from the cluster service. Returns as attachment. + """ if extract_time_components(generateKubeconfig.expiryTime) is None: - return generate_response(False, status.HTTP_400_BAD_REQUEST, "Incorrect expiration time format", []) - + return generate_response( + False, status.HTTP_400_BAD_REQUEST, "Incorrect expiration time format", [] + ) + expirationTime = extract_time_components(generateKubeconfig.expiryTime) if expirationTime < 600: - return generate_response(False, status.HTTP_400_BAD_REQUEST, "Expiration time shouldn't be less than 10 min", []) + return generate_response( + False, + status.HTTP_400_BAD_REQUEST, + "Expiration time shouldn't be less than 10 min", + [], + ) clusterId = generateKubeconfig.clusterId cluster = request.app.database["cluster"].find_one({"_id": clusterId}) if cluster is None: - return generate_response(False, status.HTTP_404_NOT_FOUND, f"Cluster with ID {clusterId} not found", []) + return generate_response( + False, + status.HTTP_404_NOT_FOUND, + f"Cluster with ID {clusterId} not found", + [], + ) name = cluster["name"] hostClusterId = cluster["host_cluster_id"] @@ -171,42 +288,81 @@ def generate_kube_config(request: Request, generateKubeconfig: GenerateKubeconfi payload = { "name": name, "hostClusterId": hostClusterId, - "expirationTime": expirationTime + "expirationTime": expirationTime, } - baseUrl = os.getenv('SERVICE_URL') + baseUrl = os.getenv("SERVICE_URL") response = requests.post(baseUrl + "/generate-config", json=payload) if response.status_code == 200: # Check if the request was successful debug_response(response.json(), "Response from generate-config", "info") responseBody = response.json() - with open('file/kubeconfig') as f: - kubeconfig_file = f.read().format(cluster=responseBody["cluster"], clusterCerts=responseBody["clusterCerts"], token=responseBody["token"], server=responseBody["server"]) + with open("file/kubeconfig") as f: + kubeconfig_file = f.read().format( + cluster=responseBody["cluster"], + clusterCerts=responseBody["clusterCerts"], + token=responseBody["token"], + server=responseBody["server"], + ) - response = Response(content=kubeconfig_file, media_type='text/yaml') - response.headers['Content-Disposition'] = f'attachment; filename=kubeconfig.yaml' + response = Response(content=kubeconfig_file, media_type="text/yaml") + response.headers[ + "Content-Disposition" + ] = f"attachment; filename=kubeconfig.yaml" return response else: - debug_response(response.json(), "Error occurs on generating kubeconfig", "error") - return generate_response(False, status.HTTP_500_INTERNAL_SERVER_ERROR, "Internal server error", []) + debug_response( + response.json(), "Error occurs on generating kubeconfig", "error" + ) + return generate_response( + False, status.HTTP_500_INTERNAL_SERVER_ERROR, "Internal server error", [] + ) + @router.get("/{id}", response_description="Get a cluster by id", response_model={}) def find_cluster(id: str, request: Request): + """ + Retrieve detailed information about a cluster by its ID. + + Returns the cluster info along with associated user, subscription, + and host cluster data. + """ try: cluster = request.app.database["cluster"].find_one({"_id": id}) if cluster is None: - return generate_response(False, status.HTTP_404_NOT_FOUND, f"Cluster with ID {id} not found", None) + return generate_response( + False, + status.HTTP_404_NOT_FOUND, + f"Cluster with ID {id} not found", + None, + ) user = request.app.database["user"].find_one({"_id": cluster["user_id"]}) if user is None: - return generate_response(False, status.HTTP_404_NOT_FOUND, f"User with ID {id} not found", None) + return generate_response( + False, status.HTTP_404_NOT_FOUND, f"User with ID {id} not found", None + ) - subscription = request.app.database["subscription"].find_one({"_id": cluster["subscription_id"]}) + subscription = request.app.database["subscription"].find_one( + {"_id": cluster["subscription_id"]} + ) if subscription is None: - return generate_response(False, status.HTTP_404_NOT_FOUND, f"Subscription with ID {id} not found", None ) + return generate_response( + False, + status.HTTP_404_NOT_FOUND, + f"Subscription with ID {id} not found", + None, + ) - host_cluster = request.app.database["hostCluster"].find_one({"_id": cluster["host_cluster_id"]}) + host_cluster = request.app.database["hostCluster"].find_one( + {"_id": cluster["host_cluster_id"]} + ) if host_cluster is None: - return generate_response(False, status.HTTP_404_NOT_FOUND, f"HostCluster with ID {id} not found", None) + return generate_response( + False, + status.HTTP_404_NOT_FOUND, + f"HostCluster with ID {id} not found", + None, + ) response = ClusterResponse( id=cluster["_id"], @@ -216,18 +372,33 @@ def find_cluster(id: str, request: Request): kube_version=cluster["kube_version"], user=user, subscription=subscription, - hostCluster=host_cluster + hostCluster=host_cluster, ) - return{"success": True, - "code": status.HTTP_200_OK, - "message": "Clusters found successfully", - "data": response + return { + "success": True, + "code": status.HTTP_200_OK, + "message": "Clusters found successfully", + "data": response, } except Exception as e: debug_response(e, "Error occurs on retrieving cluster", "error") - return generate_response(False, status.HTTP_500_INTERNAL_SERVER_ERROR, "Failed to retrieve cluster", None) + return generate_response( + False, + status.HTTP_500_INTERNAL_SERVER_ERROR, + "Failed to retrieve cluster", + None, + ) -def get_and_update_status_async(clusters: List[ClusterResponse], request: Request,response_model: ResponseModel): + +def get_and_update_status_async( + clusters: List[ClusterResponse], request: Request, response_model: ResponseModel +): + """ + Fetch and update cluster status asynchronously. + + Iterates over provided clusters, retrieves current status, and updates database. + Returns success or failure for the first cluster processed. + """ for cluster in clusters: try: status = get_status(cluster.name, cluster.hostCluster.id) @@ -239,22 +410,41 @@ def get_and_update_status_async(clusters: List[ClusterResponse], request: Reques # Define the update operation using $set to update a single field update = {"$set": {"status": status.json()["status"]}} request.app.database["cluster"].update_one(filter, update) - return generate_response(True, status.HTTP_200_OK, "Cluster status updated", []) + return generate_response( + True, status.HTTP_200_OK, "Cluster status updated", [] + ) except Exception as e: debug_response(e, "Error occurs on fetching status of cluster", "error") - return generate_response(False, status.HTTP_500_INTERNAL_SERVER_ERROR, "Failed to fetch status of cluster", []) + return generate_response( + False, + status.HTTP_500_INTERNAL_SERVER_ERROR, + "Failed to fetch status of cluster", + [], + ) + @router.get("", response_description="List all cluster", response_model={}) def list_Clusters(request: Request): + """ + List all clusters for the authenticated user. + + Returns basic cluster info along with subscription and host cluster data. + """ try: user_obj: User = request.state.user print("USER_OBJ :: ", user_obj.id) - clusters = list(request.app.database["cluster"].find({"user_id": user_obj.id}).limit(10)) + clusters = list( + request.app.database["cluster"].find({"user_id": user_obj.id}).limit(10) + ) print("Clusters :: ", clusters) responses = [] for cluster in clusters: - host_cluster = request.app.database["hostCluster"].find_one({"_id": cluster["host_cluster_id"]}) - subscription = request.app.database["subscription"].find_one({"_id": cluster["subscription_id"]}) + host_cluster = request.app.database["hostCluster"].find_one( + {"_id": cluster["host_cluster_id"]} + ) + subscription = request.app.database["subscription"].find_one( + {"_id": cluster["subscription_id"]} + ) if host_cluster is not None and subscription is not None: response = ClusterResponse( id=cluster["_id"], @@ -264,65 +454,125 @@ def list_Clusters(request: Request): status=cluster["status"], name=cluster["name"], kube_version=cluster["kube_version"], - hostCluster=host_cluster + hostCluster=host_cluster, ) responses.append(response) - return{"success": True, - "code": status.HTTP_200_OK, - "message": "Clusters listed successfully", - "data": responses + return { + "success": True, + "code": status.HTTP_200_OK, + "message": "Clusters listed successfully", + "data": responses, } except Exception as e: debug_response(e, "Error occurs on listing clusters", "error") - return generate_response(False, status.HTTP_500_INTERNAL_SERVER_ERROR, "Failed to list clusters", []) + return generate_response( + False, status.HTTP_500_INTERNAL_SERVER_ERROR, "Failed to list clusters", [] + ) + -@router.get("/{id}/status", response_description="Get a cluster by id", response_model=ResponseModel) +@router.get( + "/{id}/status", + response_description="Get a cluster by id", + response_model=ResponseModel, +) def get_cluster_status(id: str, request: Request): + """Retrieve the status of a specific cluster by its ID.""" try: cluster = request.app.database["cluster"].find_one({"_id": id}) if cluster is None: - return generate_response(False, status.HTTP_404_NOT_FOUND, f"Cluster with ID {id} not found", None) + return generate_response( + False, + status.HTTP_404_NOT_FOUND, + f"Cluster with ID {id} not found", + None, + ) user = request.app.database["user"].find_one({"_id": cluster["user_id"]}) if user is None: - return generate_response(False, status.HTTP_404_NOT_FOUND, f"User with ID {id} not found", None) + return generate_response( + False, status.HTTP_404_NOT_FOUND, f"User with ID {id} not found", None + ) - host_cluster = request.app.database["hostCluster"].find_one({"_id": cluster["host_cluster_id"]}) + host_cluster = request.app.database["hostCluster"].find_one( + {"_id": cluster["host_cluster_id"]} + ) if host_cluster is None: - return generate_response(False, status.HTTP_404_NOT_FOUND, f"HostCluster with ID {id} not found", None) + return generate_response( + False, + status.HTTP_404_NOT_FOUND, + f"HostCluster with ID {id} not found", + None, + ) response = get_status(cluster["name"], host_cluster["_id"]) if response.status_code == 200: - return generate_response(True, status.HTTP_200_OK, "Cluster status retrieved", response.json()) + return generate_response( + True, status.HTTP_200_OK, "Cluster status retrieved", response.json() + ) else: - return generate_response(False, status.HTTP_500_INTERNAL_SERVER_ERROR, "Internal server error", None) + return generate_response( + False, + status.HTTP_500_INTERNAL_SERVER_ERROR, + "Internal server error", + None, + ) except Exception as e: debug_response(e, "Error occurs on retrieving cluster status", "error") - return generate_response(False, status.HTTP_500_INTERNAL_SERVER_ERROR, "Failed to retrieve cluster status", None) + return generate_response( + False, + status.HTTP_500_INTERNAL_SERVER_ERROR, + "Failed to retrieve cluster status", + None, + ) + def get_status(name: str, hostClusterId): + """Request cluster status from host cluster service.""" payload = { "name": name, "hostClusterId": hostClusterId, } - baseUrl = os.getenv('SERVICE_URL') + baseUrl = os.getenv("SERVICE_URL") return requests.post(baseUrl + "/host-cluster/cluster/status", json=payload) -@router.patch("/{id}/start", response_description="Start cluster by id", status_code=status.HTTP_200_OK, response_model=ResponseModel) + +@router.patch( + "/{id}/start", + response_description="Start cluster by id", + status_code=status.HTTP_200_OK, + response_model=ResponseModel, +) def start_cluster(id: str, request: Request): + """ + Start a cluster by its ID. + + Publishes a 'cluster-start' event via Dapr. + """ try: cluster = request.app.database["cluster"].find_one({"_id": id}) if cluster is None: - return generate_response(False, status.HTTP_404_NOT_FOUND, f"Cluster with ID {id} not found", None) + return generate_response( + False, + status.HTTP_404_NOT_FOUND, + f"Cluster with ID {id} not found", + None, + ) - host_cluster = request.app.database["hostCluster"].find_one({"_id": cluster["host_cluster_id"]}) + host_cluster = request.app.database["hostCluster"].find_one( + {"_id": cluster["host_cluster_id"]} + ) if host_cluster is None: - return generate_response(False, status.HTTP_404_NOT_FOUND, f"HostCluster with ID {id} not found", None) + return generate_response( + False, + status.HTTP_404_NOT_FOUND, + f"HostCluster with ID {id} not found", + None, + ) with DaprClient() as client: payload = { "host_cluster_id": host_cluster["_id"], - "cluster_name": cluster["name"] + "cluster_name": cluster["name"], } client.publish_event( pubsub_name="messagebus", @@ -332,26 +582,56 @@ def start_cluster(id: str, request: Request): ) logging.info("Published data: " + json.dumps(payload)) debug_response(payload, "Published data", "info") - return generate_response(True, status.HTTP_200_OK, "Sent command for starting cluster", None) + return generate_response( + True, status.HTTP_200_OK, "Sent command for starting cluster", None + ) except Exception as e: debug_response(e, "Error occurs on starting cluster", "error") - return generate_response(False, status.HTTP_500_INTERNAL_SERVER_ERROR, "Failed to start cluster", None) + return generate_response( + False, + status.HTTP_500_INTERNAL_SERVER_ERROR, + "Failed to start cluster", + None, + ) + -@router.patch("/{id}/stop", response_description="Stop cluster by id", status_code=status.HTTP_200_OK, response_model=ResponseModel) +@router.patch( + "/{id}/stop", + response_description="Stop cluster by id", + status_code=status.HTTP_200_OK, + response_model=ResponseModel, +) def stop_cluster(id: str, request: Request): + """ + Stop a cluster by its ID. + + Publishes a 'cluster-stop' event via Dapr. + """ try: cluster = request.app.database["cluster"].find_one({"_id": id}) if cluster is None: - return generate_response(False, status.HTTP_404_NOT_FOUND, f"Cluster with ID {id} not found", None) + return generate_response( + False, + status.HTTP_404_NOT_FOUND, + f"Cluster with ID {id} not found", + None, + ) - host_cluster = request.app.database["hostCluster"].find_one({"_id": cluster["host_cluster_id"]}) + host_cluster = request.app.database["hostCluster"].find_one( + {"_id": cluster["host_cluster_id"]} + ) if host_cluster is None: - return generate_response(False, status.HTTP_404_NOT_FOUND, f"HostCluster with ID {id} not found", None) + return generate_response( + False, + status.HTTP_404_NOT_FOUND, + f"HostCluster with ID {id} not found", + None, + ) with DaprClient() as client: payload = { "host_cluster_id": host_cluster["_id"], - "cluster_name": cluster["name"] + "cluster_name": cluster["name"], } client.publish_event( pubsub_name="messagebus", @@ -361,21 +641,48 @@ def stop_cluster(id: str, request: Request): ) logging.info("Published data: " + json.dumps(payload)) debug_response(payload, "Published data", "info") - return generate_response(True, status.HTTP_200_OK, "Sent command for stopping cluster", None) + return generate_response( + True, status.HTTP_200_OK, "Sent command for stopping cluster", None + ) except Exception as e: debug_response(e, "Error occurs on stopping cluster", "error") - return generate_response(False, status.HTTP_500_INTERNAL_SERVER_ERROR, "Failed to stop cluster", None) + return generate_response( + False, status.HTTP_500_INTERNAL_SERVER_ERROR, "Failed to stop cluster", None + ) -@router.delete("/{id}", response_description="Delete cluster by id", status_code=status.HTTP_202_ACCEPTED, response_model=ResponseModel) + +@router.delete( + "/{id}", + response_description="Delete cluster by id", + status_code=status.HTTP_202_ACCEPTED, + response_model=ResponseModel, +) def delete_cluster(id: str, request: Request, response: Response): + """ + Delete a cluster by its ID. + + Publishes a 'cluster-delete' event via Dapr if deletion succeeds. + """ try: cluster = request.app.database["cluster"].find_one({"_id": id}) if cluster is None: - return generate_response(False, status.HTTP_404_NOT_FOUND, f"Cluster with ID {id} not found", None) + return generate_response( + False, + status.HTTP_404_NOT_FOUND, + f"Cluster with ID {id} not found", + None, + ) - host_cluster = request.app.database["hostCluster"].find_one({"_id": cluster["host_cluster_id"]}) + host_cluster = request.app.database["hostCluster"].find_one( + {"_id": cluster["host_cluster_id"]} + ) if host_cluster is None: - return generate_response(False, status.HTTP_404_NOT_FOUND, f"HostCluster with ID {id} not found", None) + return generate_response( + False, + status.HTTP_404_NOT_FOUND, + f"HostCluster with ID {id} not found", + None, + ) delete_result = request.app.database["cluster"].delete_one({"_id": id}) if delete_result.deleted_count == 1: @@ -383,7 +690,7 @@ def delete_cluster(id: str, request: Request, response: Response): with DaprClient() as client: payload = { "host_cluster_id": host_cluster["_id"], - "cluster_name": cluster["name"] + "cluster_name": cluster["name"], } client.publish_event( pubsub_name="messagebus", @@ -391,11 +698,28 @@ def delete_cluster(id: str, request: Request, response: Response): data=json.dumps(payload), data_content_type="application/json", ) - return generate_response(True, status.HTTP_202_ACCEPTED, f"Cluster with ID {id} deleted", None) + return generate_response( + True, + status.HTTP_202_ACCEPTED, + f"Cluster with ID {id} deleted", + None, + ) except Exception as e: debug_response(e, "Error occurs on publishing delete event", "error") - return generate_response(False, status.HTTP_500_INTERNAL_SERVER_ERROR, "Failed to publish delete event", None) - return generate_response(False, status.HTTP_404_NOT_FOUND, f"Cluster with ID {id} not found", None) + return generate_response( + False, + status.HTTP_500_INTERNAL_SERVER_ERROR, + "Failed to publish delete event", + None, + ) + return generate_response( + False, status.HTTP_404_NOT_FOUND, f"Cluster with ID {id} not found", None + ) except Exception as e: debug_response(e, "Error occurs on deleting cluster", "error") - return generate_response(False, status.HTTP_500_INTERNAL_SERVER_ERROR, "Failed to delete cluster", None) + return generate_response( + False, + status.HTTP_500_INTERNAL_SERVER_ERROR, + "Failed to delete cluster", + None, + ) diff --git a/cluster-api/routes/host_cluster.py b/cluster-api/routes/host_cluster.py index b84d590..5db2bdd 100644 --- a/cluster-api/routes/host_cluster.py +++ b/cluster-api/routes/host_cluster.py @@ -1,11 +1,18 @@ +""" +Host Cluster API Routes. + +This module defines FastAPI routes for creating, listing, retrieving, +and deleting host clusters. It handles validation, authentication, +database operations, and response formatting. +""" + import logging -from fastapi import APIRouter, Body, Request, Response, HTTPException, status -from fastapi.encoders import jsonable_encoder from typing import List -from fastapi.responses import JSONResponse from dto.host_cluster_request import HostClusterRequest - +from fastapi import APIRouter, Body, HTTPException, Request, Response, status +from fastapi.encoders import jsonable_encoder +from fastapi.responses import JSONResponse from models.common_response import ResponseModel from models.host_cluster import HostCluster from models.user import User @@ -15,50 +22,137 @@ router = APIRouter() -@router.post("/", response_description="Create a cluster", status_code=status.HTTP_201_CREATED, response_model=ResponseModel) + +@router.post( + "/", + response_description="Create a cluster", + status_code=status.HTTP_201_CREATED, + response_model=ResponseModel, +) def create_Cluster(request: Request, clusterRequest: HostClusterRequest = Body(...)): + """ + Create a new host cluster. + + Validates the cluster name, associates it with the authenticated user, + inserts it into the database, and returns the created object. + """ name_status, error_message = is_valid_url_name(clusterRequest.name) if not name_status and error_message is not None: - return generate_response(False, status.HTTP_400_BAD_REQUEST, f"Failed to create host cluster {error_message}", None) + return generate_response( + False, + status.HTTP_400_BAD_REQUEST, + f"Failed to create host cluster {error_message}", + None, + ) try: user_obj: User = request.state.user host_cluster_obj = host_Cluster_from_dict(clusterRequest, user_obj.id) - new_cluster = request.app.database["hostCluster"].insert_one(jsonable_encoder(host_cluster_obj)) + new_cluster = request.app.database["hostCluster"].insert_one( + jsonable_encoder(host_cluster_obj) + ) created_cluster = request.app.database["hostCluster"].find_one( {"_id": new_cluster.inserted_id} ) - return generate_response(True, status.HTTP_201_CREATED, "Host cluster created successfully", created_cluster) + return generate_response( + True, + status.HTTP_201_CREATED, + "Host cluster created successfully", + created_cluster, + ) except Exception as e: debug_response(e, "Error occurs on adding host cluster", "error") - return generate_response(False, status.HTTP_500_INTERNAL_SERVER_ERROR, "Failed to create host cluster", None) + return generate_response( + False, + status.HTTP_500_INTERNAL_SERVER_ERROR, + "Failed to create host cluster", + None, + ) -@router.get("/", response_description="List all host cluster", response_model=ResponseModel) + +@router.get( + "/", response_description="List all host cluster", response_model=ResponseModel +) def list_host_cluster(request: Request): + """ + Retrieve a list of all host clusters. + + Returns a list of up to 100 host clusters from the database. + """ try: hostCluster = list(request.app.database["hostCluster"].find(limit=100)) - return generate_response(True, status.HTTP_200_OK, "List all host clusters", hostCluster) + return generate_response( + True, status.HTTP_200_OK, "List all host clusters", hostCluster + ) except Exception as e: debug_response(e, "Error occurs on listing host clusters", "error") - return generate_response(False, status.HTTP_500_INTERNAL_SERVER_ERROR, "Failed to list host clusters", None) + return generate_response( + False, + status.HTTP_500_INTERNAL_SERVER_ERROR, + "Failed to list host clusters", + None, + ) + -@router.get("/{id}", response_description="Get a single host cluster by id", response_model=ResponseModel) +@router.get( + "/{id}", + response_description="Get a single host cluster by id", + response_model=ResponseModel, +) def find_host_cluster(id: str, request: Request): + """ + Retrieve a host cluster by its ID. + + Returns the host cluster data if found, otherwise a 404 error. + """ try: host_cluster = request.app.database["hostCluster"].find_one({"_id": id}) if host_cluster is not None: - return generate_response(True, status.HTTP_200_OK, "Host cluster found", host_cluster) - return generate_response(False, status.HTTP_404_NOT_FOUND, f"Host cluster with ID {id} not found", None) + return generate_response( + True, status.HTTP_200_OK, "Host cluster found", host_cluster + ) + return generate_response( + False, + status.HTTP_404_NOT_FOUND, + f"Host cluster with ID {id} not found", + None, + ) except Exception as e: debug_response(e, "Error occurs on retrieving host cluster", "error") - return generate_response(False, status.HTTP_500_INTERNAL_SERVER_ERROR, "Failed to retrieve host cluster", None) + return generate_response( + False, + status.HTTP_500_INTERNAL_SERVER_ERROR, + "Failed to retrieve host cluster", + None, + ) + @router.delete("/{id}", response_description="Delete a host cluster") def delete_subscription(id: str, request: Request, response: ResponseModel): + """ + Delete a host cluster by its ID. + + Removes the cluster from the database and returns success or not found response. + """ try: delete_result = request.app.database["hostCluster"].delete_one({"_id": id}) if delete_result.deleted_count == 1: - return generate_response(True, status.HTTP_204_NO_CONTENT, f"Host cluster with ID {id} deleted", None) - return generate_response(False, status.HTTP_404_NOT_FOUND, f"Host cluster with ID {id} not found", None) + return generate_response( + True, + status.HTTP_204_NO_CONTENT, + f"Host cluster with ID {id} deleted", + None, + ) + return generate_response( + False, + status.HTTP_404_NOT_FOUND, + f"Host cluster with ID {id} not found", + None, + ) except Exception as e: debug_response(e, "Error occurs on deleting host cluster", "error") - return generate_response(False, status.HTTP_500_INTERNAL_SERVER_ERROR, "Failed to delete host cluster", None) \ No newline at end of file + return generate_response( + False, + status.HTTP_500_INTERNAL_SERVER_ERROR, + "Failed to delete host cluster", + None, + ) diff --git a/cluster-api/routes/kube_list.py b/cluster-api/routes/kube_list.py index 838cacb..6264d9e 100644 --- a/cluster-api/routes/kube_list.py +++ b/cluster-api/routes/kube_list.py @@ -1,74 +1,192 @@ -import uuid -from fastapi import APIRouter, Body, Request, HTTPException, status, Response -from pydantic import BaseModel -from fastapi.encoders import jsonable_encoder +""" +KubeVersion API Routes. + +This module defines FastAPI routes for creating, listing, updating, +and deleting kubeversions. It handles validation, database operations, +and response formatting. +""" + +# pylint: disable=used-before-assignment + import logging -from bson import ObjectId +import uuid from typing import List + +from bson import ObjectId from dto.kube_version_request import KubeVersionRequest +from fastapi import APIRouter, Body, HTTPException, Request, Response, status +from fastapi.encoders import jsonable_encoder from models.common_response import ResponseModel from models.kube_version import KubeVersion +from pydantic import BaseModel from utills.common_response import debug_response, generate_response router = APIRouter() + # Create a new kubeversion -@router.post("/", response_description="Create a kubeversion", status_code=status.HTTP_201_CREATED, response_model=ResponseModel) -def create_kubeversion(request: Request, clusterRequest: KubeVersionRequest = Body(...)): +@router.post( + "/", + response_description="Create a kubeversion", + status_code=status.HTTP_201_CREATED, + response_model=ResponseModel, +) +def create_kubeversion( + request: Request, clusterRequest: KubeVersionRequest = Body(...) +): + """ + Create a new kubeversion entry in the database. + + Args: + request (Request): FastAPI request object. + cluster_request (KubeVersionRequest): KubeVersion data. + + Returns: + ResponseModel: Success or failure response. + """ debug_response(clusterRequest.dict(), "Received request body", "debug") try: kubeversion_data = jsonable_encoder(clusterRequest) - kubeversion_data["_id"] = str(uuid.uuid4()) # Generate a UUID and use it as the _id + kubeversion_data["_id"] = str( + uuid.uuid4() + ) # Generate a UUID and use it as the _id debug_response(kubeversion_data) - new_kubeversion = request.app.database["kubeversion"].insert_one(kubeversion_data) - inserted_kubeversion = request.app.database["kubeversion"].find_one({"_id": new_kubeversion.inserted_id}) + new_kubeversion = request.app.database["kubeversion"].insert_one( + kubeversion_data + ) + inserted_kubeversion = request.app.database["kubeversion"].find_one( + {"_id": new_kubeversion.inserted_id} + ) if inserted_kubeversion: inserted_kubeversion["_id"] = str(inserted_kubeversion["_id"]) - return generate_response(True, status.HTTP_201_CREATED, "Kubeversion created successfully", inserted_kubeversion) + return generate_response( + True, + status.HTTP_201_CREATED, + "Kubeversion created successfully", + inserted_kubeversion, + ) except Exception as e: debug_response(e, "Error occurs on creating kubeversion", "error") - return generate_response(False, status.HTTP_500_INTERNAL_SERVER_ERROR, "Failed to create kubeversion", None) + return generate_response( + False, + status.HTTP_500_INTERNAL_SERVER_ERROR, + "Failed to create kubeversion", + None, + ) + # List all kubeversions -@router.get("/", response_description="List all kubeversion", response_model=ResponseModel) +@router.get( + "/", response_description="List all kubeversion", response_model=ResponseModel +) def list_kubeversions(request: Request): + """ + List all kubeversions stored in the database. + + Args: + request (Request): FastAPI request object. + + Returns: + ResponseModel: List of kubeversions. + """ try: kubeversions = list(request.app.database["kubeversion"].find(limit=100)) for kubeversion in kubeversions: kubeversion["_id"] = str(kubeversion["_id"]) - return generate_response(True, status.HTTP_200_OK, "List all kubeversions", kubeversions) + return generate_response( + True, status.HTTP_200_OK, "List all kubeversions", kubeversions + ) except Exception as e: debug_response(e, "Failed to retrieve kubeversions", "error") - return generate_response(False, status.HTTP_500_INTERNAL_SERVER_ERROR, "Internal Server Error", []) + return generate_response( + False, status.HTTP_500_INTERNAL_SERVER_ERROR, "Internal Server Error", [] + ) + # Update an existing kubeversion -@router.put("/{id}", response_description="Update kubeversion", response_model=ResponseModel) -def update_kubeversion(id: str, request: Request, clusterRequest: KubeVersionRequest = Body(...)): +@router.put( + "/{id}", response_description="Update kubeversion", response_model=ResponseModel +) +def update_kubeversion( + id: str, request: Request, clusterRequest: KubeVersionRequest = Body(...) +): + """ + Update an existing kubeversion by ID. + + Args: + id (str): KubeVersion ID. + request (Request): FastAPI request object. + cluster_request (KubeVersionRequest): Updated kubeversion data. + + Returns: + ResponseModel: Success or failure response. + """ try: kubeversion_data = jsonable_encoder(clusterRequest) - update_result = request.app.database["kubeversion"].update_one({"_id": ObjectId(id)}, {"$set": kubeversion_data}) + update_result = request.app.database["kubeversion"].update_one( + {"_id": ObjectId(id)}, {"$set": kubeversion_data} + ) if update_result.matched_count == 0: debug_response(e, "Failed to retrieve kubeversions", "error") - return generate_response(True, status.HTTP_404_NOT_FOUND, f"kubeversion with ID {id} not found", None) + return generate_response( + True, + status.HTTP_404_NOT_FOUND, + f"kubeversion with ID {id} not found", + None, + ) - updated_kubeversion = request.app.database["kubeversion"].find_one({"_id": ObjectId(id)}) + updated_kubeversion = request.app.database["kubeversion"].find_one( + {"_id": ObjectId(id)} + ) if updated_kubeversion: updated_kubeversion["_id"] = str(updated_kubeversion["_id"]) - return generate_response(True, status.HTTP_202_ACCEPTED, f"kubeversion with {id} updated", updated_kubeversion) + return generate_response( + True, + status.HTTP_202_ACCEPTED, + f"kubeversion with {id} updated", + updated_kubeversion, + ) except Exception as e: debug_response(e, "Error occurs on updating kubeversion", "error") - return generate_response(False, status.HTTP_500_INTERNAL_SERVER_ERROR, "Internal Server Error", None) + return generate_response( + False, status.HTTP_500_INTERNAL_SERVER_ERROR, "Internal Server Error", None + ) + # Delete a kubeversion -@router.delete("/{id}", response_description="Delete kubeversion", response_model=ResponseModel) +@router.delete( + "/{id}", response_description="Delete kubeversion", response_model=ResponseModel +) def delete_kubeversion(id: str, request: Request, response: Response): + """ + Delete a kubeversion by ID. + + Args: + id (str): KubeVersion ID. + request (Request): FastAPI request object. + response (Response): FastAPI response object. + + Returns: + ResponseModel: Success or failure response. + """ try: - delete_result = request.app.database["kubeversion"].delete_one({"_id": ObjectId(id)}) + delete_result = request.app.database["kubeversion"].delete_one( + {"_id": ObjectId(id)} + ) if delete_result.deleted_count == 1: response.status_code = status.HTTP_204_NO_CONTENT debug_response(id, "kubeversion deleted", "info") - return generate_response(True, status.HTTP_202_ACCEPTED, f"kubeversion with {id} deleted", response) - return generate_response(True, status.HTTP_404_NOT_FOUND, "kubeversion with ID {id} not found", None) + return generate_response( + True, + status.HTTP_202_ACCEPTED, + f"kubeversion with {id} deleted", + response, + ) + return generate_response( + True, status.HTTP_404_NOT_FOUND, "kubeversion with ID {id} not found", None + ) except Exception as e: debug_response(e, "Error occurs on deleting kubeversion", "error") - return generate_response(False, status.HTTP_500_INTERNAL_SERVER_ERROR, "Internal Server Error", None) + return generate_response( + False, status.HTTP_500_INTERNAL_SERVER_ERROR, "Internal Server Error", None + ) diff --git a/cluster-api/routes/public.py b/cluster-api/routes/public.py index 316001f..4e13b60 100644 --- a/cluster-api/routes/public.py +++ b/cluster-api/routes/public.py @@ -1,28 +1,45 @@ -from datetime import datetime -import os +""" +Cluster Subscription, Status, and Login API Routes. + +Provides endpoints to list subscriptions, update cluster status with WebSocket +notifications, and perform user login via Keycloak. +""" + import asyncio -from fastapi import APIRouter, Body, Request, HTTPException, status -from fastapi.encoders import jsonable_encoder +import os +from datetime import datetime from typing import List + from dto.cluster_status_request import ClusterStatusRequest +from fastapi import APIRouter, Body, HTTPException, Request, status +from fastapi.encoders import jsonable_encoder from keycloak import KeycloakOpenID from models.common_response import ResponseModel from models.subscription import Subscription from models.user import UserLogin +from routes.websocket import ( # Import both functions + broadcast_message, + send_message_to_user, +) from utills.common_response import debug_response, generate_response -from routes.websocket import send_message_to_user, broadcast_message # Import both functions router = APIRouter() -@router.get("/subscriptions", response_description="List all subscriptions", response_model=ResponseModel) + +@router.get( + "/subscriptions", + response_description="List all subscriptions", + response_model=ResponseModel, +) def list_subscription(request: Request): + """Retrieve a list of subscriptions.""" try: subscriptions = list(request.app.database["subscription"].find(limit=100)) res = generate_response( success=True, code=status.HTTP_200_OK, message="List all subscriptions", - data=subscriptions + data=subscriptions, ) return res @@ -32,11 +49,18 @@ def list_subscription(request: Request): success=False, code=HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR), message="Failed to retrieve subscriptions", - data=[] + data=[], ) -@router.patch("/cluster-status", response_description="Post pod Status", status_code=status.HTTP_200_OK) -async def update_cluster_status(request: Request, data: ClusterStatusRequest = Body(...)): + +@router.patch( + "/cluster-status", + response_description="Post pod Status", + status_code=status.HTTP_200_OK, +) +async def update_cluster_status( + request: Request, data: ClusterStatusRequest = Body(...) +): """Update cluster status and notify the cluster owner via WebSocket.""" try: # Input validation @@ -45,17 +69,17 @@ async def update_cluster_status(request: Request, data: ClusterStatusRequest = B success=False, code=status.HTTP_400_BAD_REQUEST, message="Cluster ID is required", - data=None + data=None, ) - + if not data.status: return generate_response( success=False, code=status.HTTP_400_BAD_REQUEST, message="Status is required", - data=None + data=None, ) - + # Find cluster by ID cluster = request.app.database["cluster"].find_one({"_id": data.id}) if cluster is None: @@ -63,25 +87,29 @@ async def update_cluster_status(request: Request, data: ClusterStatusRequest = B success=False, code=status.HTTP_404_NOT_FOUND, message=f"Cluster with ID {data.id} not found", - data=None + data=None, ) - + # Update cluster status in database filter = {"_id": data.id} update = {"$set": {"status": data.status, "updated_at": datetime.utcnow()}} update_result = request.app.database["cluster"].update_one(filter, update) - + if update_result.modified_count == 0: # No changes were made print(f"No changes made to cluster {data.id} status") - + # Extract user_id from cluster document user_id = None if "user_id" in cluster: user_id = cluster["user_id"] - elif "user" in cluster and isinstance(cluster["user"], dict) and "id" in cluster["user"]: + elif ( + "user" in cluster + and isinstance(cluster["user"], dict) + and "id" in cluster["user"] + ): user_id = cluster["user"]["id"] - + # Send WebSocket notification if we have a user ID notification_sent = False if user_id: @@ -91,13 +119,15 @@ async def update_cluster_status(request: Request, data: ClusterStatusRequest = B "cluster_id": data.id, "cluster_name": cluster.get("name", ""), "status": data.status, - "timestamp": datetime.utcnow().isoformat() + "timestamp": datetime.utcnow().isoformat(), } - + # Send message to user notification_sent = await send_message_to_user(user_id, message) - print(f"WebSocket notification {'sent' if notification_sent else 'not sent'} to user {user_id}") - + print( + f"WebSocket notification {'sent' if notification_sent else 'not sent'} to user {user_id}" + ) + # Return success response return { "success": True, @@ -106,27 +136,34 @@ async def update_cluster_status(request: Request, data: ClusterStatusRequest = B "data": { "id": data.id, "status": data.status, - "notification_sent": notification_sent - } + "notification_sent": notification_sent, + }, } - + except Exception as e: debug_response(e, "Error occurs on updating cluster status", "error") return generate_response( success=False, code=status.HTTP_500_INTERNAL_SERVER_ERROR, message="Failed to update cluster status", - data=None + data=None, ) -@router.post("/login", response_description="Login", status_code=status.HTTP_200_OK, response_model=ResponseModel) -def login( data: UserLogin = Body(...)): + +@router.post( + "/login", + response_description="Login", + status_code=status.HTTP_200_OK, + response_model=ResponseModel, +) +def login(data: UserLogin = Body(...)): + """Authenticate user via Keycloak and return token.""" try: debug_response(data.dict(), "Received request body", "debug") keycloak_openid = KeycloakOpenID( - server_url=os.getenv('KEYCLOAK_URL'), - realm_name=os.getenv('REALM_NAME'), - client_id=os.getenv('CLIENT_ID'), + server_url=os.getenv("KEYCLOAK_URL"), + realm_name=os.getenv("REALM_NAME"), + client_id=os.getenv("CLIENT_ID"), ) token = keycloak_openid.token(data.userName, data.password) debug_response(token, "Received token", "debug") @@ -134,7 +171,7 @@ def login( data: UserLogin = Body(...)): success=True, code=status.HTTP_200_OK, message="Login successful", - data=token + data=token, ) except Exception as e: debug_response(e, "Error occurs on login", "error") @@ -142,5 +179,5 @@ def login( data: UserLogin = Body(...)): success=False, code=status.HTTP_401_UNAUTHORIZED, message="Failed to login", - data=None + data=None, ) diff --git a/cluster-api/routes/subscription.py b/cluster-api/routes/subscription.py index d1c9558..84c502b 100644 --- a/cluster-api/routes/subscription.py +++ b/cluster-api/routes/subscription.py @@ -1,23 +1,41 @@ +""" +Subscription API Routes. + +Provides endpoints to create, list, retrieve, and delete subscriptions. +""" + import os -from fastapi import APIRouter, Body, Request, Response, HTTPException, status -from fastapi.encoders import jsonable_encoder from typing import List +from fastapi import APIRouter, Body, HTTPException, Request, Response, status +from fastapi.encoders import jsonable_encoder from models.common_response import ResponseModel from models.subscription import Subscription from utills.common_response import debug_response, generate_response router = APIRouter() -@router.post("/", response_description="Create a new subscription", status_code=status.HTTP_201_CREATED, response_model=ResponseModel) + +@router.post( + "/", + response_description="Create a new subscription", + status_code=status.HTTP_201_CREATED, + response_model=ResponseModel, +) def create_subscription(request: Request, subscription: Subscription = Body(...)): + """Create a new subscription in the database.""" try: subscription = jsonable_encoder(subscription) new_subscription = request.app.database["subscription"].insert_one(subscription) created_subscription = request.app.database["subscription"].find_one( {"_id": new_subscription.inserted_id} ) - res = generate_response(True,status.HTTP_201_CREATED, "Subscription created successfully", created_subscription) + res = generate_response( + True, + status.HTTP_201_CREATED, + "Subscription created successfully", + created_subscription, + ) return res except Exception as e: debug_response(e) @@ -25,11 +43,15 @@ def create_subscription(request: Request, subscription: Subscription = Body(...) success=False, code=HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR), message="Failed to create subscription", - data=None + data=None, ) -@router.get("", response_description="List all subscriptions", response_model=ResponseModel) + +@router.get( + "", response_description="List all subscriptions", response_model=ResponseModel +) def list_subscription(request: Request): + """Retrieve a list of all subscriptions.""" try: subscriptions = list(request.app.database["subscription"].find(limit=100)) @@ -37,7 +59,7 @@ def list_subscription(request: Request): success=True, code=status.HTTP_200_OK, message="List all subscriptions", - data=subscriptions + data=subscriptions, ) return res @@ -47,36 +69,61 @@ def list_subscription(request: Request): success=False, code=HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR), message="Failed to retrieve subscriptions", - data={} + data={}, ) -@router.get("/{id}", response_description="Get a single subscription by id", response_model=ResponseModel) + +@router.get( + "/{id}", + response_description="Get a single subscription by id", + response_model=ResponseModel, +) def find_subscription(id: str, request: Request): + """Retrieve a subscription by its ID.""" try: subscription = request.app.database["subscription"].find_one({"_id": id}) if subscription is not None: - return generate_response(True,status.HTTP_200_OK, "Subscription found", subscription) - return generate_response(True,status.HTTP_204_NO_CONTENT, "Subscription not found", None) + return generate_response( + True, status.HTTP_200_OK, "Subscription found", subscription + ) + return generate_response( + True, status.HTTP_204_NO_CONTENT, "Subscription not found", None + ) except Exception as e: debug_response(e) return generate_response( success=False, code=HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR), message="Failed to retrieve subscription", - data=None + data=None, ) -@router.delete("/{id}", response_description="Delete a subscription", status_code=status.HTTP_202_ACCEPTED, response_model=ResponseModel) + + +@router.delete( + "/{id}", + response_description="Delete a subscription", + status_code=status.HTTP_202_ACCEPTED, + response_model=ResponseModel, +) def delete_subscription(id: str, request: Request): + """Delete a subscription by its ID.""" try: delete_result = request.app.database["subscription"].delete_one({"_id": id}) if delete_result.deleted_count == 1: - return generate_response(True, status.HTTP_204_NO_CONTENT, "Subscription deleted successfully", None) - return generate_response(True, status.HTTP_404_NOT_FOUND, "Subscription not found", None) + return generate_response( + True, + status.HTTP_204_NO_CONTENT, + "Subscription deleted successfully", + None, + ) + return generate_response( + True, status.HTTP_404_NOT_FOUND, "Subscription not found", None + ) except Exception as e: debug_response(e) return generate_response( success=False, code=HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR), message="Failed to delete subscription", - data=None + data=None, ) diff --git a/cluster-api/routes/user.py b/cluster-api/routes/user.py index e1cad07..f2f5cbe 100644 --- a/cluster-api/routes/user.py +++ b/cluster-api/routes/user.py @@ -1,124 +1,206 @@ +""" +User API Routes with Keycloak Integration. + +Provides endpoints to list users, verify tokens, +check subscriptions, and request subscriptions. +""" + import os -from fastapi import APIRouter, Body, Request, Response, HTTPException, status -from fastapi.encoders import jsonable_encoder from typing import List + import requests +from fastapi import APIRouter, Body, HTTPException, Request, Response, status +from fastapi.encoders import jsonable_encoder +from keycloak import KeycloakAdmin, KeycloakOpenID from middleware.middleware import is_authenticated from models.common_response import ResponseModel from models.user import User, UserLogin -from keycloak import KeycloakAdmin, KeycloakOpenID from utills.common_response import debug_response, generate_response router = APIRouter() # Define your Keycloak configuration & constant values -KEYCLOAK_URL = os.getenv('KEYCLOAK_URL') -REALM_NAME = os.getenv('REALM_NAME') -ADMIN_CLIENT_ID = os.getenv('ADMIN_CLIENT_ID') -ADMIN_CLIENT_SECRET = os.getenv('ADMIN_CLIENT_SECRET') -REQUEST_GROUP_NAME = os.getenv('REQUEST_GROUP_NAME') +KEYCLOAK_URL = os.getenv("KEYCLOAK_URL") +REALM_NAME = os.getenv("REALM_NAME") +ADMIN_CLIENT_ID = os.getenv("ADMIN_CLIENT_ID") +ADMIN_CLIENT_SECRET = os.getenv("ADMIN_CLIENT_SECRET") +REQUEST_GROUP_NAME = os.getenv("REQUEST_GROUP_NAME") # Initialize KeycloakOpenID -keycloak_admin = KeycloakAdmin(server_url=KEYCLOAK_URL, - client_id=ADMIN_CLIENT_ID, - realm_name=REALM_NAME, - client_secret_key=ADMIN_CLIENT_SECRET) +keycloak_admin = KeycloakAdmin( + server_url=KEYCLOAK_URL, + client_id=ADMIN_CLIENT_ID, + realm_name=REALM_NAME, + client_secret_key=ADMIN_CLIENT_SECRET, +) + @router.get("/", response_description="List all users", response_model=ResponseModel) def list_user(request: Request): + """Retrieve a list of all users from the database.""" try: users = list(request.app.database["user"].find(limit=100)) return generate_response(True, status.HTTP_200_OK, "List of users", users) except Exception as e: debug_response(e, "Error occurs on listing users", "error") - return generate_response(False, status.HTTP_500_INTERNAL_SERVER_ERROR, "Failed to list users", []) + return generate_response( + False, status.HTTP_500_INTERNAL_SERVER_ERROR, "Failed to list users", [] + ) -@router.get("/subscription-check", response_description="subscription check", status_code=status.HTTP_202_ACCEPTED, response_model=ResponseModel) -def subscription_check(request: Request): + +@router.get( + "/subscription-check", + response_description="subscription check", + status_code=status.HTTP_202_ACCEPTED, + response_model=ResponseModel, +) +def subscription_check(request: Request): + """ + Check subscription status and Keycloak groups/roles + for the authenticated user. + """ try: authorization_header = request.headers.get("Authorization", "") token = authorization_header.split("Bearer ")[1] user_info = is_authenticated(token) - user_groups = keycloak_admin.get_user_groups(user_id=user_info['sub']) - user = request.app.database["user"].find_one({"email": user_info['email']}) + user_groups = keycloak_admin.get_user_groups(user_id=user_info["sub"]) + user = request.app.database["user"].find_one({"email": user_info["email"]}) response = { - "user_id": user['_id'], - "username": user_info['name'], - "email": user_info['email'], + "user_id": user["_id"], + "username": user_info["name"], + "email": user_info["email"], "user_groups": user_groups, - "realm_roles": user_info['realm_access']['roles'] + "realm_roles": user_info["realm_access"]["roles"], } - return generate_response(True, status.HTTP_202_ACCEPTED, "Subscription check successful", response) + return generate_response( + True, status.HTTP_202_ACCEPTED, "Subscription check successful", response + ) except Exception as e: debug_response(e, "Error occurs on subscription check", "error") - return generate_response(False, status.HTTP_400_BAD_REQUEST, "Failed to check subscription", []) + return generate_response( + False, status.HTTP_400_BAD_REQUEST, "Failed to check subscription", [] + ) + -@router.get("/{id}", response_description="Get a single user by id",response_model=ResponseModel) +@router.get( + "/{id}", + response_description="Get a single user by id", + response_model=ResponseModel, +) def find_user(id: str, request: Request): + """Retrieve a single user by ID.""" try: user = request.app.database["user"].find_one({"_id": id}) if user is not None: return generate_response(True, status.HTTP_200_OK, "User found", [user]) - return generate_response(False, status.HTTP_404_NOT_FOUND, f"User with ID {id} not found", []) + return generate_response( + False, status.HTTP_404_NOT_FOUND, f"User with ID {id} not found", [] + ) except Exception as e: debug_response(e, "Error occurs on retrieving user", "error") - return generate_response(False, status.HTTP_500_INTERNAL_SERVER_ERROR, "Failed to retrieve user", []) + return generate_response( + False, status.HTTP_500_INTERNAL_SERVER_ERROR, "Failed to retrieve user", [] + ) -@router.post("/ping", response_description="verified_user", status_code=status.HTTP_202_ACCEPTED) + +@router.post( + "/ping", response_description="verified_user", status_code=status.HTTP_202_ACCEPTED +) def ping_user(): + """Simple endpoint to verify service availability.""" return generate_response(True, status.HTTP_202_ACCEPTED, "Ping successful", []) -@router.post("/verify-token", response_description="verified_user", status_code=status.HTTP_202_ACCEPTED, response_model=ResponseModel) + +@router.post( + "/verify-token", + response_description="verified_user", + status_code=status.HTTP_202_ACCEPTED, + response_model=ResponseModel, +) def token_verify(request: Request): + """Verify JWT token and check if the user has 'create-cluster' role.""" try: authorization_header = request.headers.get("Authorization", "") token = authorization_header.split("Bearer ")[1] user_info = is_authenticated(token) # checking create 'create-cluster' role is assign to the user. - if user_info and 'realm_access' in user_info: - realm_roles = user_info['realm_access']['roles'] - if 'create-cluster' not in realm_roles: + if user_info and "realm_access" in user_info: + realm_roles = user_info["realm_access"]["roles"] + if "create-cluster" not in realm_roles: # If the user does not have the 'create-cluster' role - raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="User is not allowed to create clusters") + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="User is not allowed to create clusters", + ) print("token_verify :: ", user_info) response = { "status": status.HTTP_202_ACCEPTED, - "realm_roles": user_info['realm_access']['roles'], - "user_id": user_info['sub'], + "realm_roles": user_info["realm_access"]["roles"], + "user_id": user_info["sub"], } - return generate_response(True, status.HTTP_202_ACCEPTED, "Token verified", [response]) + return generate_response( + True, status.HTTP_202_ACCEPTED, "Token verified", [response] + ) except Exception as e: debug_response(e, "Error occurs on verifying token", "error") - return generate_response(False, status.HTTP_500_INTERNAL_SERVER_ERROR, "Failed to verify token", []) + return generate_response( + False, status.HTTP_500_INTERNAL_SERVER_ERROR, "Failed to verify token", [] + ) -@router.post("/subscription-request", response_description="request for subscription", status_code=status.HTTP_202_ACCEPTED,response_model=ResponseModel) -def subscription_request(request: Request): + +@router.post( + "/subscription-request", + response_description="request for subscription", + status_code=status.HTTP_202_ACCEPTED, + response_model=ResponseModel, +) +def subscription_request(request: Request): + """Request a subscription for the authenticated user.""" try: authorization_header = request.headers.get("Authorization", "") token = authorization_header.split("Bearer ")[1] user_info = is_authenticated(token) - user_id = user_info.get('sub') + user_id = user_info.get("sub") print("subscription_request :: ", user_id) status_code = None message = None - user_groups = keycloak_admin.get_user_groups(user_id=user_info['sub']) + user_groups = keycloak_admin.get_user_groups(user_id=user_info["sub"]) if not user_groups: # If user_groups is empty - group_id = keycloak_admin.get_group_by_path(path="/" + REQUEST_GROUP_NAME).get("id") + group_id = keycloak_admin.get_group_by_path( + path="/" + REQUEST_GROUP_NAME + ).get("id") keycloak_admin.group_user_add(user_id=user_id, group_id=group_id) status_code = status.HTTP_202_ACCEPTED message = "Subscription request sent successfully" - elif any(group['name'] == REQUEST_GROUP_NAME for group in user_groups): - return generate_response(False, status.HTTP_400_BAD_REQUEST, "User already requested for subscription", []) + elif any(group["name"] == REQUEST_GROUP_NAME for group in user_groups): + return generate_response( + False, + status.HTTP_400_BAD_REQUEST, + "User already requested for subscription", + [], + ) else: - return generate_response(False, status.HTTP_400_BAD_REQUEST, "User already approved for subscription", []) + return generate_response( + False, + status.HTTP_400_BAD_REQUEST, + "User already approved for subscription", + [], + ) - response = { - "status": status_code, - "message": message - } - return generate_response(True, status.HTTP_202_ACCEPTED, "Subscription request sent successfully", [response]) + response = {"status": status_code, "message": message} + return generate_response( + True, + status.HTTP_202_ACCEPTED, + "Subscription request sent successfully", + [response], + ) except Exception as e: debug_response(e, "Error occurs on subscription request", "error") - return generate_response(False, status.HTTP_500_INTERNAL_SERVER_ERROR, "Failed to request subscription", []) \ No newline at end of file + return generate_response( + False, + status.HTTP_500_INTERNAL_SERVER_ERROR, + "Failed to request subscription", + [], + ) diff --git a/cluster-api/routes/websocket.py b/cluster-api/routes/websocket.py index 17b5fe9..57886d2 100644 --- a/cluster-api/routes/websocket.py +++ b/cluster-api/routes/websocket.py @@ -1,12 +1,22 @@ -from fastapi import APIRouter, WebSocket, WebSocketDisconnect, status -from typing import Dict, Optional -import logging -import json +""" +WebSocket Router for Cluster Status Updates. + +Manages active WebSocket connections, sending messages to single or multiple users, +and broadcasting messages to all connected users with retry and logging support. +""" + import asyncio +import json +import logging from datetime import datetime +from typing import Dict, Optional + +from fastapi import APIRouter, WebSocket, WebSocketDisconnect, status # Configure logging -logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') +logging.basicConfig( + level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s" +) logger = logging.getLogger("websocket") router = APIRouter() @@ -14,66 +24,75 @@ # Dictionary to manage active WebSocket connections with last activity timestamp active_connections: Dict[str, Dict[str, any]] = {} + # Function to send a message to a specific user with retry async def send_message_to_user(user_id: str, message: dict, retries: int = 2) -> bool: """Send a message to a specific user with automatic retries. - + Args: user_id: The user ID to send the message to message: The message payload as a dictionary retries: Number of retry attempts if sending fails - + Returns: bool: True if message was sent successfully, False otherwise """ if not user_id: logger.warning(f"Cannot send message - user_id is empty") return False - + if user_id not in active_connections: logger.info(f"User {user_id} not connected, message not sent: {message}") return False - + for attempt in range(retries + 1): try: websocket = active_connections[user_id]["connection"] await websocket.send_json(message) - + # Update last activity timestamp active_connections[user_id]["last_active"] = datetime.now() - + logger.info(f"Successfully sent message to user {user_id}") return True except Exception as e: if attempt < retries: - logger.warning(f"Attempt {attempt+1} failed to send message to {user_id}: {str(e)}. Retrying...") + logger.warning( + f"Attempt {attempt + 1} failed to send message to {user_id}: {str(e)}. Retrying..." + ) await asyncio.sleep(0.5) # Wait before retry else: - logger.error(f"Failed to send message to {user_id} after {retries+1} attempts: {str(e)}") + logger.error( + f"Failed to send message to {user_id} after {retries + 1} attempts: {str(e)}" + ) # Remove dead connection active_connections.pop(user_id, None) return False - + return False + # Send message to multiple users efficiently async def broadcast_to_users(user_ids: list, message: dict) -> Dict[str, bool]: """Send a message to multiple users efficiently. - + Args: user_ids: List of user IDs to send the message to message: The message payload as a dictionary - + Returns: dict: Dictionary of user_id to success/failure status """ if not user_ids: return {} - + # Create tasks for all users - tasks = {user_id: asyncio.create_task(send_message_to_user(user_id, message)) - for user_id in user_ids if user_id} - + tasks = { + user_id: asyncio.create_task(send_message_to_user(user_id, message)) + for user_id in user_ids + if user_id + } + # Wait for all tasks to complete results = {} for user_id, task in tasks.items(): @@ -82,22 +101,23 @@ async def broadcast_to_users(user_ids: list, message: dict) -> Dict[str, bool]: except Exception as e: logger.error(f"Error in broadcast to {user_id}: {str(e)}") results[user_id] = False - + return results + # Function to broadcast a message to all connected users async def broadcast_message(message: dict) -> int: """Broadcast a message to all connected users. - + Args: message: The message payload as a dictionary - + Returns: int: Number of successful deliveries """ success_count = 0 failed_connections = [] - + for user_id, conn_info in active_connections.items(): try: await conn_info["connection"].send_json(message) @@ -106,30 +126,31 @@ async def broadcast_message(message: dict) -> int: except Exception as e: logger.error(f"Failed to broadcast to {user_id}: {str(e)}") failed_connections.append(user_id) - + # Clean up failed connections for user_id in failed_connections: active_connections.pop(user_id, None) - + return success_count + @router.websocket("/{user_id}") async def websocket_endpoint(websocket: WebSocket, user_id: str): """WebSocket endpoint handler for user connections.""" if not user_id: await websocket.close(code=status.WS_1008_POLICY_VIOLATION) return - + # Accept the connection await websocket.accept() logger.info(f"WebSocket connection established for user: {user_id}") - + # Store connection with timestamp active_connections[user_id] = { "connection": websocket, - "last_active": datetime.now() + "last_active": datetime.now(), } - + # Send initial connection confirmation try: welcome_message = { @@ -137,32 +158,32 @@ async def websocket_endpoint(websocket: WebSocket, user_id: str): "user_id": user_id, "message": "Connected to cluster status updates", "timestamp": datetime.now().isoformat(), - "connection_count": len(active_connections) + "connection_count": len(active_connections), } await websocket.send_json(welcome_message) except Exception as e: logger.error(f"Error sending initial message: {str(e)}") - + # Handle incoming messages try: while True: # Wait for messages from client data = await websocket.receive_json() logger.info(f"Received data from {user_id}: {data}") - + # Update activity timestamp active_connections[user_id]["last_active"] = datetime.now() - + # Send acknowledgment response = { "event": "message_received", "user_id": user_id, "status": "connected", "timestamp": datetime.now().isoformat(), - "data_received": data + "data_received": data, } await websocket.send_json(response) - + except WebSocketDisconnect: logger.info(f"WebSocket disconnected for user: {user_id}") active_connections.pop(user_id, None) diff --git a/cluster-api/schemas/cluster_schema.py b/cluster-api/schemas/cluster_schema.py index 5095cb7..75cee10 100644 --- a/cluster-api/schemas/cluster_schema.py +++ b/cluster-api/schemas/cluster_schema.py @@ -1,10 +1,24 @@ -from email.quoprimime import unquote +""" +Utility functions for cluster operations. + +Includes serializers and validators for cluster data. +""" import json -from pipes import quote import re +from email.quoprimime import unquote +from pipes import quote def clusters_serializer(res): + """ + Serialize a cluster document into a standardized dictionary. + + Args: + cluster (dict): Cluster document from the database. + + Returns: + dict: Serialized cluster data. + """ response = { "id": res["_id"], "name": res["name"], @@ -17,8 +31,9 @@ def clusters_serializer(res): } return response + def is_valid_url_name(name): - # Check if the name is empty + """Check if a cluster name is valid for URLs.""" if not name: return False, "Name cannot be empty." @@ -27,7 +42,10 @@ def is_valid_url_name(name): return False, "Name is too long" # Check if the name contains only valid characters (letters, digits, hyphens, and underscores) - if not re.match(r'^[a-zA-Z0-9-_]+$', name): - return False, "Name contains invalid characters. Only letters, digits, hyphens, and underscores are allowed." - - return True, None \ No newline at end of file + if not re.match(r"^[a-zA-Z0-9-_]+$", name): + return ( + False, + "Name contains invalid characters. Only letters, digits, hyphens, and underscores are allowed.", + ) + + return True, None diff --git a/cluster-api/schemas/host_cluster_schema.py b/cluster-api/schemas/host_cluster_schema.py index 58e097a..25b5dac 100644 --- a/cluster-api/schemas/host_cluster_schema.py +++ b/cluster-api/schemas/host_cluster_schema.py @@ -1,12 +1,20 @@ +""" +Module for host cluster serialization and conversion. + +Provides functions to serialize single or multiple host clusters, +convert JSON data to Python objects, and create host_cluster models +from host_cluster_request DTOs. +""" + import json -from pyparsing import Any from dto.host_cluster_request import HostClusterRequest - from models.host_cluster import HostCluster +from pyparsing import Any def host_cluster_serializer(hostCluster) -> dict: + """Serialize a single host cluster for output.""" return { "id": str(hostCluster["_id"]), "name": hostCluster["name"], @@ -22,31 +30,35 @@ def host_cluster_serializer(hostCluster) -> dict: def host_clusters_serializer(cursor) -> list: + """Serialize a list of host clusters.""" return [host_cluster_serializer(hostCluster) for hostCluster in cursor] def host_clusters_serializer_test(data): - # Convert JSON data to Python object + """Convert JSON data to serialized host cluster list.""" python_object = json.loads(data) response = [] for res in python_object: - response.append({ - "id": res["_id"], - "name": res["name"], - "region": res["region"], - "provider": res["provider"], - "nodes": res["nodes"], - "active": res["active"], - "version": res["version"], - "created": res["created"], - "updated": res["updated"], - "user_id": res["user_id"], - }) + response.append( + { + "id": res["_id"], + "name": res["name"], + "region": res["region"], + "provider": res["provider"], + "nodes": res["nodes"], + "active": res["active"], + "version": res["version"], + "created": res["created"], + "updated": res["updated"], + "user_id": res["user_id"], + } + ) return response def host_Cluster_from_dict(res: HostClusterRequest, user_id: str): + """Convert host_cluster_request to host_cluster model.""" return HostCluster( name=res.name, region=res.region, diff --git a/cluster-api/schemas/subscription_schema.py b/cluster-api/schemas/subscription_schema.py index 2556158..3bea608 100644 --- a/cluster-api/schemas/subscription_schema.py +++ b/cluster-api/schemas/subscription_schema.py @@ -1,16 +1,22 @@ -from pyparsing import Any +""" +Module for creating Subscription objects from dictionaries. + +Contains utility functions for converting data to Subscription model instances. +""" from models.subscription import Subscription +from pyparsing import Any def subscription_from_dict(res: Any): + """Convert a dictionary to a Subscription object.""" return Subscription( - name=res['name'], - pods=res['pods'], - service=res['service'], - config_map=res['config_map'], - persistance_vol_claims=res['persistance_vol_claims'], - replication_ctl=res['replication_ctl'], - secrets=res['secrets'], - loadbalancer=res['loadbalancer'], - node_port=res['node_port'] + name=res["name"], + pods=res["pods"], + service=res["service"], + config_map=res["config_map"], + persistance_vol_claims=res["persistance_vol_claims"], + replication_ctl=res["replication_ctl"], + secrets=res["secrets"], + loadbalancer=res["loadbalancer"], + node_port=res["node_port"], ) diff --git a/cluster-api/schemas/user_schema.py b/cluster-api/schemas/user_schema.py index 9a250d2..4c54bce 100644 --- a/cluster-api/schemas/user_schema.py +++ b/cluster-api/schemas/user_schema.py @@ -1,23 +1,28 @@ -from pyparsing import Any -from models.user import User +""" +Module for creating User objects from different dictionary formats. +Provides utility functions to convert data from Keycloak or +internal user dicts to User model instances. +""" +from models.user import User +from pyparsing import Any def user_from_keycloak_dict(res: Any): + """Convert a Keycloak dictionary to a User object.""" return User( id=res.get("sid", "2122fd0b-d673-41a9-a2d2-36133d5eaa78"), name=res.get("name", res.get("preferred_username", "user")), email=res.get("email", "a@b.com"), - userName=res.get("preferred_username", "user") + userName=res.get("preferred_username", "user"), ) def user_from_user_dict(res: Any): - + """Convert an internal user dictionary to a User object.""" return User( id=res.get("_id", ""), name=res.get("name", res.get("userName", "user")), email=res.get("email", "a@b.com"), - userName=res.get("userName", "user") + userName=res.get("userName", "user"), ) - \ No newline at end of file diff --git a/cluster-api/utills/__init__.py b/cluster-api/utills/__init__.py new file mode 100644 index 0000000..13c6f26 --- /dev/null +++ b/cluster-api/utills/__init__.py @@ -0,0 +1 @@ +"""Utils package initialization.""" diff --git a/cluster-api/utills/common_response.py b/cluster-api/utills/common_response.py index 4611612..604086b 100644 --- a/cluster-api/utills/common_response.py +++ b/cluster-api/utills/common_response.py @@ -1,25 +1,51 @@ +"""Utility functions to generate API responses and debug logs.""" + import logging import os +from typing import Any, Dict, List, Optional -from pyparsing import Union +from fastapi.responses import JSONResponse from models.common_response import ResponseModel -from typing import Any, List, Optional, Dict +from pyparsing import Union -from fastapi.responses import JSONResponse -def generate_response(success: bool, code: int, message: str, data: Union[List[Any], Any] = []) -> JSONResponse: +def generate_response( + success: bool, code: int, message: str, data: Union[List[Any], Any] = [] +) -> JSONResponse: + """Generate a standardized JSON response for API endpoints. + + Args: + success (bool): Whether the request was successful. + code (int): HTTP status code. + message (str): Response message. + data (list|any): Response data payload. + + Returns: + JSONResponse: FastAPI JSON response. + """ response_content = { "success": success, "code": code, "message": message, - "data": data + "data": data, } return JSONResponse(content=response_content, status_code=code) -def debug_response(data: any, message: Optional[str] = None, type: Optional[str] = None): + + +def debug_response( + data: any, message: Optional[str] = None, type: Optional[str] = None +): + """Log debug information if DEBUG environment variable is enabled. + + Args: + data (Any): Data to log. + message (str, optional): Optional message to include. + type (str, optional): Type of log ('error', 'info', or default 'debug'). + """ if os.getenv("DEBUG", "false").lower() == "true": if type == "error": logging.error("Error occurs: %s, Message: %s", str(data), message) elif type == "info": logging.info("Debug data: %s, Message: %s", data, message) else: - logging.debug("Debug data: %s", data) \ No newline at end of file + logging.debug("Debug data: %s", data) diff --git a/cluster-api/utills/common_utills.py b/cluster-api/utills/common_utills.py index 03c3671..ad82f34 100644 --- a/cluster-api/utills/common_utills.py +++ b/cluster-api/utills/common_utills.py @@ -1,33 +1,67 @@ +""" +Utility functions for cluster operations, including time parsing and +selecting the best cluster from a list of host clusters. +""" import logging import os import re -from fastapi import HTTPException,status + import requests +from fastapi import HTTPException, status def extract_time_components(time_string): - pattern = r'(\d+)h(\d+)m|(\d+)h|(\d+)m' + """ + Convert a time string like '1h30m', '2h', or '45m' into total seconds. + + Args: + time_string (str): The time string to parse. + + Returns: + int or None: Total seconds represented by the string, or None if invalid. + """ + pattern = r"(\d+)h(\d+)m|(\d+)h|(\d+)m" match = re.match(pattern, time_string) if match: hours = match.group(1) or match.group(3) minutes = match.group(2) or match.group(4) - return int(hours) * 3600 if hours is not None else 0 + int(minutes) *60 if minutes is not None else 0 - + return ( + int(hours) * 3600 + if hours is not None + else 0 + int(minutes) * 60 + if minutes is not None + else 0 + ) + else: return None - + def get_best_cluster(host_cluster_ids): - payload = { - "host_cluster_ids": host_cluster_ids - } - baseUrl = os.getenv('SERVICE_URL') - response = requests.post(baseUrl+"/cluster-check", json=payload) + """ + Select the best cluster from a list of host cluster IDs by calling + an external service. + + Args: + host_cluster_ids (list): List of host cluster IDs to check. + + Returns: + str: The ID of the best cluster. + + Raises: + HTTPException: If the service does not return a successful response. + """ + payload = {"host_cluster_ids": host_cluster_ids} + baseUrl = os.getenv("SERVICE_URL") + response = requests.post(baseUrl + "/cluster-check", json=payload) if response.status_code == 200: # Check if the request was successful print("POST request successful") responseBody = response.json() logging.info("response from select best cluster :: %s", str(responseBody)) return responseBody["id"] else: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="resource not avialable in the current host") \ No newline at end of file + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="resource not available in the current host", + ) diff --git a/cluster-api/utills/seeder.py b/cluster-api/utills/seeder.py index f7c65b7..0c45bfb 100644 --- a/cluster-api/utills/seeder.py +++ b/cluster-api/utills/seeder.py @@ -1,35 +1,137 @@ +""" +Seeder module to populate the database with initial test data. + +This module seeds collections for host clusters, users, subscriptions, +clusters, and kube versions for development or testing purposes. +""" + + def seed_db(db): - # Seed hostCluster collection + """ + Seed the database with initial test data. + + Args: + db: Database connection (e.g., MongoDB client) where collections + will be seeded. + """ host_clusters = [ - {"_id": "4d17f7f0-752b-4a5e-844b-915a55876b22", "name": "cluster-1","user_id": "da812999-c98a-445a-b759-bc7e21524d7c", "region": "us-east-1", "provider": "aws", "nodes": 1, "active": True, "version": "1.25", "created": "2022-01-01T00:00:00.000Z", "updated": "2022-01-01T00:00:00.000Z"}, - {"_id": "4d17f7f0-752b-4a5e-844b-915a55876b23", "name": "cluster-2","user_id": "da812999-c98a-445a-b759-bc7e21524d7d", "region": "us-west-1", "provider": "aws", "nodes": 2, "active": True, "version": "1.26", "created": "2022-01-01T00:00:00.000Z", "updated": "2022-01-01T00:00:00.000Z"} + { + "_id": "4d17f7f0-752b-4a5e-844b-915a55876b22", + "name": "cluster-1", + "user_id": "da812999-c98a-445a-b759-bc7e21524d7c", + "region": "us-east-1", + "provider": "aws", + "nodes": 1, + "active": True, + "version": "1.25", + "created": "2022-01-01T00:00:00.000Z", + "updated": "2022-01-01T00:00:00.000Z", + }, + { + "_id": "4d17f7f0-752b-4a5e-844b-915a55876b23", + "name": "cluster-2", + "user_id": "da812999-c98a-445a-b759-bc7e21524d7d", + "region": "us-west-1", + "provider": "aws", + "nodes": 2, + "active": True, + "version": "1.26", + "created": "2022-01-01T00:00:00.000Z", + "updated": "2022-01-01T00:00:00.000Z", + }, ] db["hostCluster"].insert_many(host_clusters) # Seed user collection users = [ - {"_id": "da812999-c98a-445a-b759-bc7e21524d7c", "name": "umesh khatiwada", "email": "a@b.com", "userName": "user"}, - {"_id": "da812999-c98a-445a-b759-bc7e21524d7d", "name": "john doe", "email": "john@doe.com", "userName": "johndoe"} + { + "_id": "da812999-c98a-445a-b759-bc7e21524d7c", + "name": "umesh khatiwada", + "email": "a@b.com", + "userName": "user", + }, + { + "_id": "da812999-c98a-445a-b759-bc7e21524d7d", + "name": "john doe", + "email": "john@doe.com", + "userName": "johndoe", + }, ] db["user"].insert_many(users) # Seed subscription collection subscriptions = [ - {"_id": "039623b9-c20e-4f0e-a81a-de85a550ef19", "name": "basic", "pods": 10, "service": 10, "configMap": 10, "persistanceVolClaims": 10, "replication_ctl": 10, "secrets": 10, "loadbalancer": 0, "nodePort": 10, "created": "2022-01-01T00:00:00.000Z", "updated": "2022-01-01T00:00:00.000Z"}, - {"_id": "039623b9-c20e-4f0e-a81a-de85a550ef20", "name": "premium", "pods": 20, "service": 20, "configMap": 20, "persistanceVolClaims": 20, "replication_ctl": 20, "secrets": 20, "loadbalancer": 1, "nodePort": 20, "created": "2022-01-01T00:00:00.000Z", "updated": "2022-01-01T00:00:00.000Z"} + { + "_id": "039623b9-c20e-4f0e-a81a-de85a550ef19", + "name": "basic", + "pods": 10, + "service": 10, + "configMap": 10, + "persistanceVolClaims": 10, + "replication_ctl": 10, + "secrets": 10, + "loadbalancer": 0, + "nodePort": 10, + "created": "2022-01-01T00:00:00.000Z", + "updated": "2022-01-01T00:00:00.000Z", + }, + { + "_id": "039623b9-c20e-4f0e-a81a-de85a550ef20", + "name": "premium", + "pods": 20, + "service": 20, + "configMap": 20, + "persistanceVolClaims": 20, + "replication_ctl": 20, + "secrets": 20, + "loadbalancer": 1, + "nodePort": 20, + "created": "2022-01-01T00:00:00.000Z", + "updated": "2022-01-01T00:00:00.000Z", + }, ] db["subscription"].insert_many(subscriptions) # Seed cluster collection clusters = [ - {"_id": "5a9539f2-668e-4e3d-9ae8-0823c8ae2ebb", "name": "test1", "user_id": "da812999-c98a-445a-b759-bc7e21524d7c", "status": "Stopped", "kube_version": "v1.30.0", "host_cluster_id": "4d17f7f0-752b-4a5e-844b-915a55876b22", "subscription_id":"039623b9-c20e-4f0e-a81a-de85a550ef19", "created": "2022-01-01T00:00:00.000Z", "updated": "2022-01-01T00:00:00.000Z"}, - {"_id": "5a9539f2-668e-4e3d-9ae8-0823c8ae2ebc", "name": "test2", "user_id": "da812999-c98a-445a-b759-bc7e21524d7d", "status": "Running", "kube_version": "v1.31.0", "host_cluster_id": "4d17f7f0-752b-4a5e-844b-915a55876b23","subscription_id":"039623b9-c20e-4f0e-a81a-de85a550ef19", "created": "2022-01-01T00:00:00.000Z", "updated": "2022-01-01T00:00:00.000Z"} + { + "_id": "5a9539f2-668e-4e3d-9ae8-0823c8ae2ebb", + "name": "test1", + "user_id": "da812999-c98a-445a-b759-bc7e21524d7c", + "status": "Stopped", + "kube_version": "v1.30.0", + "host_cluster_id": "4d17f7f0-752b-4a5e-844b-915a55876b22", + "subscription_id": "039623b9-c20e-4f0e-a81a-de85a550ef19", + "created": "2022-01-01T00:00:00.000Z", + "updated": "2022-01-01T00:00:00.000Z", + }, + { + "_id": "5a9539f2-668e-4e3d-9ae8-0823c8ae2ebc", + "name": "test2", + "user_id": "da812999-c98a-445a-b759-bc7e21524d7d", + "status": "Running", + "kube_version": "v1.31.0", + "host_cluster_id": "4d17f7f0-752b-4a5e-844b-915a55876b23", + "subscription_id": "039623b9-c20e-4f0e-a81a-de85a550ef19", + "created": "2022-01-01T00:00:00.000Z", + "updated": "2022-01-01T00:00:00.000Z", + }, ] db["cluster"].insert_many(clusters) # Seed kubeversion collection kube_versions = [ - {"_id": "5a9539f2-668e-4e3d-9ae8-0823c8ae2ebd", "name": "v1.25.0", "active": True, "kube_version": "v1.25.0"}, - {"_id": "5a9539f2-668e-4e3d-9ae8-0823c8ae2ebe", "name": "v1.26,0", "active": True, "kube_version": "v1.26.0"} + { + "_id": "5a9539f2-668e-4e3d-9ae8-0823c8ae2ebd", + "name": "v1.25.0", + "active": True, + "kube_version": "v1.25.0", + }, + { + "_id": "5a9539f2-668e-4e3d-9ae8-0823c8ae2ebe", + "name": "v1.26,0", + "active": True, + "kube_version": "v1.26.0", + }, ] - db["kubeversion"].insert_many(kube_versions) \ No newline at end of file + db["kubeversion"].insert_many(kube_versions) diff --git a/cluster-service/src/controller/routes.py b/cluster-service/src/controller/routes.py index dc233c7..27ed0a6 100644 --- a/cluster-service/src/controller/routes.py +++ b/cluster-service/src/controller/routes.py @@ -1,66 +1,95 @@ +"""Flask routes for cluster management service.""" import json import logging + from flask import Blueprint, jsonify, request from src.usecases.use_cases import ( check_host_cluster_usecase, create_cluster_usecase, + delete_vcluster_usecase, + generate_kubeconfig_usecase, get_cluster_status_usecase, start_cluster_usecase, stop_vcluster_usecase, - delete_vcluster_usecase, - generate_kubeconfig_usecase, ) + # Define a Blueprint for routes routes_bp = Blueprint("routes", __name__) + @routes_bp.route("/dapr/subscribe", methods=["GET"]) def subscribe(): """Return the list of subscriptions for Dapr.""" subscriptions = [ - {"pubsubname": "messagebus", "topic": "cluster-create", "route": "create-cluster"}, - {"pubsubname": "messagebus", "topic": "cluster-start", "route": "start-cluster"}, + { + "pubsubname": "messagebus", + "topic": "cluster-create", + "route": "create-cluster", + }, + { + "pubsubname": "messagebus", + "topic": "cluster-start", + "route": "start-cluster", + }, {"pubsubname": "messagebus", "topic": "cluster-stop", "route": "stop-cluster"}, - {"pubsubname": "messagebus", "topic": "cluster-delete", "route": "delete-cluster"}, - {"pubsubname": "messagebus", "topic": "cluster-plan-upgrade", "route": "update-cluster-plan"}, + { + "pubsubname": "messagebus", + "topic": "cluster-delete", + "route": "delete-cluster", + }, + { + "pubsubname": "messagebus", + "topic": "cluster-plan-upgrade", + "route": "update-cluster-plan", + }, ] logging.info("Dapr pub/sub is subscribed to: %s", json.dumps(subscriptions)) return jsonify(subscriptions) + @routes_bp.route("/create-cluster", methods=["POST"]) def create_cluster(): """Handle cluster creation.""" return create_cluster_usecase(request.json) + @routes_bp.route("/cluster-check", methods=["POST"]) def check_host_cluster(): """Check the status of a host cluster.""" return check_host_cluster_usecase(request.json) + @routes_bp.route("/start-cluster", methods=["POST"]) def start_cluster(): """Start a cluster.""" return start_cluster_usecase(request.json) + @routes_bp.route("/", methods=["GET"]) def health_check(): """Health check endpoint.""" return jsonify({"message": "Cluster service running"}), 200 + @routes_bp.route("/host-cluster/cluster/status", methods=["POST"]) def get_cluster_status(): """Get the status of a cluster.""" return get_cluster_status_usecase(request.json) + @routes_bp.route("/stop-cluster", methods=["POST"]) def stop_vcluster(): """Stop a virtual cluster.""" return stop_vcluster_usecase(request.json) + @routes_bp.route("/delete-cluster", methods=["POST"]) def delete_cluster(): """Delete a cluster.""" return delete_vcluster_usecase(request.json) + @routes_bp.route("/generate-config", methods=["POST"]) def generate_config(): - return generate_kubeconfig_usecase(request.json) \ No newline at end of file + """Generate a kubeconfig for a cluster.""" + return generate_kubeconfig_usecase(request.json) diff --git a/cluster-service/src/models/client_node_metrix.py b/cluster-service/src/models/client_node_metrix.py index 6e7f8d6..619423f 100644 --- a/cluster-service/src/models/client_node_metrix.py +++ b/cluster-service/src/models/client_node_metrix.py @@ -1,6 +1,18 @@ +"""Module for storing and managing client node metrics.""" + + class ClientNodesMetrics: + """Stores metrics for client nodes.""" + def __init__(self): + """Initialize an empty dictionary to store metrics by node name.""" self.metrics = {} # Dictionary to store metrics by node name def add_metric(self, node_name, metric): - self.metrics[node_name] = metric \ No newline at end of file + """Add or update a metric for a specific node. + + Args: + node_name (str): The name of the node. + metric (Any): The metric data associated with the node. + """ + self.metrics[node_name] = metric diff --git a/cluster-service/src/models/subscription.py b/cluster-service/src/models/subscription.py index 8826523..3c409d8 100644 --- a/cluster-service/src/models/subscription.py +++ b/cluster-service/src/models/subscription.py @@ -1,5 +1,34 @@ +"""Module for subscription-related data structures and parsing utilities.""" + + class Subscription: - def __init__(self, name, pods, service, config_map, persistence_vol_claims, replication_ctl, secrets, loadbalancer, node_port): + """Represents a Kubernetes subscription configuration.""" + + def __init__( + self, + name, + pods, + service, + config_map, + persistence_vol_claims, + replication_ctl, + secrets, + loadbalancer, + node_port, + ): + """Initialize a Subscription instance. + + Args: + name (str): Name of the subscription. + pods (Any): Pod specifications. + service (Any): Service specifications. + config_map (Any): ConfigMap specifications. + persistence_vol_claims (Any): Persistent volume claims. + replication_ctl (Any): Replication controller configuration. + secrets (Any): Secrets associated with the subscription. + loadbalancer (Any): Load balancer configuration. + node_port (Any): Node port configuration. + """ self.name = name self.pods = pods self.service = service @@ -12,15 +41,22 @@ def __init__(self, name, pods, service, config_map, persistence_vol_claims, repl def parse_subscription_json(json_data): + """Parse a JSON dictionary and return a Subscription instance. + + Args: + json_data (dict): JSON data representing a subscription. + + Returns: + Subscription: Populated Subscription object. + """ return Subscription( - name=json_data['name'], - pods=json_data['pods'], - service=json_data['service'], - config_map=json_data['config_map'], - persistence_vol_claims=json_data['persistance_vol_claims'], - replication_ctl=json_data['replication_ctl'], - secrets=json_data['secrets'], - loadbalancer=json_data['loadbalancer'], - node_port=json_data['node_port'] + name=json_data["name"], + pods=json_data["pods"], + service=json_data["service"], + config_map=json_data["config_map"], + persistence_vol_claims=json_data["persistance_vol_claims"], + replication_ctl=json_data["replication_ctl"], + secrets=json_data["secrets"], + loadbalancer=json_data["loadbalancer"], + node_port=json_data["node_port"], ) - diff --git a/cluster-service/src/usecases/__init__.py b/cluster-service/src/usecases/__init__.py index e69de29..9929249 100644 --- a/cluster-service/src/usecases/__init__.py +++ b/cluster-service/src/usecases/__init__.py @@ -0,0 +1 @@ +"""Use-case functions for cluster management.""" diff --git a/cluster-service/src/usecases/use_cases.py b/cluster-service/src/usecases/use_cases.py index 0336c1f..29ac8c7 100644 --- a/cluster-service/src/usecases/use_cases.py +++ b/cluster-service/src/usecases/use_cases.py @@ -1,20 +1,34 @@ +""" +Cluster management use-cases: create, start, stop, delete, and manage vclusters. + +Includes kubeconfig generation and host cluster checks. +""" + import base64 +import json import logging import os import tempfile import time -import json -from flask import jsonify, request +from http.client import HTTPException + import yaml +from flask import jsonify, request from kubernetes import client, config -from http.client import HTTPException +from src.models.subscription import parse_subscription_json +from src.utils.cluster_utils import ( + add_labels_to_statefulset, + createNamespace, + generate_cluster_yaml, + generate_resource_quota_yaml, + generate_vclusterYaml, +) from src.utils.common_utils import get_available_resources_fromSecret, get_pod_status from src.utils.secret_utils import get_vault_secret -from src.utils.cluster_utils import add_labels_to_statefulset, createNamespace, generate_vclusterYaml -from src.utils.cluster_utils import generate_cluster_yaml, generate_resource_quota_yaml -from src.models.subscription import parse_subscription_json + def load_kubernetes_client(base64_kubeconfig): + """Load a Kubernetes client from a base64-encoded kubeconfig string.""" decoded_kubeconfig = base64.b64decode(base64_kubeconfig) with tempfile.NamedTemporaryFile(delete=False) as temp_file: temp_file.write(decoded_kubeconfig) @@ -24,7 +38,9 @@ def load_kubernetes_client(base64_kubeconfig): temp_file.close() return k8s_client + def create_cluster_usecase(data): + """Create or update a virtual cluster based on the provided payload.""" logging.info("Published data Create Cluster :: " + json.dumps(data["data"])) secret = get_vault_secret(data["data"]["host_cluster_id"]) host_cluster_name = data["data"]["host_cluster_name"] @@ -34,15 +50,19 @@ def create_cluster_usecase(data): namespace = data["data"]["cluster"]["name"] + "-vcluster" name = data["data"]["cluster"]["name"] cluster_id = data["data"]["cluster"]["id"] - domain = os.getenv('HOST_NAME') - host = f'{name}.{host_cluster_name}.{domain}' + domain = os.getenv("HOST_NAME") + host = f"{name}.{host_cluster_name}.{domain}" - vcluster = generate_vclusterYaml(namespace=namespace, name=name, host=host, kube_version=kube_version) + vcluster = generate_vclusterYaml( + namespace=namespace, name=name, host=host, kube_version=kube_version + ) custom_resource = generate_cluster_yaml(namespace=namespace, name=name) logging.info("Subscription data: " + json.dumps(data["data"]["subscription"])) subscriptionData = parse_subscription_json(data["data"]["subscription"]) - quota = generate_resource_quota_yaml(subscription=subscriptionData, namespace=namespace) + quota = generate_resource_quota_yaml( + subscription=subscriptionData, namespace=namespace + ) try: try: @@ -51,7 +71,7 @@ def create_cluster_usecase(data): version="v1alpha1", namespace=namespace, plural="vclusters", - name=name + name=name, ) except client.rest.ApiException as e: if e.status == 404: @@ -59,10 +79,12 @@ def create_cluster_usecase(data): pass else: raise e - + if existing_resource: - vcluster_dict = yaml.safe_load(vcluster) if isinstance(vcluster, str) else vcluster - resource_version = existing_resource['metadata'].get('resourceVersion') + vcluster_dict = ( + yaml.safe_load(vcluster) if isinstance(vcluster, str) else vcluster + ) + resource_version = existing_resource["metadata"].get("resourceVersion") vcluster_dict["metadata"]["resourceVersion"] = str(resource_version) client.CustomObjectsApi().replace_namespaced_custom_object( @@ -74,10 +96,12 @@ def create_cluster_usecase(data): body=vcluster_dict, ) wait_for_service_creation(namespace, name) - return jsonify({ - "message": "vcluster updated successfully", - "name": name, - }) + return jsonify( + { + "message": "vcluster updated successfully", + "name": name, + } + ) else: createNamespace(namespace) client.CustomObjectsApi().create_namespaced_custom_object( @@ -108,35 +132,39 @@ def create_cluster_usecase(data): logging.error("error occurs while creating cluster :: %s", str(e)) raise HTTPException(status_code=500, detail=str(e)) + def generate_kubeconfig_usecase(body): - name = body['name'] + """Generate a kubeconfig for a vcluster with a temporary token.""" + name = body["name"] hostClusterId = body["hostClusterId"] expirationTime = body["expirationTime"] namespace = f"{name}-vcluster" secret = get_vault_secret(hostClusterId) - load_kubernetes_client(secret) + load_kubernetes_client(secret) - secret = client.CoreV1Api().read_namespaced_secret(name=f"{name}-kubeconfig", namespace=namespace) - kubeconfig = base64.b64decode(secret.data['value']).decode('utf-8') + secret = client.CoreV1Api().read_namespaced_secret( + name=f"{name}-kubeconfig", namespace=namespace + ) + kubeconfig = base64.b64decode(secret.data["value"]).decode("utf-8") kubeconfig_data = yaml.safe_load(kubeconfig) server = None - for cluster in kubeconfig_data.get('clusters', []): - ser = cluster.get('cluster', {}).get('server') + for cluster in kubeconfig_data.get("clusters", []): + ser = cluster.get("cluster", {}).get("server") if ser: server = ser break - print("SERVER :: ", server) + print("SERVER :: ", server) cluster_cert = None - for cluster in kubeconfig_data.get('clusters', []): - cert = cluster.get('cluster', {}).get('certificate-authority-data') + for cluster in kubeconfig_data.get("clusters", []): + cert = cluster.get("cluster", {}).get("certificate-authority-data") if cert: cluster_cert = cert break print(cluster_cert) - k8sClient = load_kubernetes_client(secret.data['value']) + k8sClient = load_kubernetes_client(secret.data["value"]) vclient = k8sClient.CoreV1Api() rbac_v1 = k8sClient.RbacAuthorizationV1Api() @@ -151,7 +179,9 @@ def generate_kubeconfig_usecase(body): service_account = client.V1ServiceAccount( metadata=client.V1ObjectMeta(name=name) ) - vclient.create_namespaced_service_account(namespace="default", body=service_account) + vclient.create_namespaced_service_account( + namespace="default", body=service_account + ) cluster_role = client.V1ClusterRole( metadata=client.V1ObjectMeta(name=name, namespace="default"), @@ -189,13 +219,11 @@ def generate_kubeconfig_usecase(body): metadata=client.V1ObjectMeta(name=name), spec=client.V1TokenRequestSpec( expiration_seconds=expirationTime, - audiences=["https://kubernetes.default.svc.cluster.local", "k3s"] - ) + audiences=["https://kubernetes.default.svc.cluster.local", "k3s"], + ), ) token_response = vclient.create_namespaced_service_account_token( - namespace="default", - name=name, - body=token_request + namespace="default", name=name, body=token_request ) print(token_response) token_value = token_response.status.token @@ -204,19 +232,18 @@ def generate_kubeconfig_usecase(body): "cluster": name, "clusterCerts": cluster_cert, "token": token_value, - "server": server + "server": server, } return jsonify(responseData), 200 + def check_host_cluster_usecase(body): + """Check available resources across multiple host clusters and return the best.""" logging.info("Published data: " + json.dumps(body)) secret_response = [] for host_cluster_id in body["host_cluster_ids"]: secret = get_vault_secret(host_cluster_id) - secret_response.append({ - "id": host_cluster_id, - "encoded_config": secret - }) + secret_response.append({"id": host_cluster_id, "encoded_config": secret}) best_cluster = get_available_resources_fromSecret(secret_response) if best_cluster: key = list(best_cluster.keys())[0] @@ -224,55 +251,56 @@ def check_host_cluster_usecase(body): result = { "id": key, "best_cpu": data["best_cpu"], - "best_memory": data["best_memory"] + "best_memory": data["best_memory"], } return jsonify(result), 200 else: return jsonify({"error": "No available resources"}), 404 + def start_cluster_usecase(data): + """Start a vcluster by scaling its StatefulSet replicas to 1.""" logging.info("Published data: " + json.dumps(data)) received_data = data["data"] secret = get_vault_secret(received_data["host_cluster_id"]) - load_kubernetes_client(secret) + load_kubernetes_client(secret) name = received_data["cluster_name"] namespace = f"{name}-vcluster" api_client_apps = client.AppsV1Api() try: vcluster_statefulset = api_client_apps.read_namespaced_stateful_set( - name=name, - namespace=namespace + name=name, namespace=namespace ) if vcluster_statefulset.spec.replicas >= 1: - return jsonify({"message": "cluster already started"}), 200 + return jsonify({"message": "cluster already started"}), 200 elif vcluster_statefulset.spec.replicas == 0: vcluster_statefulset.spec.replicas = 1 api_client_apps.replace_namespaced_stateful_set( - name=name, - namespace=namespace, - body=vcluster_statefulset + name=name, namespace=namespace, body=vcluster_statefulset ) res = {"message": "cluster starting"} logging.info("Cluster started :: ", res) - return jsonify(res), 200 + return jsonify(res), 200 except Exception as e: raise HTTPException(status_code=500, detail=str(e)) + def get_cluster_status_usecase(body): - name = body['name'] + """Return the current status of a vcluster by checking pod states.""" + name = body["name"] hostClusterId = body["hostClusterId"] namespace = f"{name}-vcluster" secret = get_vault_secret(hostClusterId) - load_kubernetes_client(secret) + load_kubernetes_client(secret) status = get_pod_status(namespace, name) print("STATUS :: ", status) - responseData = { - "status": status - } + responseData = {"status": status} return jsonify(responseData), 200 + def stop_vcluster_usecase(data): + """Stop a vcluster by scaling its StatefulSet replicas to 0.""" received_data = data["data"] secret = get_vault_secret(received_data["host_cluster_id"]) load_kubernetes_client(secret) @@ -281,17 +309,14 @@ def stop_vcluster_usecase(data): namespace = f"{name}-vcluster" try: vcluster_statefulset = api_client_apps.read_namespaced_stateful_set( - name=name, - namespace=namespace + name=name, namespace=namespace ) if vcluster_statefulset.spec.replicas == 0: - return jsonify({"message": "cluster already stopped"}), 200 + return jsonify({"message": "cluster already stopped"}), 200 elif vcluster_statefulset.spec.replicas >= 1: vcluster_statefulset.spec.replicas = 0 api_client_apps.replace_namespaced_stateful_set( - name=name, - namespace=namespace, - body=vcluster_statefulset + name=name, namespace=namespace, body=vcluster_statefulset ) res = {"message": "cluster stopping"} logging.info("Cluster stopping :: ", res) @@ -299,7 +324,9 @@ def stop_vcluster_usecase(data): except Exception as e: raise HTTPException(status_code=500, detail=str(e)) + def delete_vcluster_usecase(data): + """Delete a vcluster by removing its Kubernetes namespace.""" received_data = data["data"] secret = get_vault_secret(received_data["host_cluster_id"]) load_kubernetes_client(secret) @@ -315,29 +342,39 @@ def delete_vcluster_usecase(data): print(f"Error deleting namespace '{namespace}': {str(e)}") raise HTTPException(status_code=500, detail=str(e)) -def update_cluster_plan_usecase( data): + +def update_cluster_plan_usecase(data): + """Update the resource quotas of a vcluster based on a new subscription plan.""" logging.info("Published data: " + json.dumps(data["data"])) secret = get_vault_secret(data["data"]["host_cluster_id"]) - load_kubernetes_client(secret) + load_kubernetes_client(secret) try: namespace = data["data"]["cluster"] + "-vcluster" logging.info("Subscription data: " + json.dumps(data["data"]["subscription"])) subscriptionData = parse_subscription_json(data["data"]["subscription"]) - quota = generate_resource_quota_yaml(subscription=subscriptionData, namespace=namespace) - resource_quotas = client.CoreV1Api().list_namespaced_resource_quota(namespace=namespace) + quota = generate_resource_quota_yaml( + subscription=subscriptionData, namespace=namespace + ) + resource_quotas = client.CoreV1Api().list_namespaced_resource_quota( + namespace=namespace + ) client.CoreV1Api().create_namespaced_resource_quota( namespace=namespace, body=yaml.safe_load(quota) ) for rq in resource_quotas.items: resource_quota_name = rq.metadata.name try: - response = client.CoreV1Api().delete_namespaced_resource_quota(name=resource_quota_name, namespace=namespace) + response = client.CoreV1Api().delete_namespaced_resource_quota( + name=resource_quota_name, namespace=namespace + ) print(f"Resource Quota '{resource_quota_name}' deleted successfully.") except client.rest.ApiException as e: if e.status == 404: print(f"Resource Quota '{resource_quota_name}' not found.") else: - print(f"An error occurred while deleting '{resource_quota_name}':", e) + print( + f"An error occurred while deleting '{resource_quota_name}':", e + ) return { "message": "vcluster plan upgraded successfully", } @@ -345,51 +382,57 @@ def update_cluster_plan_usecase( data): print("Error occurs while updating cluster plan :: ", e) raise HTTPException(status_code=500, detail=str(e)) + def create_ingress(name: str, namespace: str, host: str): + """Create an NGINX ingress for the given vcluster.""" print("WE are here for INGRESS") networking_v1_api = client.NetworkingV1Api() body = client.V1Ingress( api_version="networking.k8s.io/v1", kind="Ingress", - metadata=client.V1ObjectMeta(name=name, annotations={ - "nginx.ingress.kubernetes.io/backend-protocol": "HTTPS", - "nginx.ingress.kubernetes.io/ssl-passthrough": "true", - "nginx.ingress.kubernetes.io/ssl-redirect": "true", - "cert-manager.io/cluster-issuer": "letsencrypt-prod" - }), + metadata=client.V1ObjectMeta( + name=name, + annotations={ + "nginx.ingress.kubernetes.io/backend-protocol": "HTTPS", + "nginx.ingress.kubernetes.io/ssl-passthrough": "true", + "nginx.ingress.kubernetes.io/ssl-redirect": "true", + "cert-manager.io/cluster-issuer": "letsencrypt-prod", + }, + ), spec=client.V1IngressSpec( ingress_class_name="nginx", - tls=[ - client.V1IngressTLS( - hosts=[host], - secret_name="tls-secret" - ) - ], - rules=[client.V1IngressRule( - host=host, - http=client.V1HTTPIngressRuleValue( - paths=[client.V1HTTPIngressPath( - path="/", - path_type="ImplementationSpecific", - backend=client.V1IngressBackend( - service=client.V1IngressServiceBackend( - port=client.V1ServiceBackendPort( - number=443, + tls=[client.V1IngressTLS(hosts=[host], secret_name="tls-secret")], + rules=[ + client.V1IngressRule( + host=host, + http=client.V1HTTPIngressRuleValue( + paths=[ + client.V1HTTPIngressPath( + path="/", + path_type="ImplementationSpecific", + backend=client.V1IngressBackend( + service=client.V1IngressServiceBackend( + port=client.V1ServiceBackendPort( + number=443, + ), + name=name, + ) ), - name=name) ) - )] + ] + ), ) - )] - ) + ], + ), ) api_response = networking_v1_api.create_namespaced_ingress( - namespace=namespace, - body=body + namespace=namespace, body=body ) print("Ingress created. Status='%s'" % str(api_response.status)) + def wait_for_service_creation(namespace, name): + """Wait until the Kubernetes Service for a vcluster exists.""" print("WE are here for WAiting") api_client = client.CoreV1Api() while True: @@ -403,18 +446,22 @@ def wait_for_service_creation(namespace, name): print(f"Service '{name}' not found, retrying...") time.sleep(5) + def wait_for_sts_pod_readiness(namespace, name, clusterId): + """Wait until all pods in a StatefulSet are running, then add status labels.""" try: core_v1 = client.CoreV1Api() - timeout = 300 + timeout = 300 start_time = time.time() while True: - pods = core_v1.list_namespaced_pod(namespace, field_selector=f"metadata.name={name}").items + pods = core_v1.list_namespaced_pod( + namespace, field_selector=f"metadata.name={name}" + ).items all_ready = all(p.status.phase == "Running" for p in pods) if all_ready: labels = { "status-controller-vcluster": "cluster-manager", - "status-controller": clusterId + "status-controller": clusterId, } add_labels_to_statefulset(namespace, name, labels) return True diff --git a/cluster-service/src/utils/__init__.py b/cluster-service/src/utils/__init__.py new file mode 100644 index 0000000..b4b6136 --- /dev/null +++ b/cluster-service/src/utils/__init__.py @@ -0,0 +1 @@ +"""Utils functions for cluster management.""" diff --git a/cluster-service/src/utils/app_constant.py b/cluster-service/src/utils/app_constant.py index 6e691bf..e6ff759 100644 --- a/cluster-service/src/utils/app_constant.py +++ b/cluster-service/src/utils/app_constant.py @@ -1,2 +1,4 @@ +"""Application-wide constants for resource usage thresholds.""" + CPU_THRESHOLD = 80 RAM_THRESHOLD = 90 diff --git a/cluster-service/src/utils/best_cluster_utils.py b/cluster-service/src/utils/best_cluster_utils.py index f998e49..ea85ee5 100644 --- a/cluster-service/src/utils/best_cluster_utils.py +++ b/cluster-service/src/utils/best_cluster_utils.py @@ -1,27 +1,29 @@ +"""Utilities for selecting the best cluster based on CPU and memory usage.""" from src.utils.app_constant import CPU_THRESHOLD, RAM_THRESHOLD def get_best_cluster(data): + """Select the cluster with the lowest CPU and memory usage below thresholds.""" best_data = {} - best_cpu = float('inf') - best_memory = float('inf') + best_cpu = float("inf") + best_memory = float("inf") send_notification = True for filename, stats in data.items(): - cpu = stats['CPU'] - memory = stats['MEMORY'] + cpu = stats["CPU"] + memory = stats["MEMORY"] if cpu < CPU_THRESHOLD and memory < RAM_THRESHOLD: if cpu < best_cpu or (cpu == best_cpu and memory < best_memory): best_cpu = cpu best_memory = memory - best_data = {filename: {'best_cpu': cpu, 'best_memory': memory}} + best_data = {filename: {"best_cpu": cpu, "best_memory": memory}} send_notification = False elif cpu < best_cpu and memory < best_memory: best_cpu = cpu best_memory = memory - best_data = {filename: {'best_cpu': cpu, 'best_memory': memory}} + best_data = {filename: {"best_cpu": cpu, "best_memory": memory}} send_notification = True if send_notification: @@ -30,5 +32,7 @@ def get_best_cluster(data): return best_data + def send_notification_to_admin(): - print("Notification sent to admin: CPU or memory usage exceeds 80.") \ No newline at end of file + """Notify admin when CPU or memory usage exceeds defined thresholds.""" + print("Notification sent to admin: CPU or memory usage exceeds 80.") diff --git a/cluster-service/src/utils/cluster_utils.py b/cluster-service/src/utils/cluster_utils.py index cfee40f..00cb84d 100644 --- a/cluster-service/src/utils/cluster_utils.py +++ b/cluster-service/src/utils/cluster_utils.py @@ -1,8 +1,12 @@ +"""Utility functions for managing Kubernetes clusters and vclusters.""" import time + +from kubernetes import client from src.models.subscription import Subscription -from kubernetes import client + def generate_vclusterYaml(name, namespace, host, kube_version): + """Generate the YAML configuration for a vcluster.""" yaml_template = """ apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1 kind: VCluster @@ -40,9 +44,13 @@ def generate_vclusterYaml(name, namespace, host, kube_version): extraSANs: - {host} """ - return yaml_template.format(name=name, namespace=namespace,host=host,kube_version=kube_version) + return yaml_template.format( + name=name, namespace=namespace, host=host, kube_version=kube_version + ) + def generate_cluster_yaml(name: str, namespace: str): + """Generate the YAML configuration for a Cluster.""" yaml_template = """ apiVersion: cluster.x-k8s.io/v1beta1 kind: Cluster @@ -64,7 +72,9 @@ def generate_cluster_yaml(name: str, namespace: str): namespace=namespace, ) -def generate_resource_quota_yaml(subscription:Subscription ,namespace: str): + +def generate_resource_quota_yaml(subscription: Subscription, namespace: str): + """Generate the YAML configuration for a ResourceQuota based on subscription.""" yaml_template = """ apiVersion: v1 kind: ResourceQuota @@ -95,22 +105,28 @@ def generate_resource_quota_yaml(subscription:Subscription ,namespace: str): node_ports=subscription.node_port, ) + def createNamespace(namespace_name): + """Create a Kubernetes namespace. Deletes it first if it already exists.""" api_client = client.CoreV1Api() try: # Check if the namespace already exists try: api_client.read_namespace(name=namespace_name) print(f"Namespace '{namespace_name}' already exists. Deleting it...") - api_client.delete_namespace(name=namespace_name, body=client.V1DeleteOptions()) + api_client.delete_namespace( + name=namespace_name, body=client.V1DeleteOptions() + ) print(f"Namespace '{namespace_name}' deleted successfully.") - + # Wait for the namespace to be fully deleted wait_for_namespace_deletion(namespace_name) - + except client.rest.ApiException as e: if e.status == 404: - print(f"Namespace '{namespace_name}' does not exist. Proceeding to create it.") + print( + f"Namespace '{namespace_name}' does not exist. Proceeding to create it." + ) else: raise e api_client.create_namespace( @@ -121,10 +137,9 @@ def createNamespace(namespace_name): print(f"Error handling namespace '{namespace_name}': {str(e)}") raise e + def wait_for_namespace_deletion(namespace_name): - """ - Wait until the namespace is fully deleted. - """ + """Wait until the namespace is fully deleted.""" api_client = client.CoreV1Api() while True: @@ -133,7 +148,7 @@ def wait_for_namespace_deletion(namespace_name): api_client.read_namespace(name=namespace_name) print(f"Namespace '{namespace_name}' is still being deleted. Waiting...") time.sleep(5) - + except client.rest.ApiException as e: if e.status == 404: print(f"Namespace '{namespace_name}' has been deleted successfully.") @@ -141,9 +156,10 @@ def wait_for_namespace_deletion(namespace_name): else: print(f"Error checking namespace '{namespace_name}': {str(e)}") raise e - + def add_labels_to_statefulset(namespace, sts_name, labels): + """Add labels to an existing Kubernetes StatefulSet.""" try: apps_v1 = client.AppsV1Api() @@ -152,10 +168,11 @@ def add_labels_to_statefulset(namespace, sts_name, labels): # Add labels to the StatefulSet's template metadata labels statefulset.spec.template.metadata.labels.update(labels) # statefulset.spec.template.metadata.annotations = annotations - - updated_statefulset = apps_v1.patch_namespaced_stateful_set(sts_name, namespace, statefulset) - + + updated_statefulset = apps_v1.patch_namespaced_stateful_set( + sts_name, namespace, statefulset + ) + return updated_statefulset except Exception as e: return f"An error occurred: {e}" - diff --git a/cluster-service/src/utils/common_utils.py b/cluster-service/src/utils/common_utils.py index eef42cd..a418aea 100644 --- a/cluster-service/src/utils/common_utils.py +++ b/cluster-service/src/utils/common_utils.py @@ -1,12 +1,24 @@ +""" +Utility functions for Kubernetes resource checks. + +Includes kubeconfig parsing and calculating cluster resource usage. +""" + import base64 import logging import tempfile -from kubernetes import client, config +from kubernetes import client, config from src.utils.best_cluster_utils import get_best_cluster def get_available_resources_fromSecret(kubeconfigSecrets): + """ + Extract CPU and memory usage percentages from kubeconfig secrets. + + Decodes kubeconfigs, collects node metrics, and returns the best cluster + based on resource availability. + """ try: clusterResource = {} for Kube_secret in kubeconfigSecrets: @@ -24,9 +36,11 @@ def get_available_resources_fromSecret(kubeconfigSecrets): temp_file.close() api = client.CustomObjectsApi() api_client = client.CoreV1Api() - k8s_nodes_matrix = api.list_cluster_custom_object("metrics.k8s.io", "v1beta1", "nodes") - nodes = api_client.list_node().items - + k8s_nodes_matrix = api.list_cluster_custom_object( + "metrics.k8s.io", "v1beta1", "nodes" + ) + nodes = api_client.list_node().items + # Use a dictionary to store metrics client_nodes_metrics = {} @@ -38,15 +52,21 @@ def get_available_resources_fromSecret(kubeconfigSecrets): clusterResource[Kube_secret["id"]]["MEMORY"] = percentage["PercMEM"] best = get_best_cluster(clusterResource) - print("Best Cluster :: ", best) + print("Best Cluster :: ", best) return best except Exception as e: logging.error("error occurs :: %s", str(e)) # Fixed string formatting - return {"error": f"Error occurred: {str(e)}"} # Return a dictionary for consistent response - + return { + "error": f"Error occurred: {str(e)}" + } # Return a dictionary for consistent response def cluster_load(nos, nmx, mx): + """ + Calculate used and total CPU/memory to produce utilization percentages. + + Returns a dictionary containing PercCPU and PercMEM values. + """ if nos is None or nmx is None: raise ValueError("Invalid node or node metrics lists") @@ -57,15 +77,15 @@ def cluster_load(nos, nmx, mx): "AllocatableCPU": ToMilliValue(no.status.allocatable["cpu"]), "AllocatableMEM": ToMB(no.status.allocatable["memory"]), "CurrentCPU": 0, # Default to 0 for new nodes without metrics - "CurrentMEM": 0 # Default to 0 for new nodes without metrics + "CurrentMEM": 0, # Default to 0 for new nodes without metrics } - for mx_item in nmx['items']: - node_name = mx_item['metadata']['name'] + for mx_item in nmx["items"]: + node_name = mx_item["metadata"]["name"] if node_name in node_metrics: node = node_metrics[node_name] - node["CurrentCPU"] = ToMilliValue(mx_item['usage']['cpu']) - node["CurrentMEM"] = ToMB(mx_item['usage']['memory']) + node["CurrentCPU"] = ToMilliValue(mx_item["usage"]["cpu"]) + node["CurrentMEM"] = ToMB(mx_item["usage"]["memory"]) node_metrics[node_name] = node ccpu, cmem, tcpu, tmem = 0, 0, 0, 0 @@ -82,33 +102,49 @@ def cluster_load(nos, nmx, mx): def to_percentage(value, total): + """ + Return percentage value of 'value' relative to 'total'. + + Avoids division-by-zero by returning 0.0 when total is zero. + """ if total == 0: return 0.0 - return (value / total) * 100.0 + return (value / total) * 100.0 # Helper function to convert to MB def ToMB(value): + """ + Convert memory values (Ki/Mi/Gi/Ti) into MB. + + Returns numeric MB value. + """ if isinstance(value, str): - if value.endswith('Ki'): + if value.endswith("Ki"): value = int(value[:-2]) / 1024 - elif value.endswith('Mi'): + elif value.endswith("Mi"): value = int(value[:-2]) - elif value.endswith('Gi'): + elif value.endswith("Gi"): value = int(value[:-2]) * 1024 - elif value.endswith('Ti'): + elif value.endswith("Ti"): value = int(value[:-2]) * 1024 * 1024 else: value = int(value) / (1024 * 1024) - return value - + return value + + def ToMilliValue(value): + """ + Convert CPU units (n, u, m, cores) into millicores. + + Returns integer millicore value. + """ if isinstance(value, str): - if value.endswith('n'): - return int(value[:-1]) / 1000000 - elif value.endswith('u'): + if value.endswith("n"): + return int(value[:-1]) / 1000000 + elif value.endswith("u"): return int(value[:-1]) / 1000 - elif value.endswith('m'): + elif value.endswith("m"): return int(value[:-1]) else: try: @@ -117,17 +153,25 @@ def ToMilliValue(value): raise ValueError("Unknown CPU value format: " + value) else: return int(value) - + + def check_namespace_existence(namespace): + """Return True if a namespace exists, otherwise False.""" try: v1 = client.CoreV1Api() v1.read_namespace(name=namespace) return True # Namespace exists except Exception as e: - return False - + return False + + +def get_pod_status(namespace: str, name: str): + """ + Return the current status (phase) of a pod. -def get_pod_status(namespace:str, name:str): + If the namespace does not exist, returns 'Failed'. On any other + error, returns 'Creating'. + """ try: if not check_namespace_existence(namespace): return "Failed" @@ -137,5 +181,5 @@ def get_pod_status(namespace:str, name:str): pod_status = pod.status.phase return pod_status except Exception as e: - logging.error("error occurs while fetching status :: %s",str(e)) + logging.error("error occurs while fetching status :: %s", str(e)) return "Creating" diff --git a/cluster-service/src/utils/secret_utils.py b/cluster-service/src/utils/secret_utils.py index 452cb4e..1627755 100644 --- a/cluster-service/src/utils/secret_utils.py +++ b/cluster-service/src/utils/secret_utils.py @@ -1,15 +1,26 @@ +"""Utility functions for retrieving secrets from Dapr's Vault integration.""" + import logging -from dapr.clients import DaprClient from http.client import HTTPException +from dapr.clients import DaprClient + + def get_vault_secret(secret_Id): - logging.info("SCRET_ID :: %s",secret_Id) + """ + Retrieve a specific secret value from the Vault secret store via Dapr. + + Logs the request, fetches the secret from the Dapr secret store named + "vault", and returns the value associated with the given secret_id. + Raises an HTTPException if the retrieval fails. + """ + logging.info("SCRET_ID :: %s", secret_Id) try: with DaprClient() as dprClient: secret = dprClient.get_secret(store_name="vault", key="dapr") - secret = secret.secret.get(secret_Id) - logging.info('Secret From VAult :: %s', secret) + secret = secret.secret.get(secret_Id) + logging.info("Secret From VAult :: %s", secret) return secret except Exception as e: - logging.error("Error on fetching secret :: %s", str(e)) - raise HTTPException(status_code=500, detail=str(e)) \ No newline at end of file + logging.error("Error on fetching secret :: %s", str(e)) + raise HTTPException(status_code=500, detail=str(e)) diff --git a/cluster-service/tests/__init__.py b/cluster-service/tests/__init__.py index e69de29..fae6326 100644 --- a/cluster-service/tests/__init__.py +++ b/cluster-service/tests/__init__.py @@ -0,0 +1 @@ +"""Test package initialization.""" diff --git a/cluster-service/tests/test_best_cluster_utils.py b/cluster-service/tests/test_best_cluster_utils.py index 4976f50..64509e5 100644 --- a/cluster-service/tests/test_best_cluster_utils.py +++ b/cluster-service/tests/test_best_cluster_utils.py @@ -1,110 +1,66 @@ +"""Unit tests for verifying the behavior of the get_best_cluster function.""" import unittest + from utils.best_cluster_utils import get_best_cluster + class TestDataSelection(unittest.TestCase): + """Tests for cluster selection logic in get_best_cluster().""" def test_get_best_cluster_single_entry(self): - data = { - 'test1.yaml': { - 'CPU': 50, - 'MEMORY': 70 - } - } + """Ensure correct output when only one cluster entry exists.""" + data = {"test1.yaml": {"CPU": 50, "MEMORY": 70}} - expected_result = { - 'test1.yaml': { - 'best_cpu': 50, - 'best_memory': 70 - } - } + expected_result = {"test1.yaml": {"best_cpu": 50, "best_memory": 70}} result = get_best_cluster(data) self.assertEqual(result, expected_result) def test_get_best_cluster_multiple_entries(self): + """Verify best cluster selection among multiple entries.""" data = { - 'test1.yaml': { - 'CPU': 80, - 'MEMORY': 90 - }, - 'test2.yaml': { - 'CPU': 70, - 'MEMORY': 60 - }, - 'test3.yaml': { - 'CPU': 50, - 'MEMORY': 70 - } - } - expected_result = { - 'test3.yaml': { - 'best_cpu': 50, - 'best_memory': 70 - } + "test1.yaml": {"CPU": 80, "MEMORY": 90}, + "test2.yaml": {"CPU": 70, "MEMORY": 60}, + "test3.yaml": {"CPU": 50, "MEMORY": 70}, } + expected_result = {"test3.yaml": {"best_cpu": 50, "best_memory": 70}} result = get_best_cluster(data) self.assertEqual(result, expected_result) def test_get_best_cluster_no_data_below_threshold(self): + """Return empty result when all clusters exceed thresholds.""" data = { - 'test1.yaml': { - 'CPU': 90, - 'MEMORY': 85 - }, - 'test2.yaml': { - 'CPU': 95, - 'MEMORY': 75 - } + "test1.yaml": {"CPU": 90, "MEMORY": 85}, + "test2.yaml": {"CPU": 95, "MEMORY": 75}, } expected_result = {} result = get_best_cluster(data) self.assertEqual(result, expected_result) def test_get_best_cluster_with_notification(self): + """Verify prioritization logic when some entries exceed thresholds.""" data = { - 'test1.yaml': { - 'CPU': 80, - 'MEMORY': 70 - }, - 'test2.yaml': { - 'CPU': 90, - 'MEMORY': 60 - }, - 'test3.yaml': { - 'CPU': 50, - 'MEMORY': 70 - } - } - expected_result = { - 'test3.yaml': { - 'best_cpu': 50, - 'best_memory': 70 - } + "test1.yaml": {"CPU": 80, "MEMORY": 70}, + "test2.yaml": {"CPU": 90, "MEMORY": 60}, + "test3.yaml": {"CPU": 50, "MEMORY": 70}, } + expected_result = {"test3.yaml": {"best_cpu": 50, "best_memory": 70}} result = get_best_cluster(data) self.assertEqual(result, expected_result) def test_get_best_cluster_with_notification1(self): + """Ensure empty result when mixed values still trigger notification.""" data = { - 'test1.yaml': { - 'CPU': 80, - 'MEMORY': 10 - }, - 'test2.yaml': { - 'CPU': 10, - 'MEMORY': 90 - }, - } - expected_result = { - + "test1.yaml": {"CPU": 80, "MEMORY": 10}, + "test2.yaml": {"CPU": 10, "MEMORY": 90}, } + expected_result = {} result = get_best_cluster(data) - print("ACTUAL RESULT :: ",result) - self.assertEqual(result, expected_result) - - + print("ACTUAL RESULT :: ", result) + self.assertEqual(result, expected_result) # Check if the notification was sent # Implement your own assertions or mocks for the notification functionality -if __name__ == '__main__': + +if __name__ == "__main__": unittest.main() From 3d6356dc9d090f74ec4b6c2d49d3f9728714577b Mon Sep 17 00:00:00 2001 From: Subash Nagarkoti Date: Fri, 5 Dec 2025 11:07:48 +0545 Subject: [PATCH 4/7] fix: added the venv setup in pipeline --- .github/workflows/pre-commit.yaml | 18 +- cluster-api/main.py | 53 ++++-- cluster-api/test_cluster_status.py | 76 +++++---- cluster-api/test_websocket.py | 258 +++++++++++++++-------------- commitlint.config.cjs | 24 +++ 5 files changed, 263 insertions(+), 166 deletions(-) create mode 100644 commitlint.config.cjs diff --git a/.github/workflows/pre-commit.yaml b/.github/workflows/pre-commit.yaml index 3a2702d..4b6cb79 100644 --- a/.github/workflows/pre-commit.yaml +++ b/.github/workflows/pre-commit.yaml @@ -28,10 +28,17 @@ jobs: with: python-version: "3.11" - - name: Install pre-commit + - name: Create and activate Python venv run: | + python -m venv .venv + source .venv/bin/activate python -m pip install --upgrade pip pip install pre-commit + shell: bash + + # Optional: add venv to PATH for subsequent steps + - name: Add venv to PATH + run: echo "${{ github.workspace }}/.venv/bin" >> $GITHUB_PATH - name: Load Pre-commit Config run: | @@ -43,6 +50,7 @@ jobs: else echo "✔ Using project's existing .pre-commit-config.yaml" fi + shell: bash - name: Inject temporary Stylelint config for CI run: | @@ -62,13 +70,14 @@ jobs: else echo "✔ .stylelintrc.json already exists — skipping" fi - + shell: bash # -------------------------------------------------------------------- # STEP 1: Run pre-commit (capture full logs and exit code safely) # -------------------------------------------------------------------- - name: Run pre-commit (full logs) id: runprecommit run: | + source .venv/bin/activate echo "🔍 Running full pre-commit checks..." set +e # allow failure @@ -78,12 +87,14 @@ jobs: echo "Pre-commit exit code: $exit_code" echo "$exit_code" > precommit_exit_code.txt + shell: bash # -------------------------------------------------------------------- # STEP 2: Summary of FAILED hooks # -------------------------------------------------------------------- - name: Pre-commit summary of failed hooks run: | + source .venv/bin/activate echo "=====================================================" echo " PRE-COMMIT SUMMARY" echo "=====================================================" @@ -113,4 +124,5 @@ jobs: | sed 's/^/ • /' || echo " None" echo "-----------------------------------------------------" - exit $exit_code \ No newline at end of file + exit $exit_code + shell: bash diff --git a/cluster-api/main.py b/cluster-api/main.py index 6aacd9e..380974a 100644 --- a/cluster-api/main.py +++ b/cluster-api/main.py @@ -1,19 +1,26 @@ +"""FastAPI application for cluster management API. + +This module defines the FastAPI app, middleware, database startup/shutdown events, +and route registrations. +""" + import logging import os + from fastapi import Depends, FastAPI from fastapi.middleware import Middleware -from pymongo import MongoClient +from fastapi.middleware.cors import CORSMiddleware from middleware.dependency import has_access from middleware.middleware import validate_keycloak_token +from pymongo import MongoClient +from routes.cluster import router as cluster from routes.host_cluster import router as host_cluster_router -from routes.subscription import router as subscription_route from routes.kube_list import router as kube_route from routes.public import router as public_route +from routes.subscription import router as subscription_route from routes.user import router as user_route -from routes.cluster import router as cluster -from fastapi.middleware.cors import CORSMiddleware -from utills.seeder import seed_db from routes.websocket import router as websocket_router +from utills.seeder import seed_db app = FastAPI() @@ -30,10 +37,12 @@ # routes PROTECTED = [Depends(has_access)] + @app.on_event("startup") def startup_db_client(): - app.mongodb_client = MongoClient(os.getenv('ATLAS_URI')) - app.database = app.mongodb_client[os.getenv('DB_NAME')] + """Initialize MongoDB client and seed database if necessary.""" + app.mongodb_client = MongoClient(os.getenv("ATLAS_URI")) + app.database = app.mongodb_client[os.getenv("DB_NAME")] logging.basicConfig(level=logging.INFO) # Check if collections exist and seed data if they do not @@ -43,13 +52,33 @@ def startup_db_client(): logging.info(f"Collection {collection} not found. Seeding data...") seed_db(app.database) + @app.on_event("shutdown") def shutdown_db_client(): + """Close MongoDB client on application shutdown.""" app.mongodb_client.close() + + app.include_router(public_route, tags=["public"], prefix="/v1/public") -app.include_router(host_cluster_router, tags=["host-cluster"], prefix="/v1/host-clusters", dependencies=PROTECTED) -app.include_router(subscription_route, tags=["subscription"], prefix="/v1/subscriptions", dependencies=PROTECTED) -app.include_router(kube_route, tags=["kubeversion"], prefix="/v1/kubeversion", dependencies=PROTECTED) -app.include_router(user_route, tags=["user"], prefix="/v1/users", dependencies=PROTECTED) -app.include_router(cluster, tags=["cluster"], prefix="/v1/clusters", dependencies=PROTECTED) +app.include_router( + host_cluster_router, + tags=["host-cluster"], + prefix="/v1/host-clusters", + dependencies=PROTECTED, +) +app.include_router( + subscription_route, + tags=["subscription"], + prefix="/v1/subscriptions", + dependencies=PROTECTED, +) +app.include_router( + kube_route, tags=["kubeversion"], prefix="/v1/kubeversion", dependencies=PROTECTED +) +app.include_router( + user_route, tags=["user"], prefix="/v1/users", dependencies=PROTECTED +) +app.include_router( + cluster, tags=["cluster"], prefix="/v1/clusters", dependencies=PROTECTED +) app.include_router(websocket_router, tags=["websocket"], prefix="/v1/websocket") diff --git a/cluster-api/test_cluster_status.py b/cluster-api/test_cluster_status.py index cbd677b..acc44e7 100644 --- a/cluster-api/test_cluster_status.py +++ b/cluster-api/test_cluster_status.py @@ -1,44 +1,47 @@ -import requests +""" +Module to update cluster status and simulate cluster lifecycle via API. + +Provides utility functions to send status updates and simulate a cluster's +lifecycle for testing purposes. +""" + +import argparse import json import sys import time -import argparse from datetime import datetime +import requests + + def update_cluster_status(cluster_id, status, server_url="http://localhost:8000"): """Send a request to update cluster status. - + Args: cluster_id: ID of the cluster to update status: New status string for the cluster server_url: API server URL - + Returns: tuple: (success, response_data) """ url = f"{server_url}/v1/public/cluster-status" - - payload = { - "id": cluster_id, - "status": status - } - - headers = { - "Content-Type": "application/json", - "Accept": "application/json" - } - + + payload = {"id": cluster_id, "status": status} + + headers = {"Content-Type": "application/json", "Accept": "application/json"} + try: start_time = time.time() response = requests.patch(url, json=payload, headers=headers) request_time = time.time() - start_time - + print(f"Request completed in {request_time:.3f} seconds") print(f"Status code: {response.status_code}") - + response_data = response.json() print(json.dumps(response_data, indent=2)) - + return response.ok, response_data except requests.RequestException as e: print(f"Request error: {str(e)}") @@ -48,6 +51,7 @@ def update_cluster_status(cluster_id, status, server_url="http://localhost:8000" print(f"Raw response: {response.text}") return False, None + def simulate_cluster_lifecycle(cluster_id, server_url="http://localhost:8000"): """Simulate a complete cluster lifecycle with status updates.""" statuses = [ @@ -58,40 +62,50 @@ def simulate_cluster_lifecycle(cluster_id, server_url="http://localhost:8000"): ("Upgrading", 4), ("Running", 3), ("Terminating", 2), - ("Terminated", 0) + ("Terminated", 0), ] - + for status, delay in statuses: print(f"\n=== Updating cluster {cluster_id} to '{status}' state ===") success, _ = update_cluster_status(cluster_id, status, server_url) - + if not success: print("Failed to update status, aborting simulation") return False - + if delay > 0: print(f"Waiting {delay} seconds before next status update...") time.sleep(delay) - + print("\nCluster lifecycle simulation completed!") return True + if __name__ == "__main__": - parser = argparse.ArgumentParser(description='Update cluster status or simulate lifecycle') - parser.add_argument('cluster_id', help='ID of the cluster to update') - parser.add_argument('--status', '-s', help='New status for the cluster') - parser.add_argument('--server', '-u', default='http://localhost:8000', help='API server URL') - parser.add_argument('--simulate', '-sim', action='store_true', help='Simulate full cluster lifecycle') - + parser = argparse.ArgumentParser( + description="Update cluster status or simulate lifecycle" + ) + parser.add_argument("cluster_id", help="ID of the cluster to update") + parser.add_argument("--status", "-s", help="New status for the cluster") + parser.add_argument( + "--server", "-u", default="http://localhost:8000", help="API server URL" + ) + parser.add_argument( + "--simulate", + "-sim", + action="store_true", + help="Simulate full cluster lifecycle", + ) + args = parser.parse_args() - + if args.simulate: print(f"Simulating cluster lifecycle for cluster {args.cluster_id}") simulate_cluster_lifecycle(args.cluster_id, args.server) elif args.status: print(f"Updating cluster {args.cluster_id} to status: {args.status}") success, _ = update_cluster_status(args.cluster_id, args.status, args.server) - + if success: print("Status update sent successfully!") else: diff --git a/cluster-api/test_websocket.py b/cluster-api/test_websocket.py index 6969a0d..ec35ddd 100644 --- a/cluster-api/test_websocket.py +++ b/cluster-api/test_websocket.py @@ -1,120 +1,138 @@ -import asyncio -import websockets -import json -import sys -import signal -import time -import argparse -from datetime import datetime - -# Flag to control the main loop -running = True - -def handle_signal(sig, frame): - """Handle interrupt signal to allow graceful shutdown.""" - global running - print("\nShutting down...") - running = False - -# Register signal handlers -signal.signal(signal.SIGINT, handle_signal) -signal.signal(signal.SIGTERM, handle_signal) - -def format_message(message_data): - """Format a received message for display.""" - try: - if isinstance(message_data, str): - data = json.loads(message_data) - else: - data = message_data - - event_type = data.get('event', 'unknown') - timestamp = data.get('timestamp', datetime.now().isoformat()) - - if event_type == 'cluster_status_updated': - return f"[{timestamp}] CLUSTER UPDATE: {data.get('cluster_name', 'unknown')} (ID: {data.get('cluster_id', 'unknown')}) is now {data.get('status', 'unknown')}" - elif event_type == 'connection_established': - return f"[{timestamp}] CONNECTION ESTABLISHED: {data.get('message', '')}" - elif event_type == 'message_received': - return f"[{timestamp}] MESSAGE ACKNOWLEDGED: Server received {json.dumps(data.get('data_received', {}))}" - else: - return f"[{timestamp}] {json.dumps(data)}" - except Exception as e: - return f"Error formatting message: {str(e)}\nRaw message: {message_data}" - -async def send_periodic_ping(websocket, interval=30): - """Send periodic ping messages to keep the connection alive.""" - while running: - try: - await asyncio.sleep(interval) - if not running: - break - - ping_message = { - "type": "ping", - "timestamp": datetime.now().isoformat() - } - await websocket.send(json.dumps(ping_message)) - print(f"Ping sent at {datetime.now().isoformat()}") - except Exception as e: - if running: - print(f"Error sending ping: {str(e)}") - break - -async def test_websocket(user_id, server_url, keep_alive=True): - """Connect to WebSocket server and handle messages.""" - uri = f"{server_url}/v1/websocket/{user_id}" - - print(f"Connecting to {uri} as user {user_id}...") - try: - async with websockets.connect(uri, ping_interval=60) as websocket: - print(f"Connected to WebSocket server as user {user_id}!") - - # Start ping task if keep_alive is True - ping_task = None - if keep_alive: - ping_task = asyncio.create_task(send_periodic_ping(websocket)) - - # Send initial message - initial_message = { - "message": "Hello from client", - "user_id": user_id, - "client_timestamp": datetime.now().isoformat() - } - await websocket.send(json.dumps(initial_message)) - print(f"Sent initial message: {json.dumps(initial_message)}") - - # Listen for messages - while running: - try: - response = await websocket.recv() - formatted = format_message(response) - print(formatted) - except websockets.exceptions.ConnectionClosed: - print("Connection closed by server") - break - except Exception as e: - print(f"Error receiving message: {str(e)}") - if not running: - break - await asyncio.sleep(1) - - # Cancel ping task - if ping_task: - ping_task.cancel() - - except Exception as e: - print(f"Connection error: {str(e)}") - -if __name__ == "__main__": - parser = argparse.ArgumentParser(description='WebSocket Client for Cluster Status Updates') - parser.add_argument('--user', '-u', default='test-user', help='User ID to connect with') - parser.add_argument('--server', '-s', default='ws://localhost:8000', help='WebSocket server URL') - parser.add_argument('--no-ping', action='store_true', help='Disable periodic ping messages') - - args = parser.parse_args() - - try: - asyncio.run(test_websocket(args.user, args.server, not args.no_ping)) - except KeyboardInterrupt: - print("Interrupted by user") +""" +WebSocket client to receive cluster status updates and send messages. + +Connects to the server, handles incoming messages, and optionally sends periodic pings. +""" + +# import argparse +# import asyncio +# import json +# import signal +# import sys +# import time +# from datetime import datetime + +# import websockets + +# # Flag to control the main loop +# running = True + + +# def handle_signal(sig, frame): +# """Handle interrupt signal to allow graceful shutdown.""" +# global running +# print("\nShutting down...") +# running = False + + +# # Register signal handlers +# signal.signal(signal.SIGINT, handle_signal) +# signal.signal(signal.SIGTERM, handle_signal) + + +# def format_message(message_data): +# """Format a received message for display.""" +# try: +# if isinstance(message_data, str): +# data = json.loads(message_data) +# else: +# data = message_data + +# event_type = data.get("event", "unknown") +# timestamp = data.get("timestamp", datetime.now().isoformat()) + +# if event_type == "cluster_status_updated": +# return f"[{timestamp}] CLUSTER UPDATE: {data.get('cluster_name', 'unknown')} (ID: {data.get('cluster_id', 'unknown')}) is now {data.get('status', 'unknown')}" +# elif event_type == "connection_established": +# return f"[{timestamp}] CONNECTION ESTABLISHED: {data.get('message', '')}" +# elif event_type == "message_received": +# return f"[{timestamp}] MESSAGE ACKNOWLEDGED: Server received {json.dumps(data.get('data_received', {}))}" +# else: +# return f"[{timestamp}] {json.dumps(data)}" +# except Exception as e: +# return f"Error formatting message: {str(e)}\nRaw message: {message_data}" + + +# async def send_periodic_ping(websocket, interval=30): +# """Send periodic ping messages to keep the connection alive.""" +# while running: +# try: +# await asyncio.sleep(interval) +# if not running: +# break + +# ping_message = {"type": "ping", "timestamp": datetime.now().isoformat()} +# await websocket.send(json.dumps(ping_message)) +# print(f"Ping sent at {datetime.now().isoformat()}") +# except Exception as e: +# if running: +# print(f"Error sending ping: {str(e)}") +# break + + +# async def test_websocket(user_id, server_url, keep_alive=True): +# """Connect to WebSocket server and handle messages.""" +# uri = f"{server_url}/v1/websocket/{user_id}" + +# print(f"Connecting to {uri} as user {user_id}...") +# try: +# async with websockets.connect(uri, ping_interval=60) as websocket: +# print(f"Connected to WebSocket server as user {user_id}!") + +# # Start ping task if keep_alive is True +# ping_task = None +# if keep_alive: +# ping_task = asyncio.create_task(send_periodic_ping(websocket)) + +# # Send initial message +# initial_message = { +# "message": "Hello from client", +# "user_id": user_id, +# "client_timestamp": datetime.now().isoformat(), +# } +# await websocket.send(json.dumps(initial_message)) +# print(f"Sent initial message: {json.dumps(initial_message)}") + +# # Listen for messages +# while running: +# try: +# response = await websocket.recv() +# formatted = format_message(response) +# print(formatted) +# except websockets.exceptions.ConnectionClosed: +# print("Connection closed by server") +# break +# except Exception as e: +# print(f"Error receiving message: {str(e)}") +# if not running: +# break +# await asyncio.sleep(1) + +# # Cancel ping task +# if ping_task: +# ping_task.cancel() + +# except Exception as e: +# print(f"Connection error: {str(e)}") + + +# if __name__ == "__main__": +# parser = argparse.ArgumentParser( +# description="WebSocket Client for Cluster Status Updates" +# ) +# parser.add_argument( +# "--user", "-u", default="test-user", help="User ID to connect with" +# ) +# parser.add_argument( +# "--server", "-s", default="ws://localhost:8000", help="WebSocket server URL" +# ) +# parser.add_argument( +# "--no-ping", action="store_true", help="Disable periodic ping messages" +# ) + +# args = parser.parse_args() + +# try: +# asyncio.run(test_websocket(args.user, args.server, not args.no_ping)) +# except KeyboardInterrupt: +# print("Interrupted by user") diff --git a/commitlint.config.cjs b/commitlint.config.cjs new file mode 100644 index 0000000..a0e89b0 --- /dev/null +++ b/commitlint.config.cjs @@ -0,0 +1,24 @@ +module.exports = { + extends: ['@commitlint/config-conventional'], + rules: { + 'type-enum': [2, 'always', [ + 'feat', // A new feature + 'fix', // A bug fix + 'docs', // Documentation only changes + 'style', // Changes that do not affect code meaning + 'refactor', // A code change that neither fixes a bug nor adds a feature + 'perf', // A code change that improves performance + 'test', // Adding missing tests or correcting existing tests + 'build', // Build system or external dependencies + 'ci', // CI configuration changes + 'chore', // Other changes that don't modify src or test files + 'revert', // Reverts a previous commit + ]], + 'type-case': [2, 'always', 'lowerCase'], + 'type-empty': [2, 'never'], + 'scope-case': [2, 'always', 'lowerCase'], + 'subject-empty': [2, 'never'], + 'subject-full-stop': [2, 'never', '.'], + 'header-max-length': [2, 'always', 72], + }, +}; From 31b80adbfa70c1858895f04a27dbed2251f11ba0 Mon Sep 17 00:00:00 2001 From: Subash Nagarkoti Date: Fri, 5 Dec 2025 11:38:17 +0545 Subject: [PATCH 5/7] fix: added the venv setup in pipe --- .codespell-ignore-words | 10 ++++++++++ .flake8 | 1 + cluster-api/README.md | 2 +- manifest/charts/dapr/README.md | 6 +++--- manifest/charts/keycloak/charts/postgresql/README.md | 4 ++-- manifest/charts/rabbitmq/README.md | 6 +++--- 6 files changed, 20 insertions(+), 9 deletions(-) diff --git a/.codespell-ignore-words b/.codespell-ignore-words index 11fec86..8a32e13 100644 --- a/.codespell-ignore-words +++ b/.codespell-ignore-words @@ -1 +1,11 @@ ser +hashi +Wheter +aci +ot +boostrapping +confuguration +certifcates +associtive +commmands +properies diff --git a/.flake8 b/.flake8 index 5b280e1..de8980f 100644 --- a/.flake8 +++ b/.flake8 @@ -15,6 +15,7 @@ ignore = B008 B006 C417 + D100,D101,D102,D103,D104,D105,D106 exclude = venv/ diff --git a/cluster-api/README.md b/cluster-api/README.md index b7406f8..d4f1bfe 100644 --- a/cluster-api/README.md +++ b/cluster-api/README.md @@ -1,4 +1,4 @@ -# 📘 **Cluster API Documentation** +## 📘 **Cluster API Documentation** Welcome to the **Cluster API** project! This documentation provides a comprehensive guide to set up, run, and leverage the power of FastAPI integrated with Dapr for seamless microservice communication. Follow the steps below to get started and enable message broker functionality using Dapr sidecars. diff --git a/manifest/charts/dapr/README.md b/manifest/charts/dapr/README.md index 70e21d6..56034b0 100644 --- a/manifest/charts/dapr/README.md +++ b/manifest/charts/dapr/README.md @@ -88,7 +88,7 @@ The Helm chart has the follow configuration options that can be supplied: | `global.mtls.enabled` | Mutual TLS enablement | `true` | | `global.mtls.workloadCertTTL` | TTL for workload cert | `24h` | | `global.mtls.allowedClockSkew` | Allowed clock skew for workload cert rotation | `15m` | -| `global.dnsSuffix` | Kuberentes DNS suffix | `.cluster.local` | +| `global.dnsSuffix` | Kubernetes DNS suffix | `.cluster.local` | | `global.daprControlPlaneOs` | Operating System for Dapr control plane | `linux` | | `global.daprControlPlaneArch` | CPU Architecture for Dapr control plane | `amd64` | | `global.nodeSelector` | Pods will be scheduled onto a node node whose labels match the nodeSelector | `{}` | @@ -98,7 +98,7 @@ The Helm chart has the follow configuration options that can be supplied: | `global.issuerFilenames.ca` | Custom name of the file containing the root CA certificate inside the container | `ca.crt` | | `global.issuerFilenames.cert` | Custom name of the file containing the leaf certificate inside the container | `issuer.crt` | | `global.issuerFilenames.key` | Custom name of the file containing the leaf certificate's key inside the container | `issuer.key` | -| `global.actors.enabled` | Enables the Dapr actors building block. When "false", the Dapr Placement serice is not installed, and attempting to use Dapr actors will fail. | `true` | +| `global.actors.enabled` | Enables the Dapr actors building block. When "false", the Dapr Placement service is not installed, and attempting to use Dapr actors will fail. | `true` | | `global.rbac.namespaced` | Removes cluster wide permissions where applicable | `false` | | `global.argoRolloutServiceReconciler.enabled` | Enable the service reconciler for Dapr-enabled Argo Rollouts | `false` | @@ -165,7 +165,7 @@ The Helm chart has the follow configuration options that can be supplied: | `dapr_sidecar_injector.runAsNonRoot` | Boolean value for `securityContext.runAsNonRoot` for the Sidecar Injector container itself. You may have to set this to `false` when running in Minikube | `true` | | `dapr_sidecar_injector.sidecarRunAsNonRoot` | When this boolean value is true (the default), the injected sidecar containers have `runAsRoot: true`. You may have to set this to `false` when running Minikube | `true` | | `dapr_sidecar_injector.sidecarReadOnlyRootFilesystem` | When this boolean value is true (the default), the injected sidecar containers have `readOnlyRootFilesystem: true` | `true` | -| `dapr_sidecar_injector.sidecarDropALLCapabilities` | When this boolean valus is true, the injected sidecar containers have `securityContext.capabilities.drop: ["ALL"]` | `false` | +| `dapr_sidecar_injector.sidecarDropALLCapabilities` | When this boolean values is true, the injected sidecar containers have `securityContext.capabilities.drop: ["ALL"]` | `false` | | `dapr_sidecar_injector.allowedServiceAccounts` | String value for extra allowed service accounts in the format of `namespace1:serviceAccount1,namespace2:serviceAccount2` | `""` | | `dapr_sidecar_injector.allowedServiceAccountsPrefixNames` | Comma-separated list of extra allowed service accounts. Each item in the list should be in the format of namespace:serviceaccount. To match service accounts by a common prefix, you can add an asterisk (`*`) at the end of the prefix. For instance, ns1*:sa2* will match any service account that starts with sa2, whose namespace starts with ns1. For example, it will match service accounts like sa21 and sa2223 in namespaces such as ns1, ns1dapr, and so on. | `""` | | `dapr_sidecar_injector.resources` | Value of `resources` attribute. Can be used to set memory/cpu resources/limits. See the section "Resource configuration" above. Defaults to empty | `{}` | diff --git a/manifest/charts/keycloak/charts/postgresql/README.md b/manifest/charts/keycloak/charts/postgresql/README.md index fabceeb..996bb7d 100644 --- a/manifest/charts/keycloak/charts/postgresql/README.md +++ b/manifest/charts/keycloak/charts/postgresql/README.md @@ -623,7 +623,7 @@ The [Bitnami PostgreSQL](https://github.com/bitnami/containers/tree/main/bitnami Persistent Volume Claims are used to keep the data across deployments. This is known to work in GCE, AWS, and minikube. See the [Parameters](#parameters) section to configure the PVC or to disable persistence. -If you already have data in it, you will fail to sync to standby nodes for all commits, details can refer to the [code present in the container repository](https://github.com/bitnami/containers/tree/main/bitnami/postgresql). If you need to use those data, please covert them to sql and import after `helm install` finished. +If you already have data in it, you will fail to sync to standby nodes for all commits, details can refer to the [code present in the container repository](https://github.com/bitnami/containers/tree/main/bitnami/postgresql). If you need to use those data, please convert them to sql and import after `helm install` finished. ## NetworkPolicy @@ -682,4 +682,4 @@ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and -limitations under the License. \ No newline at end of file +limitations under the License. diff --git a/manifest/charts/rabbitmq/README.md b/manifest/charts/rabbitmq/README.md index 404b935..337ff99 100644 --- a/manifest/charts/rabbitmq/README.md +++ b/manifest/charts/rabbitmq/README.md @@ -903,9 +903,9 @@ See the [Upgrading guide](https://www.rabbitmq.com/upgrade.html) and the [Rabbit - The layout of the persistent volumes has changed (if using persistence). Action is required if preserving data through the upgrade is desired: - The data has moved from `mnesia/` within the persistent volume to the root of the persistent volume - The `config/` and `schema/` directories within the persistent volume are no longer used - - An init container can be used to move and clean up the peristent volumes. An example can be found [here](https://github.com/bitnami/charts/issues/10913#issuecomment-1169619513). + - An init container can be used to move and clean up the persistent volumes. An example can be found [here](https://github.com/bitnami/charts/issues/10913#issuecomment-1169619513). - Alternately the value `persistence.subPath` can be overridden to be `mnesia` so that the directory layout is consistent with what it was previously. - - Note however that this will leave the unused `config/` and `schema/` directories within the peristent volume forever. + - Note however that this will leave the unused `config/` and `schema/` directories within the persistent volume forever. Consequences: @@ -955,4 +955,4 @@ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and -limitations under the License. \ No newline at end of file +limitations under the License. From 4e427a45c919d14964e9744cc8570d8f52f91607 Mon Sep 17 00:00:00 2001 From: Subash Nagarkoti Date: Fri, 5 Dec 2025 11:49:05 +0545 Subject: [PATCH 6/7] fix: pre-commit hook setup issues --- .stylelintrc.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stylelintrc.json b/.stylelintrc.json index 98e6f06..2bcfa06 100644 --- a/.stylelintrc.json +++ b/.stylelintrc.json @@ -4,6 +4,6 @@ "color-hex-length": "short", "no-duplicate-selectors": true, "selector-max-id": 0, - "selector-no-qualifyin`g-type": true + "selector-no-qualifying-type": true } } From 859c1ddef52aa4f41264db8ec1573ecabe402400 Mon Sep 17 00:00:00 2001 From: Subash Nagarkoti Date: Fri, 5 Dec 2025 12:01:35 +0545 Subject: [PATCH 7/7] fix: pre-commit hook setup for clustermanager repo issues --- .codespell-ignore-words | 2 +- .pre-commit-config.yaml | 1 + CHANGELOG.md | 2 +- CODE_OF_CONDUCT.md | 2 +- CONTRIBUTING.md | 12 +- README.md | 8 +- cluster-api/Dockerfile | 2 +- cluster-api/env_example.bash | 2 +- cluster-api/middleware/models/cluster.py | 10 +- .../middleware/models/common_response.py | 6 +- .../middleware/models/generate_kubeconfig.py | 6 +- cluster-api/middleware/models/host_cluster.py | 10 +- cluster-api/middleware/models/kube_version.py | 4 +- cluster-api/middleware/models/subscription.py | 25 +- cluster-api/middleware/models/user.py | 15 +- cluster-api/requirements.txt | 2 +- cluster-service/env.example.sh | 2 +- cluster-service/main.py | 9 +- cluster-service/requirements.txt | 2 +- cluster-status/.env_sample | 2 +- cluster-status/Dockerfile | 2 +- cluster-status/README.md | 2 +- cluster-status/deployment/apply.sh | 2 +- .../deployment/cluster-role-binding.yml | 2 +- cluster-status/deployment/destroy.sh | 2 +- cluster-status/deployment/service-account.yml | 2 +- cluster-status/go.mod | 2 - cluster-status/go.sum | 3 - cluster-ui/.eslintrc.cjs | 2 +- cluster-ui/.gitignore | 2 +- cluster-ui/config_example.js | 4 +- cluster-ui/package-lock.json | 2352 ++++++++--------- cluster-ui/package.json | 22 +- cluster-ui/public/config.js | 2 +- cluster-ui/public/vite.svg | 2 +- cluster-ui/src/app.tsx | 6 +- cluster-ui/src/assets/logo.svg | 2 +- cluster-ui/src/assets/react.svg | 2 +- .../src/container/WebsocketConnection.tsx | 14 +- cluster-ui/src/helpers/commonHelper.tsx | 2 +- cluster-ui/src/hooks/useClusterActions.ts | 2 +- cluster-ui/src/hooks/useClusterDetails.ts | 2 +- .../src/hooks/useClusterListResponse.ts | 2 +- cluster-ui/src/hooks/useClusterVersionList.ts | 2 +- .../src/hooks/useCreateClusterResponse.ts | 2 +- .../src/hooks/useHostClusterListResponse.ts | 2 +- cluster-ui/src/hooks/useKubeConfigDownload.ts | 2 +- .../src/hooks/useSubscriptionsResponse.ts | 2 +- cluster-ui/src/index.css | 6 +- cluster-ui/src/main.tsx | 2 +- cluster-ui/src/models/clusters.d.ts | 2 +- cluster-ui/src/models/response.d.ts | 3 - cluster-ui/src/models/subscriptions.ts | 6 +- cluster-ui/src/models/user.d.ts | 2 +- cluster-ui/src/services/clusterService.ts | 2 +- cluster-ui/src/services/keycloak.tsx | 2 +- cluster-ui/src/store/useSubscription.ts | 2 +- .../clusterActionArea/clusterActionArea.tsx | 4 +- .../clusterInfoArea/clusterInfoArea.tsx | 2 +- .../components/clusterList/clusterList.tsx | 4 +- .../views/components/clusterList/table.d.ts | 1 - .../components/clusterList/table.utils.ts | 2 - .../clusterList/tableContents.utils.tsx | 4 +- .../createCluster/createCluster.tsx | 6 +- .../src/views/components/header/header.tsx | 2 +- .../components/profileMenu/profileMenu.tsx | 4 +- .../views/pages/ClusterDetail/clusterInfo.tsx | 2 +- cluster-ui/tsconfig.node.json | 8 +- cluster-ui/vite.config.ts | 4 +- commitlint.config.js | 2 +- .../contributing/submitting-pull-requests.md | 22 +- docs/content/contributing/thank-you.md | 2 +- docs/script/clusterManager_setup-local.sh | 50 +- .../templates/dapr_operator_service.yaml | 4 +- .../templates/dapr_placement_service.yaml | 2 +- .../templates/dapr_sentry_service.yaml | 4 +- .../dapr_sidecar_injector_deployment.yaml | 2 +- manifest/charts/dapr/crds/httpendpoints.yaml | 2 +- manifest/charts/dapr/crds/resiliency.yaml | 1 - manifest/charts/dapr/crds/subscription.yaml | 4 +- manifest/charts/keycloak/README.md | 2 +- .../charts/common/templates/_utils.tpl | 4 +- .../charts/common/templates/_utils.tpl | 4 +- .../charts/postgresql/values.schema.json | 198 +- .../templates/configmap-env-vars.yaml | 5 +- .../charts/keycloak/templates/tls-secret.yaml | 1 - manifest/charts/mongodb/README.md | 2 +- .../common/templates/_compatibility.tpl | 2 +- .../charts/common/templates/_errors.tpl | 2 +- .../charts/common/templates/_images.tpl | 1 - .../charts/common/templates/_resources.tpl | 16 +- .../charts/common/templates/_utils.tpl | 4 +- .../charts/mongodb/templates/_helpers.tpl | 2 +- .../mongodb/templates/networkpolicy.yaml | 4 +- .../replicaset/scripts-configmap.yaml | 1 - manifest/charts/mongodb/values.schema.json | 318 +-- .../common/templates/_compatibility.tpl | 2 +- .../charts/common/templates/_errors.tpl | 2 +- .../charts/common/templates/_images.tpl | 1 - .../charts/common/templates/_resources.tpl | 16 +- .../charts/common/templates/_utils.tpl | 4 +- .../rabbitmq/templates/config-secret.yaml | 2 +- .../rabbitmq/templates/serviceaccount.yaml | 1 - .../rabbitmq/templates/statefulset.yaml | 8 +- .../charts/rabbitmq/templates/validation.yaml | 1 - manifest/charts/rabbitmq/values.schema.json | 124 +- manifest/charts/vault/CONTRIBUTING.md | 2 +- manifest/charts/vault/README.md | 4 +- manifest/charts/vault/templates/NOTES.txt | 1 - .../templates/injector-certs-secret.yaml | 2 +- .../templates/injector-psp-rolebinding.yaml | 2 +- .../charts/vault/templates/injector-psp.yaml | 2 +- .../charts/vault/templates/injector-role.yaml | 2 +- .../vault/templates/injector-rolebinding.yaml | 2 +- .../templates/server-clusterrolebinding.yaml | 2 +- manifest/charts/vault/values.schema.json | 2228 ++++++++-------- 116 files changed, 2851 insertions(+), 2857 deletions(-) diff --git a/.codespell-ignore-words b/.codespell-ignore-words index 8a32e13..0205be2 100644 --- a/.codespell-ignore-words +++ b/.codespell-ignore-words @@ -1,6 +1,6 @@ ser hashi -Wheter +wheter aci ot boostrapping diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ca98d10..3b8d154 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -14,6 +14,7 @@ repos: - id: destroyed-symlinks - id: pretty-format-json args: ["--autofix"] + exclude: 'tsconfig.*\.json' ######### golang fmt and go tidey ######### - repo: https://github.com/TekWizely/pre-commit-golang diff --git a/CHANGELOG.md b/CHANGELOG.md index 6faea49..7b85565 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +1,2 @@ ## [v3.2.1](https://github.com/ClusterManager/ClusterManager/tree/v3.2.1) (2024-11-20) -[All Commits](https://github.com/ClusterManager/ClusterManager/compare/v3.2.0...v3.2.1) \ No newline at end of file +[All Commits](https://github.com/ClusterManager/ClusterManager/compare/v3.2.0...v3.2.1) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 5d28717..36382a6 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -67,7 +67,7 @@ Project maintainers may further clarify what constitutes project representation. ### **Reporting** -Instances of abusive, harassing, or otherwise unacceptable behavior can be reported to the project team at: +Instances of abusive, harassing, or otherwise unacceptable behavior can be reported to the project team at: **[community@01coud.com](mailto:community@01coud.com)** All reports will be: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cc49c53..8b862f3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,34 +10,34 @@ Explore these resources to make your contribution effective and aligned with the ### 📥 **Submitting Pull Requests** -Follow the step-by-step guide to submit pull requests: +Follow the step-by-step guide to submit pull requests: [**Submitting Pull Requests Guide**](docs/content/contributing/submitting-pull-requests.md) ### 🐛 **Submitting Issues** -Learn how to report bugs, propose features, or provide feedback: +Learn how to report bugs, propose features, or provide feedback: [**Submitting Issues Guide**](docs/content/contributing/submitting-issues.md) ### 🔒 **Submitting Security Issues** -Help us keep ClusterManager secure by reporting vulnerabilities responsibly: +Help us keep ClusterManager secure by reporting vulnerabilities responsibly: [**Submitting Security Issues Guide**](docs/content/contributing/submitting-security-issues.md) ### 📣 **Advocating for ClusterManager** -Discover how you can spread the word about ClusterManager: +Discover how you can spread the word about ClusterManager: [**Advocating for ClusterManager**](https://doc.example.com/ClusterManager/contributing/advocating) ### 🛠️ **Triage Process** -Understand how we manage issues and pull requests to keep the project running smoothly: +Understand how we manage issues and pull requests to keep the project running smoothly: [**Triage Process Guide**](https://doc.example.com/contributors-guide/blob/master/issue_triage.md) --- ## **Interested in Becoming a Maintainer?** -If you’re passionate about ClusterManager and want to take a more active role, check out the: +If you’re passionate about ClusterManager and want to take a more active role, check out the: [**Maintainers Guidelines**](docs/content/contributing/maintainers-guidelines.md) --- diff --git a/README.md b/README.md index 471066b..0bbefc5 100644 --- a/README.md +++ b/README.md @@ -222,13 +222,13 @@ Here is the script to setup the [clusterManager](docs/script/clusterManager_setu ## 👥 Maintainers -We embrace an open and inclusive community philosophy. Motivated contributors are encouraged to join the [maintainers' team](docs/content/contributing/maintainers.md). +We embrace an open and inclusive community philosophy. Motivated contributors are encouraged to join the [maintainers' team](docs/content/contributing/maintainers.md). Learn more about pull request reviews and issue triaging in our [contributor guide](docs/content/contributing/contributor-guidelines.md). --- -## 👥 Community +## 👥 Community -Join our vibrant [Discord community](https://discord.gg/g4a3P9af) to connect with contributors and maintainers. Engage in meaningful discussions, collaborate on ideas, and stay updated on the latest developments! +Join our vibrant [Discord community](https://discord.gg/g4a3P9af) to connect with contributors and maintainers. Engage in meaningful discussions, collaborate on ideas, and stay updated on the latest developments! --- @@ -236,7 +236,7 @@ Let me know if you'd like any further refinements! 😊 ## 🤝 Contributing -Interested in contributing? Refer to our [contributing documentation](CONTRIBUTING.md). +Interested in contributing? Refer to our [contributing documentation](CONTRIBUTING.md). This project adheres to a [Code of Conduct](CODE_OF_CONDUCT.md), and participation requires compliance with its terms. --- diff --git a/cluster-api/Dockerfile b/cluster-api/Dockerfile index 48d540c..6dadd45 100644 --- a/cluster-api/Dockerfile +++ b/cluster-api/Dockerfile @@ -19,4 +19,4 @@ COPY . /app COPY . . RUN pip install --no-cache-dir --upgrade -r requirements.txt # Install your app -CMD ["python", "-m", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8081"] \ No newline at end of file +CMD ["python", "-m", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8081"] diff --git a/cluster-api/env_example.bash b/cluster-api/env_example.bash index d5f6c8b..3c348ad 100644 --- a/cluster-api/env_example.bash +++ b/cluster-api/env_example.bash @@ -6,4 +6,4 @@ export KEYCLOAK_URL=https://sso.example_keycloak.dev/ export REALM_NAME=clusterManager export ADMIN_CLIENT_ID=admin-cli export REQUEST_GROUP_NAME=request-user -export CLIENT_ID=clustermanagerclient \ No newline at end of file +export CLIENT_ID=clustermanagerclient diff --git a/cluster-api/middleware/models/cluster.py b/cluster-api/middleware/models/cluster.py index 5b7deae..7830682 100644 --- a/cluster-api/middleware/models/cluster.py +++ b/cluster-api/middleware/models/cluster.py @@ -1,12 +1,14 @@ -from datetime import datetime import uuid +from datetime import datetime + from pydantic import BaseModel, Field + class Cluster(BaseModel): id: str = Field(default_factory=lambda: str(uuid.uuid4()), alias="_id") name: str = Field(...) userId: str = Field(alias="user_id") - status:str = Field(...) + status: str = Field(...) kube_version: str = Field(alias="kube_version") hostClusterId: str = Field(alias="host_cluster_id") subscriptionId: str = Field(alias="subscription_id") @@ -16,10 +18,10 @@ class Cluster(BaseModel): class Config: allow_population_by_field_name = True schema_extra = { - "example":{ + "example": { "name": "one", "user_id": "066de609-b04a-4b30-b46c-32537c7f1f6e", "host_cluster_id": "066de609-b04a-4b30-b46c-32537c7f1f6e", "subscription_id": "066de609-b04a-4b30-b46c-32537c7f1f6e", } - } + } diff --git a/cluster-api/middleware/models/common_response.py b/cluster-api/middleware/models/common_response.py index 4ef860d..664f9a5 100644 --- a/cluster-api/middleware/models/common_response.py +++ b/cluster-api/middleware/models/common_response.py @@ -1,8 +1,10 @@ from typing import Any, List, Optional, Union -from pydantic import BaseModel # type: ignore + +from pydantic import BaseModel # type: ignore + class ResponseModel(BaseModel): code: int error_code: int message: str - data: Union[List[Any], Any] = [] \ No newline at end of file + data: Union[List[Any], Any] = [] diff --git a/cluster-api/middleware/models/generate_kubeconfig.py b/cluster-api/middleware/models/generate_kubeconfig.py index 7ce7f32..8c6d10e 100644 --- a/cluster-api/middleware/models/generate_kubeconfig.py +++ b/cluster-api/middleware/models/generate_kubeconfig.py @@ -8,8 +8,8 @@ class GenerateKubeconfig(BaseModel): class Config: allow_population_by_field_name = True schema_extra = { - "example":{ + "example": { "expiryTime": "1h20m", - "clusterId": "066de609-b04a-4b30-b46c-32537c7f1f6e", + "clusterId": "066de609-b04a-4b30-b46c-32537c7f1f6e", } - } \ No newline at end of file + } diff --git a/cluster-api/middleware/models/host_cluster.py b/cluster-api/middleware/models/host_cluster.py index 22866e4..8519adb 100644 --- a/cluster-api/middleware/models/host_cluster.py +++ b/cluster-api/middleware/models/host_cluster.py @@ -1,11 +1,11 @@ import uuid from datetime import datetime -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field class HostCluster(BaseModel): - id: str = Field(default_factory=uuid.uuid4,alias="_id") + id: str = Field(default_factory=uuid.uuid4, alias="_id") name: str = Field(...) region: str = Field(...) provider: str = Field(...) @@ -19,12 +19,12 @@ class HostCluster(BaseModel): class Config: allow_population_by_field_name = True schema_extra = { - "example":{ + "example": { "name": "cluster 1", "region": "us-east-1", "provider": "aws", "nodes": 1, "active": False, - "version": "1.25" + "version": "1.25", } - } \ No newline at end of file + } diff --git a/cluster-api/middleware/models/kube_version.py b/cluster-api/middleware/models/kube_version.py index 81ebf2d..56a17cc 100644 --- a/cluster-api/middleware/models/kube_version.py +++ b/cluster-api/middleware/models/kube_version.py @@ -1,11 +1,13 @@ import uuid from datetime import datetime + from pydantic import BaseModel, Field + class KubeVersion(BaseModel): name: str kube_version: str - id: str = Field(default_factory=uuid.uuid4,alias="_id") + id: str = Field(default_factory=uuid.uuid4, alias="_id") active: bool class Config: diff --git a/cluster-api/middleware/models/subscription.py b/cluster-api/middleware/models/subscription.py index cddbed6..9bfc924 100644 --- a/cluster-api/middleware/models/subscription.py +++ b/cluster-api/middleware/models/subscription.py @@ -1,10 +1,11 @@ -from datetime import datetime import uuid +from datetime import datetime + from pydantic import BaseModel, Field class Subscription(BaseModel): - id: str = Field(default_factory=uuid.uuid4,alias="_id") + id: str = Field(default_factory=uuid.uuid4, alias="_id") name: str = Field(...) pods: int = Field(...) service: int = Field(...) @@ -39,15 +40,15 @@ def is_subscription_upgrade(self, new_plan): class Config: allow_population_by_field_name = True schema_extra = { - "example":{ + "example": { "name": "basic", - "pods":5, - "service":5, - "config_map":5, - "persistance_vol_claims":5, - "replication_ctl":5, - "secrets":5, - "loadbalancer":5, - "node_port":5 + "pods": 5, + "service": 5, + "config_map": 5, + "persistance_vol_claims": 5, + "replication_ctl": 5, + "secrets": 5, + "loadbalancer": 5, + "node_port": 5, } - } \ No newline at end of file + } diff --git a/cluster-api/middleware/models/user.py b/cluster-api/middleware/models/user.py index 85140d1..a048353 100644 --- a/cluster-api/middleware/models/user.py +++ b/cluster-api/middleware/models/user.py @@ -1,8 +1,10 @@ import uuid + from pydantic import BaseModel, Field # Id, name, username, email + class User(BaseModel): id: str = Field(default_factory=uuid.uuid4, alias="_id") name: str = Field(...) @@ -12,12 +14,14 @@ class User(BaseModel): class Config: allow_population_by_field_name = True schema_extra = { - "example":{ + "example": { "name": "user one", "email": "example@example.com", - "userName": "testUserName" + "userName": "testUserName", } } + + class UserLogin(BaseModel): userName: str = Field(...) password: str = Field(...) @@ -25,8 +29,5 @@ class UserLogin(BaseModel): class Config: allow_population_by_field_name = True schema_extra = { - "example":{ - "userName": "testUserName", - "password": "testPassword" - } - } \ No newline at end of file + "example": {"userName": "testUserName", "password": "testPassword"} + } diff --git a/cluster-api/requirements.txt b/cluster-api/requirements.txt index 25ae300..00608ae 100644 --- a/cluster-api/requirements.txt +++ b/cluster-api/requirements.txt @@ -6,4 +6,4 @@ pytest==7.0.1 dapr==1.13.0 python-keycloak==3.11.1 pyparsing==3.1.2 -python-jose>=3.4.0 \ No newline at end of file +python-jose>=3.4.0 diff --git a/cluster-service/env.example.sh b/cluster-service/env.example.sh index 3f6048d..714093d 100644 --- a/cluster-service/env.example.sh +++ b/cluster-service/env.example.sh @@ -1 +1 @@ -export HOST_NAME=cluster_host_kubernetes.com \ No newline at end of file +export HOST_NAME=cluster_host_kubernetes.com diff --git a/cluster-service/main.py b/cluster-service/main.py index 7aa36c5..94ce026 100644 --- a/cluster-service/main.py +++ b/cluster-service/main.py @@ -1,17 +1,22 @@ import logging + from flask import Flask from src.controller.routes import routes_bp + def create_app(): """Create and configure the Flask application.""" app = Flask(__name__) app.register_blueprint(routes_bp) return app + app = create_app() if __name__ == "__main__": - logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s") + logging.basicConfig( + level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s" + ) logging.info("Starting the Flask application...") # Run the app - app.run(port=8082, host="0.0.0.0") \ No newline at end of file + app.run(port=8082, host="0.0.0.0") diff --git a/cluster-service/requirements.txt b/cluster-service/requirements.txt index c3125c4..2011d25 100644 --- a/cluster-service/requirements.txt +++ b/cluster-service/requirements.txt @@ -3,4 +3,4 @@ kubernetes==27.2.0 dapr==1.14.0 cloudevents==1.11.0 typing-extensions==4.12.2 -gunicorn>=23.0.0 \ No newline at end of file +gunicorn>=23.0.0 diff --git a/cluster-status/.env_sample b/cluster-status/.env_sample index 70b085e..d3c4594 100644 --- a/cluster-status/.env_sample +++ b/cluster-status/.env_sample @@ -1,2 +1,2 @@ API_URL=http://localhost:8081 -LOCAL=true \ No newline at end of file +LOCAL=true diff --git a/cluster-status/Dockerfile b/cluster-status/Dockerfile index 75b3df7..735e327 100644 --- a/cluster-status/Dockerfile +++ b/cluster-status/Dockerfile @@ -9,4 +9,4 @@ RUN CGO_ENABLED=0 GOOS=linux go build -o main FROM alpine:3.18 WORKDIR /app COPY --from=builder /app/main . -CMD ["./main"] \ No newline at end of file +CMD ["./main"] diff --git a/cluster-status/README.md b/cluster-status/README.md index fdb5531..adda8c4 100644 --- a/cluster-status/README.md +++ b/cluster-status/README.md @@ -74,4 +74,4 @@ This guide provides a quick and detailed installation process for the **Cluster --- -Feel free to reach out if you have any questions or need further assistance! 😊 \ No newline at end of file +Feel free to reach out if you have any questions or need further assistance! 😊 diff --git a/cluster-status/deployment/apply.sh b/cluster-status/deployment/apply.sh index bee82e9..e5fe911 100644 --- a/cluster-status/deployment/apply.sh +++ b/cluster-status/deployment/apply.sh @@ -2,4 +2,4 @@ Kubectl apply -f ./deployment/service-account.yml Kubectl apply -f ./deployment/cluster-role.yml Kubectl apply -f ./deployment/cluster-role-binding.yml -Kubectl apply -f ./deployment/deployment.yml \ No newline at end of file +Kubectl apply -f ./deployment/deployment.yml diff --git a/cluster-status/deployment/cluster-role-binding.yml b/cluster-status/deployment/cluster-role-binding.yml index 1c386ed..17ca37c 100644 --- a/cluster-status/deployment/cluster-role-binding.yml +++ b/cluster-status/deployment/cluster-role-binding.yml @@ -12,4 +12,4 @@ roleRef: subjects: - kind: ServiceAccount name: default - namespace: status-controller \ No newline at end of file + namespace: status-controller diff --git a/cluster-status/deployment/destroy.sh b/cluster-status/deployment/destroy.sh index cadb060..db0e3b2 100644 --- a/cluster-status/deployment/destroy.sh +++ b/cluster-status/deployment/destroy.sh @@ -2,4 +2,4 @@ Kubectl delete -f ./deployment/service-account.yml Kubectl delete -f ./deployment/cluster-role.yml Kubectl delete -f ./deployment/cluster-role-binding.yml -Kubectl delete -f ./deployment/deployment.yml \ No newline at end of file +Kubectl delete -f ./deployment/deployment.yml diff --git a/cluster-status/deployment/service-account.yml b/cluster-status/deployment/service-account.yml index 95c57a2..3af179f 100644 --- a/cluster-status/deployment/service-account.yml +++ b/cluster-status/deployment/service-account.yml @@ -3,4 +3,4 @@ kind: ServiceAccount metadata: name: monitoring-service-account - namespace: status-controller \ No newline at end of file + namespace: status-controller diff --git a/cluster-status/go.mod b/cluster-status/go.mod index ebff3bf..3744aec 100644 --- a/cluster-status/go.mod +++ b/cluster-status/go.mod @@ -23,14 +23,12 @@ require ( github.com/google/go-cmp v0.5.9 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/uuid v1.3.0 // indirect - github.com/imdario/mergo v0.3.6 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/spf13/pflag v1.0.5 // indirect golang.org/x/net v0.13.0 // indirect golang.org/x/oauth2 v0.8.0 // indirect golang.org/x/sys v0.10.0 // indirect diff --git a/cluster-status/go.sum b/cluster-status/go.sum index dbe00a5..46c15fb 100644 --- a/cluster-status/go.sum +++ b/cluster-status/go.sum @@ -32,8 +32,6 @@ github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/ github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= -github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= @@ -63,7 +61,6 @@ github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjR github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= diff --git a/cluster-ui/.eslintrc.cjs b/cluster-ui/.eslintrc.cjs index dd05cba..d8e3e79 100644 --- a/cluster-ui/.eslintrc.cjs +++ b/cluster-ui/.eslintrc.cjs @@ -17,4 +17,4 @@ module.exports = { '@typescript-eslint/no-explicit-any': 'off', 'react-hooks/exhaustive-deps': 'off' }, -} \ No newline at end of file +} diff --git a/cluster-ui/.gitignore b/cluster-ui/.gitignore index afea972..241c5a4 100644 --- a/cluster-ui/.gitignore +++ b/cluster-ui/.gitignore @@ -134,4 +134,4 @@ dist public/config.js # Package-lock -package-lock.json \ No newline at end of file +package-lock.json diff --git a/cluster-ui/config_example.js b/cluster-ui/config_example.js index 2dc15bc..8a6ba91 100644 --- a/cluster-ui/config_example.js +++ b/cluster-ui/config_example.js @@ -9,5 +9,5 @@ window.config = { "01cloud":["https://console.example.io/loginsso/clustermanager","https://console.test.example.dev/loginsso/clustermanager","https://console.staging.example.dev/loginsso/clustermanager"] } - -} \ No newline at end of file + +} diff --git a/cluster-ui/package-lock.json b/cluster-ui/package-lock.json index c439607..67b58d9 100644 --- a/cluster-ui/package-lock.json +++ b/cluster-ui/package-lock.json @@ -1,39 +1,47 @@ { - "name": "cluster-ui", - "version": "0.0.0", - "lockfileVersion": 1, - "requires": true, "dependencies": { "@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", "dev": true, + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", "requires": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" - } + }, + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "version": "2.3.0" }, "@babel/code-frame": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", "requires": { "@babel/highlight": "^7.24.7", "picocolors": "^1.0.0" - } + }, + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "version": "7.24.7" }, "@babel/compat-data": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.4.tgz", + "dev": true, "integrity": "sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ==", - "dev": true + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.4.tgz", + "version": "7.25.4" }, "@babel/core": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz", - "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==", + "dependencies": { + "convert-source-map": { + "dev": true, + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "version": "2.0.0" + }, + "semver": { + "dev": true, + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "version": "6.3.1" + } + }, "dev": true, + "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==", "requires": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.24.7", @@ -51,37 +59,31 @@ "json5": "^2.2.3", "semver": "^6.3.1" }, - "dependencies": { - "convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true - }, - "semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true - } - } + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz", + "version": "7.25.2" }, "@babel/generator": { - "version": "7.25.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.6.tgz", "integrity": "sha512-VPC82gr1seXOpkjAAKoLhP50vx4vGNlF4msF64dSFq1P8RfB+QAuJWGHPXXPc8QyfVWwwB/TNNU4+ayZmHNbZw==", "requires": { "@babel/types": "^7.25.6", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" - } + }, + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.6.tgz", + "version": "7.25.6" }, "@babel/helper-compilation-targets": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz", - "integrity": "sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==", + "dependencies": { + "semver": { + "dev": true, + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "version": "6.3.1" + } + }, "dev": true, + "integrity": "sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==", "requires": { "@babel/compat-data": "^7.25.2", "@babel/helper-validator-option": "^7.24.8", @@ -89,136 +91,128 @@ "lru-cache": "^5.1.1", "semver": "^6.3.1" }, - "dependencies": { - "semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true - } - } + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz", + "version": "7.25.2" }, "@babel/helper-module-imports": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", "requires": { "@babel/traverse": "^7.24.7", "@babel/types": "^7.24.7" - } + }, + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", + "version": "7.24.7" }, "@babel/helper-module-transforms": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz", - "integrity": "sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==", "dev": true, + "integrity": "sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==", "requires": { "@babel/helper-module-imports": "^7.24.7", "@babel/helper-simple-access": "^7.24.7", "@babel/helper-validator-identifier": "^7.24.7", "@babel/traverse": "^7.25.2" - } + }, + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz", + "version": "7.25.2" }, "@babel/helper-plugin-utils": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz", + "dev": true, "integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==", - "dev": true + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz", + "version": "7.24.8" }, "@babel/helper-simple-access": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", - "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", "dev": true, + "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", "requires": { "@babel/traverse": "^7.24.7", "@babel/types": "^7.24.7" - } + }, + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", + "version": "7.24.7" }, "@babel/helper-string-parser": { - "version": "7.24.8", + "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", - "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==" + "version": "7.24.8" }, "@babel/helper-validator-identifier": { - "version": "7.24.7", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", - "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==" + "version": "7.24.7" }, "@babel/helper-validator-option": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz", + "dev": true, "integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==", - "dev": true + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz", + "version": "7.24.8" }, "@babel/helpers": { - "version": "7.25.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.6.tgz", - "integrity": "sha512-Xg0tn4HcfTijTwfDwYlvVCl43V6h4KyVVX2aEm4qdO/PC6L2YvzLHFdmxhoeSA3eslcE6+ZVXHgWwopXYLNq4Q==", "dev": true, + "integrity": "sha512-Xg0tn4HcfTijTwfDwYlvVCl43V6h4KyVVX2aEm4qdO/PC6L2YvzLHFdmxhoeSA3eslcE6+ZVXHgWwopXYLNq4Q==", "requires": { "@babel/template": "^7.25.0", "@babel/types": "^7.25.6" - } + }, + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.6.tgz", + "version": "7.25.6" }, "@babel/highlight": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", "requires": { "@babel/helper-validator-identifier": "^7.24.7", "chalk": "^2.4.2", "js-tokens": "^4.0.0", "picocolors": "^1.0.0" - } + }, + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "version": "7.24.7" }, "@babel/parser": { - "version": "7.25.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.6.tgz", "integrity": "sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==", "requires": { "@babel/types": "^7.25.6" - } + }, + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.6.tgz", + "version": "7.25.6" }, "@babel/plugin-transform-react-jsx-self": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.24.7.tgz", - "integrity": "sha512-fOPQYbGSgH0HUp4UJO4sMBFjY6DuWq+2i8rixyUMb3CdGixs/gccURvYOAhajBdKDoGajFr3mUq5rH3phtkGzw==", "dev": true, + "integrity": "sha512-fOPQYbGSgH0HUp4UJO4sMBFjY6DuWq+2i8rixyUMb3CdGixs/gccURvYOAhajBdKDoGajFr3mUq5rH3phtkGzw==", "requires": { "@babel/helper-plugin-utils": "^7.24.7" - } + }, + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.24.7.tgz", + "version": "7.24.7" }, "@babel/plugin-transform-react-jsx-source": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.24.7.tgz", - "integrity": "sha512-J2z+MWzZHVOemyLweMqngXrgGC42jQ//R0KdxqkIz/OrbVIIlhFI3WigZ5fO+nwFvBlncr4MGapd8vTyc7RPNQ==", "dev": true, + "integrity": "sha512-J2z+MWzZHVOemyLweMqngXrgGC42jQ//R0KdxqkIz/OrbVIIlhFI3WigZ5fO+nwFvBlncr4MGapd8vTyc7RPNQ==", "requires": { "@babel/helper-plugin-utils": "^7.24.7" - } + }, + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.24.7.tgz", + "version": "7.24.7" }, "@babel/runtime": { - "version": "7.25.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.6.tgz", "integrity": "sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ==", "requires": { "regenerator-runtime": "^0.14.0" - } + }, + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.6.tgz", + "version": "7.25.6" }, "@babel/template": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz", "integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==", "requires": { "@babel/code-frame": "^7.24.7", "@babel/parser": "^7.25.0", "@babel/types": "^7.25.0" - } + }, + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz", + "version": "7.25.0" }, "@babel/traverse": { - "version": "7.25.6", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.6.tgz", "integrity": "sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ==", "requires": { "@babel/code-frame": "^7.24.7", @@ -228,21 +222,21 @@ "@babel/types": "^7.25.6", "debug": "^4.3.1", "globals": "^11.1.0" - } + }, + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.6.tgz", + "version": "7.25.6" }, "@babel/types": { - "version": "7.25.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.6.tgz", "integrity": "sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==", "requires": { "@babel/helper-string-parser": "^7.24.8", "@babel/helper-validator-identifier": "^7.24.7", "to-fast-properties": "^2.0.0" - } + }, + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.6.tgz", + "version": "7.25.6" }, "@emotion/babel-plugin": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.12.0.tgz", "integrity": "sha512-y2WQb+oP8Jqvvclh8Q55gLUyb7UFvgv7eJfsj7td5TToBrIUtPay2kMrZi4xjq9qw2vD0ZR5fSho0yqoFgX7Rw==", "requires": { "@babel/helper-module-imports": "^7.16.7", @@ -256,11 +250,11 @@ "find-root": "^1.1.0", "source-map": "^0.5.7", "stylis": "4.2.0" - } + }, + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.12.0.tgz", + "version": "11.12.0" }, "@emotion/cache": { - "version": "11.13.1", - "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.13.1.tgz", "integrity": "sha512-iqouYkuEblRcXmylXIwwOodiEK5Ifl7JcX7o6V4jI3iW4mLXX3dmt5xwBtIkJiQEXFAI+pC8X0i67yiPkH9Ucw==", "requires": { "@emotion/memoize": "^0.9.0", @@ -268,29 +262,29 @@ "@emotion/utils": "^1.4.0", "@emotion/weak-memoize": "^0.4.0", "stylis": "4.2.0" - } + }, + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.13.1.tgz", + "version": "11.13.1" }, "@emotion/hash": { - "version": "0.9.2", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", - "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==" + "version": "0.9.2" }, "@emotion/is-prop-valid": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.3.0.tgz", "integrity": "sha512-SHetuSLvJDzuNbOdtPVbq6yMMMlLoW5Q94uDqJZqy50gcmAjxFkVqmzqSGEFq9gT2iMuIeKV1PXVWmvUhuZLlQ==", "requires": { "@emotion/memoize": "^0.9.0" - } + }, + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.3.0.tgz", + "version": "1.3.0" }, "@emotion/memoize": { - "version": "0.9.0", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", - "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==" + "version": "0.9.0" }, "@emotion/react": { - "version": "11.13.3", - "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.13.3.tgz", "integrity": "sha512-lIsdU6JNrmYfJ5EbUCf4xW1ovy5wKQ2CkPRM4xogziOxH1nXxBSjpC9YqbFAP7circxMfYp+6x676BqWcEiixg==", "requires": { "@babel/runtime": "^7.18.3", @@ -301,11 +295,11 @@ "@emotion/utils": "^1.4.0", "@emotion/weak-memoize": "^0.4.0", "hoist-non-react-statics": "^3.3.1" - } + }, + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.13.3.tgz", + "version": "11.13.3" }, "@emotion/serialize": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.1.tgz", "integrity": "sha512-dEPNKzBPU+vFPGa+z3axPRn8XVDetYORmDC0wAiej+TNcOZE70ZMJa0X7JdeoM6q/nWTMZeLpN/fTnD9o8MQBA==", "requires": { "@emotion/hash": "^0.9.2", @@ -313,16 +307,16 @@ "@emotion/unitless": "^0.10.0", "@emotion/utils": "^1.4.0", "csstype": "^3.0.2" - } + }, + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.1.tgz", + "version": "1.3.1" }, "@emotion/sheet": { - "version": "1.4.0", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==", "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", - "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==" + "version": "1.4.0" }, "@emotion/styled": { - "version": "11.13.0", - "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.13.0.tgz", "integrity": "sha512-tkzkY7nQhW/zC4hztlwucpT8QEZ6eUzpXDRhww/Eej4tFfO0FxQYWRyg/c5CCXa4d/f174kqeXYjuQRnhzf6dA==", "requires": { "@babel/runtime": "^7.18.3", @@ -331,202 +325,213 @@ "@emotion/serialize": "^1.3.0", "@emotion/use-insertion-effect-with-fallbacks": "^1.1.0", "@emotion/utils": "^1.4.0" - } + }, + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.13.0.tgz", + "version": "11.13.0" }, "@emotion/unitless": { - "version": "0.10.0", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", - "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==" + "version": "0.10.0" }, "@emotion/use-insertion-effect-with-fallbacks": { - "version": "1.1.0", + "integrity": "sha512-+wBOcIV5snwGgI2ya3u99D7/FJquOIniQT1IKyDsBmEgwvpxMNeS65Oib7OnE2d2aY+3BU4OiH+0Wchf8yk3Hw==", "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.1.0.tgz", - "integrity": "sha512-+wBOcIV5snwGgI2ya3u99D7/FJquOIniQT1IKyDsBmEgwvpxMNeS65Oib7OnE2d2aY+3BU4OiH+0Wchf8yk3Hw==" + "version": "1.1.0" }, "@emotion/utils": { - "version": "1.4.0", + "integrity": "sha512-spEnrA1b6hDR/C68lC2M7m6ALPUHZC0lIY7jAS/B/9DuuO1ZP04eov8SMv/6fwRd8pzmsn2AuJEznRREWlQrlQ==", "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.0.tgz", - "integrity": "sha512-spEnrA1b6hDR/C68lC2M7m6ALPUHZC0lIY7jAS/B/9DuuO1ZP04eov8SMv/6fwRd8pzmsn2AuJEznRREWlQrlQ==" + "version": "1.4.0" }, "@emotion/weak-memoize": { - "version": "0.4.0", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", - "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==" + "version": "0.4.0" }, "@esbuild/android-arm": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", - "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", "dev": true, - "optional": true + "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", + "optional": true, + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", + "version": "0.18.20" }, "@esbuild/android-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", - "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", "dev": true, - "optional": true + "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", + "optional": true, + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", + "version": "0.18.20" }, "@esbuild/android-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", - "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", "dev": true, - "optional": true + "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", + "optional": true, + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", + "version": "0.18.20" }, "@esbuild/darwin-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", - "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", "dev": true, - "optional": true + "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", + "optional": true, + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", + "version": "0.18.20" }, "@esbuild/darwin-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", - "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", "dev": true, - "optional": true + "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", + "optional": true, + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", + "version": "0.18.20" }, "@esbuild/freebsd-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", - "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", "dev": true, - "optional": true + "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", + "optional": true, + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", + "version": "0.18.20" }, "@esbuild/freebsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", - "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", "dev": true, - "optional": true + "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", + "optional": true, + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", + "version": "0.18.20" }, "@esbuild/linux-arm": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", - "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", "dev": true, - "optional": true + "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", + "optional": true, + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", + "version": "0.18.20" }, "@esbuild/linux-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", - "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", "dev": true, - "optional": true + "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", + "optional": true, + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", + "version": "0.18.20" }, "@esbuild/linux-ia32": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", - "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", "dev": true, - "optional": true + "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", + "optional": true, + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", + "version": "0.18.20" }, "@esbuild/linux-loong64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", - "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", "dev": true, - "optional": true + "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", + "optional": true, + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", + "version": "0.18.20" }, "@esbuild/linux-mips64el": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", - "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", "dev": true, - "optional": true + "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", + "optional": true, + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", + "version": "0.18.20" }, "@esbuild/linux-ppc64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", - "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", "dev": true, - "optional": true + "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", + "optional": true, + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", + "version": "0.18.20" }, "@esbuild/linux-riscv64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", - "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", "dev": true, - "optional": true + "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", + "optional": true, + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", + "version": "0.18.20" }, "@esbuild/linux-s390x": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", - "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", "dev": true, - "optional": true + "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", + "optional": true, + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", + "version": "0.18.20" }, "@esbuild/linux-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", - "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", "dev": true, - "optional": true + "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", + "optional": true, + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", + "version": "0.18.20" }, "@esbuild/netbsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", - "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", "dev": true, - "optional": true + "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", + "optional": true, + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", + "version": "0.18.20" }, "@esbuild/openbsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", - "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", "dev": true, - "optional": true + "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", + "optional": true, + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", + "version": "0.18.20" }, "@esbuild/sunos-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", - "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", "dev": true, - "optional": true + "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", + "optional": true, + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", + "version": "0.18.20" }, "@esbuild/win32-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", - "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", "dev": true, - "optional": true + "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", + "optional": true, + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", + "version": "0.18.20" }, "@esbuild/win32-ia32": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", - "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", "dev": true, - "optional": true + "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", + "optional": true, + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", + "version": "0.18.20" }, "@esbuild/win32-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", - "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", "dev": true, - "optional": true + "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", + "optional": true, + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", + "version": "0.18.20" }, "@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", "dev": true, + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", "requires": { "eslint-visitor-keys": "^3.3.0" - } + }, + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "version": "4.4.0" }, "@eslint-community/regexpp": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz", + "dev": true, "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==", - "dev": true + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz", + "version": "4.11.0" }, "@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dependencies": { + "globals": { + "dev": true, + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "requires": { + "type-fest": "^0.20.2" + }, + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "version": "13.24.0" + } + }, "dev": true, + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "requires": { "ajv": "^6.12.4", "debug": "^4.3.2", @@ -538,97 +543,93 @@ "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" }, - "dependencies": { - "globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - } - } - } + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "version": "2.1.4" }, "@eslint/js": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "dev": true, "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", - "dev": true + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "version": "8.57.0" }, "@humanwhocodes/config-array": { - "version": "0.11.14", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", - "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", "dev": true, + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", "requires": { "@humanwhocodes/object-schema": "^2.0.2", "debug": "^4.3.1", "minimatch": "^3.0.5" - } + }, + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "version": "0.11.14" }, "@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "dev": true, "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "version": "1.0.1" }, "@humanwhocodes/object-schema": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "dev": true, "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "dev": true + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "version": "2.0.3" }, "@jridgewell/gen-mapping": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", "requires": { "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", "@jridgewell/trace-mapping": "^0.3.24" - } + }, + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "version": "0.3.5" }, "@jridgewell/resolve-uri": { - "version": "3.1.2", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==" + "version": "3.1.2" }, "@jridgewell/set-array": { - "version": "1.2.1", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==" + "version": "1.2.1" }, "@jridgewell/sourcemap-codec": { - "version": "1.5.0", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==" + "version": "1.5.0" }, "@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "requires": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" - } + }, + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "version": "0.3.25" }, "@mui/core-downloads-tracker": { - "version": "5.16.7", + "integrity": "sha512-RtsCt4Geed2/v74sbihWzzRs+HsIQCfclHeORh5Ynu2fS4icIKozcSubwuG7vtzq2uW3fOR1zITSP84TNt2GoQ==", "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.16.7.tgz", - "integrity": "sha512-RtsCt4Geed2/v74sbihWzzRs+HsIQCfclHeORh5Ynu2fS4icIKozcSubwuG7vtzq2uW3fOR1zITSP84TNt2GoQ==" + "version": "5.16.7" }, "@mui/icons-material": { - "version": "5.16.7", - "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.16.7.tgz", "integrity": "sha512-UrGwDJCXEszbDI7yV047BYU5A28eGJ79keTCP4cc74WyncuVrnurlmIRxaHL8YK+LI1Kzq+/JM52IAkNnv4u+Q==", "requires": { "@babel/runtime": "^7.23.9" - } + }, + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.16.7.tgz", + "version": "5.16.7" }, "@mui/material": { - "version": "5.16.7", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.16.7.tgz", + "dependencies": { + "react-is": { + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "version": "18.3.1" + } + }, "integrity": "sha512-cwwVQxBhK60OIOqZOVLFt55t01zmarKJiJUWbk0+8s/Ix5IaUzAShqlJchxsIQ4mSrWqgcKCCXKtIlG5H+/Jmg==", "requires": { "@babel/runtime": "^7.23.9", @@ -644,38 +645,31 @@ "react-is": "^18.3.1", "react-transition-group": "^4.4.5" }, - "dependencies": { - "react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" - } - } + "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.16.7.tgz", + "version": "5.16.7" }, "@mui/private-theming": { - "version": "5.16.6", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.16.6.tgz", "integrity": "sha512-rAk+Rh8Clg7Cd7shZhyt2HGTTE5wYKNSJ5sspf28Fqm/PZ69Er9o6KX25g03/FG2dfpg5GCwZh/xOojiTfm3hw==", "requires": { "@babel/runtime": "^7.23.9", "@mui/utils": "^5.16.6", "prop-types": "^15.8.1" - } + }, + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.16.6.tgz", + "version": "5.16.6" }, "@mui/styled-engine": { - "version": "5.16.6", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.16.6.tgz", "integrity": "sha512-zaThmS67ZmtHSWToTiHslbI8jwrmITcN93LQaR2lKArbvS7Z3iLkwRoiikNWutx9MBs8Q6okKvbZq1RQYB3v7g==", "requires": { "@babel/runtime": "^7.23.9", "@emotion/cache": "^11.11.0", "csstype": "^3.1.3", "prop-types": "^15.8.1" - } + }, + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.16.6.tgz", + "version": "5.16.6" }, "@mui/styles": { - "version": "5.16.7", - "resolved": "https://registry.npmjs.org/@mui/styles/-/styles-5.16.7.tgz", "integrity": "sha512-FfXhHP/2MlqH+vLs2tIHMeCChmqSRgkOALVNLKkPrDsvtoq5J8OraOutCn1scpvRjr9mO8ZhW6jKx2t/vUDxtQ==", "requires": { "@babel/runtime": "^7.23.9", @@ -695,11 +689,11 @@ "jss-plugin-rule-value-function": "^10.10.0", "jss-plugin-vendor-prefixer": "^10.10.0", "prop-types": "^15.8.1" - } + }, + "resolved": "https://registry.npmjs.org/@mui/styles/-/styles-5.16.7.tgz", + "version": "5.16.7" }, "@mui/system": { - "version": "5.16.7", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.16.7.tgz", "integrity": "sha512-Jncvs/r/d/itkxh7O7opOunTqbbSSzMTHzZkNLM+FjAOg+cYAZHrPDlYe1ZGKUYORwwb2XexlWnpZp0kZ4AHuA==", "requires": { "@babel/runtime": "^7.23.9", @@ -710,16 +704,23 @@ "clsx": "^2.1.0", "csstype": "^3.1.3", "prop-types": "^15.8.1" - } + }, + "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.16.7.tgz", + "version": "5.16.7" }, "@mui/types": { - "version": "7.2.16", + "integrity": "sha512-qI8TV3M7ShITEEc8Ih15A2vLzZGLhD+/UPNwck/hcls2gwg7dyRjNGXcQYHKLB5Q7PuTRfrTkAoPa2VV1s67Ag==", "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.16.tgz", - "integrity": "sha512-qI8TV3M7ShITEEc8Ih15A2vLzZGLhD+/UPNwck/hcls2gwg7dyRjNGXcQYHKLB5Q7PuTRfrTkAoPa2VV1s67Ag==" + "version": "7.2.16" }, "@mui/utils": { - "version": "5.16.6", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.16.6.tgz", + "dependencies": { + "react-is": { + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "version": "18.3.1" + } + }, "integrity": "sha512-tWiQqlhxAt3KENNiSRL+DIn9H5xNVK6Jjf70x3PnfQPz1MPBdh7yyIcAyVBT9xiw7hP3SomRhPR7hzBMBCjqEA==", "requires": { "@babel/runtime": "^7.23.9", @@ -729,216 +730,209 @@ "prop-types": "^15.8.1", "react-is": "^18.3.1" }, - "dependencies": { - "react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" - } - } + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.16.6.tgz", + "version": "5.16.6" }, "@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "dev": true, + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "requires": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" - } + }, + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "version": "2.1.5" }, "@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "dev": true, "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "version": "2.0.5" }, "@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "dev": true, + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "requires": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" - } + }, + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "version": "1.2.8" }, "@popperjs/core": { - "version": "2.11.8", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", - "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==" + "version": "2.11.8" }, "@react-keycloak/core": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@react-keycloak/core/-/core-3.2.0.tgz", "integrity": "sha512-1yzU7gQzs+6E1v6hGqxy0Q+kpMHg9sEcke2yxZR29WoU8KNE8E50xS6UbI8N7rWsgyYw8r9W1cUPCOF48MYjzw==", "requires": { "react-fast-compare": "^3.2.0" - } + }, + "resolved": "https://registry.npmjs.org/@react-keycloak/core/-/core-3.2.0.tgz", + "version": "3.2.0" }, "@react-keycloak/web": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@react-keycloak/web/-/web-3.4.0.tgz", "integrity": "sha512-yKKSCyqBtn7dt+VckYOW1IM5NW999pPkxDZOXqJ6dfXPXstYhOQCkTZqh8l7UL14PkpsoaHDh7hSJH8whah01g==", "requires": { "@babel/runtime": "^7.9.0", "@react-keycloak/core": "^3.2.0", "hoist-non-react-statics": "^3.3.2" - } + }, + "resolved": "https://registry.npmjs.org/@react-keycloak/web/-/web-3.4.0.tgz", + "version": "3.4.0" }, "@tanstack/query-core": { - "version": "4.36.1", + "integrity": "sha512-DJSilV5+ytBP1FbFcEJovv4rnnm/CokuVvrBEtW/Va9DvuJ3HksbXUJEpI0aV1KtuL4ZoO9AVE6PyNLzF7tLeA==", "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.36.1.tgz", - "integrity": "sha512-DJSilV5+ytBP1FbFcEJovv4rnnm/CokuVvrBEtW/Va9DvuJ3HksbXUJEpI0aV1KtuL4ZoO9AVE6PyNLzF7tLeA==" + "version": "4.36.1" }, "@tanstack/react-query": { - "version": "4.36.1", - "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-4.36.1.tgz", "integrity": "sha512-y7ySVHFyyQblPl3J3eQBWpXZkliroki3ARnBKsdJchlgt7yJLRDUcf4B8soufgiYt3pEQIkBWBx1N9/ZPIeUWw==", "requires": { "@tanstack/query-core": "4.36.1", "use-sync-external-store": "^1.2.0" - } + }, + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-4.36.1.tgz", + "version": "4.36.1" }, "@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", "dev": true, + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", "requires": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" - } + }, + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "version": "7.20.5" }, "@types/babel__generator": { - "version": "7.6.8", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", - "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", "dev": true, + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", "requires": { "@babel/types": "^7.0.0" - } + }, + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "version": "7.6.8" }, "@types/babel__template": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", "dev": true, + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", "requires": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" - } + }, + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "version": "7.4.4" }, "@types/babel__traverse": { - "version": "7.20.6", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", - "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", "dev": true, + "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", "requires": { "@babel/types": "^7.20.7" - } + }, + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", + "version": "7.20.6" }, "@types/history": { - "version": "4.7.11", - "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz", + "dev": true, "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==", - "dev": true + "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz", + "version": "4.7.11" }, "@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "dev": true, "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "version": "7.0.15" }, "@types/node": { - "version": "22.14.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.14.0.tgz", - "integrity": "sha512-Kmpl+z84ILoG+3T/zQFyAJsU6EPTmOCj8/2+83fSN6djd6I4o7uOuGIH6vq3PrjY5BGitSbFuMN18j3iknubbA==", "dev": true, + "integrity": "sha512-Kmpl+z84ILoG+3T/zQFyAJsU6EPTmOCj8/2+83fSN6djd6I4o7uOuGIH6vq3PrjY5BGitSbFuMN18j3iknubbA==", "requires": { "undici-types": "~6.21.0" - } + }, + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.14.0.tgz", + "version": "22.14.0" }, "@types/parse-json": { - "version": "4.0.2", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", - "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==" + "version": "4.0.2" }, "@types/prop-types": { - "version": "15.7.12", + "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", - "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==" + "version": "15.7.12" }, "@types/react": { - "version": "18.3.5", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.5.tgz", "integrity": "sha512-WeqMfGJLGuLCqHGYRGHxnKrXcTitc6L/nBUWfWPcTarG3t9PsquqUMuVeXZeca+mglY4Vo5GZjCi0A3Or2lnxA==", "requires": { "@types/prop-types": "*", "csstype": "^3.0.2" - } + }, + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.5.tgz", + "version": "18.3.5" }, "@types/react-dom": { - "version": "18.3.0", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz", - "integrity": "sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==", "dev": true, + "integrity": "sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==", "requires": { "@types/react": "*" - } + }, + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz", + "version": "18.3.0" }, "@types/react-router": { - "version": "5.1.20", - "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.20.tgz", - "integrity": "sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==", "dev": true, + "integrity": "sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==", "requires": { "@types/history": "^4.7.11", "@types/react": "*" - } + }, + "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.20.tgz", + "version": "5.1.20" }, "@types/react-router-dom": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz", - "integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==", "dev": true, + "integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==", "requires": { "@types/history": "^4.7.11", "@types/react": "*", "@types/react-router": "*" - } + }, + "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz", + "version": "5.3.3" }, "@types/react-transition-group": { - "version": "4.4.11", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.11.tgz", "integrity": "sha512-RM05tAniPZ5DZPzzNFP+DmrcOdD0efDUxMy3145oljWSl3x9ZV5vhme98gTxFrj2lhXvmGNnUiuDyJgY9IKkNA==", "requires": { "@types/react": "*" - } + }, + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.11.tgz", + "version": "4.4.11" }, "@types/semver": { - "version": "7.5.8", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", + "dev": true, "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", - "dev": true + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", + "version": "7.5.8" }, "@types/websocket": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/@types/websocket/-/websocket-1.0.10.tgz", - "integrity": "sha512-svjGZvPB7EzuYS94cI7a+qhwgGU1y89wUgjT6E2wVUfmAGIvRfT7obBvRtnhXCSsoMdlG4gBFGE7MfkIXZLoww==", "dev": true, + "integrity": "sha512-svjGZvPB7EzuYS94cI7a+qhwgGU1y89wUgjT6E2wVUfmAGIvRfT7obBvRtnhXCSsoMdlG4gBFGE7MfkIXZLoww==", "requires": { "@types/node": "*" - } + }, + "resolved": "https://registry.npmjs.org/@types/websocket/-/websocket-1.0.10.tgz", + "version": "1.0.10" }, "@typescript-eslint/eslint-plugin": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", - "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", "dev": true, + "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", "requires": { "@eslint-community/regexpp": "^4.4.0", "@typescript-eslint/scope-manager": "5.62.0", @@ -950,53 +944,53 @@ "natural-compare-lite": "^1.4.0", "semver": "^7.3.7", "tsutils": "^3.21.0" - } + }, + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", + "version": "5.62.0" }, "@typescript-eslint/parser": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", - "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", "dev": true, + "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", "requires": { "@typescript-eslint/scope-manager": "5.62.0", "@typescript-eslint/types": "5.62.0", "@typescript-eslint/typescript-estree": "5.62.0", "debug": "^4.3.4" - } + }, + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", + "version": "5.62.0" }, "@typescript-eslint/scope-manager": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", - "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", "dev": true, + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", "requires": { "@typescript-eslint/types": "5.62.0", "@typescript-eslint/visitor-keys": "5.62.0" - } + }, + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "version": "5.62.0" }, "@typescript-eslint/type-utils": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", - "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", "dev": true, + "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", "requires": { "@typescript-eslint/typescript-estree": "5.62.0", "@typescript-eslint/utils": "5.62.0", "debug": "^4.3.4", "tsutils": "^3.21.0" - } + }, + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", + "version": "5.62.0" }, "@typescript-eslint/types": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "dev": true, "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", - "dev": true + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "version": "5.62.0" }, "@typescript-eslint/typescript-estree": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", - "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", "dev": true, + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", "requires": { "@typescript-eslint/types": "5.62.0", "@typescript-eslint/visitor-keys": "5.62.0", @@ -1005,13 +999,13 @@ "is-glob": "^4.0.3", "semver": "^7.3.7", "tsutils": "^3.21.0" - } + }, + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "version": "5.62.0" }, "@typescript-eslint/utils": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", - "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", "dev": true, + "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", "requires": { "@eslint-community/eslint-utils": "^4.2.0", "@types/json-schema": "^7.0.9", @@ -1021,230 +1015,230 @@ "@typescript-eslint/typescript-estree": "5.62.0", "eslint-scope": "^5.1.1", "semver": "^7.3.7" - } + }, + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", + "version": "5.62.0" }, "@typescript-eslint/visitor-keys": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", - "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", "dev": true, + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", "requires": { "@typescript-eslint/types": "5.62.0", "eslint-visitor-keys": "^3.3.0" - } + }, + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "version": "5.62.0" }, "@ungap/structured-clone": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "dev": true, "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", - "dev": true + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "version": "1.2.0" }, "@vitejs/plugin-react": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.1.tgz", - "integrity": "sha512-m/V2syj5CuVnaxcUJOQRel/Wr31FFXRFlnOoq1TVtkCxsY5veGMTEmpWHndrhB2U8ScHtCQB1e+4hWYExQc6Lg==", "dev": true, + "integrity": "sha512-m/V2syj5CuVnaxcUJOQRel/Wr31FFXRFlnOoq1TVtkCxsY5veGMTEmpWHndrhB2U8ScHtCQB1e+4hWYExQc6Lg==", "requires": { "@babel/core": "^7.24.5", "@babel/plugin-transform-react-jsx-self": "^7.24.5", "@babel/plugin-transform-react-jsx-source": "^7.24.1", "@types/babel__core": "^7.20.5", "react-refresh": "^0.14.2" - } + }, + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.1.tgz", + "version": "4.3.1" }, "acorn": { - "version": "8.12.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "dev": true, "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", - "dev": true + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "version": "8.12.1" }, "acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "dev": true, "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "version": "5.3.2" }, "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "requires": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" - } + }, + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "version": "6.12.6" }, "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "dev": true, "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "version": "5.0.1" }, "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "requires": { "color-convert": "^1.9.0" - } + }, + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "version": "3.2.1" }, "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "dev": true, "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "version": "2.0.1" }, "array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "dev": true, "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "version": "2.1.0" }, "asynckit": { - "version": "0.4.0", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + "version": "0.4.0" }, "axios": { - "version": "1.7.7", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", "requires": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" - } + }, + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "version": "1.7.7" }, "babel-plugin-macros": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", "requires": { "@babel/runtime": "^7.12.5", "cosmiconfig": "^7.0.0", "resolve": "^1.19.0" - } + }, + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "version": "3.1.0" }, "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "dev": true, "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "version": "1.0.2" }, "base64-js": { - "version": "1.5.1", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" + "version": "1.5.1" }, "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" - } + }, + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "version": "1.1.11" }, "braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "requires": { "fill-range": "^7.1.1" - } + }, + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "version": "3.0.3" }, "browserslist": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz", - "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==", "dev": true, + "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==", "requires": { "caniuse-lite": "^1.0.30001646", "electron-to-chromium": "^1.5.4", "node-releases": "^2.0.18", "update-browserslist-db": "^1.1.0" - } + }, + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz", + "version": "4.23.3" }, "bufferutil": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.9.tgz", "integrity": "sha512-WDtdLmJvAuNNPzByAYpRo2rF1Mmradw6gvWsQKf63476DDXmomT9zUiGypLcG4ibIM67vhAj8jJRdbmEws2Aqw==", "requires": { "node-gyp-build": "^4.3.0" - } + }, + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.9.tgz", + "version": "4.0.9" }, "callsites": { - "version": "3.1.0", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" + "version": "3.1.0" }, "caniuse-lite": { - "version": "1.0.30001660", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001660.tgz", + "dev": true, "integrity": "sha512-GacvNTTuATm26qC74pt+ad1fW15mlQ/zuTzzY1ZoIzECTP8HURDfF43kNxPgf7H1jmelCBQTTbBNxdSXOA7Bqg==", - "dev": true + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001660.tgz", + "version": "1.0.30001660" }, "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "dependencies": { + "escape-string-regexp": { + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "version": "1.0.5" + } + }, "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "requires": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" }, - "dependencies": { - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" - } - } + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "version": "2.4.2" }, "clsx": { - "version": "2.1.1", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", - "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==" + "version": "2.1.1" }, "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "requires": { "color-name": "1.1.3" - } + }, + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "version": "1.9.3" }, "color-name": { - "version": "1.1.3", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + "version": "1.1.3" }, "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "requires": { "delayed-stream": "~1.0.0" - } + }, + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "version": "1.0.8" }, "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "dev": true, "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "version": "0.0.1" }, "convert-source-map": { - "version": "1.9.0", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + "version": "1.9.0" }, "cosmiconfig": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", "requires": { "@types/parse-json": "^4.0.0", @@ -1252,137 +1246,137 @@ "parse-json": "^5.0.0", "path-type": "^4.0.0", "yaml": "^1.10.0" - } + }, + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "version": "7.1.0" }, "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "dev": true, + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "requires": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" - } + }, + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "version": "7.0.3" }, "css-vendor": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-2.0.8.tgz", "integrity": "sha512-x9Aq0XTInxrkuFeHKbYC7zWY8ai7qJ04Kxd9MnvbC1uO5DagxoHQjm4JvG+vCdXOoFtCjbL2XSZfxmoYa9uQVQ==", "requires": { "@babel/runtime": "^7.8.3", "is-in-browser": "^1.0.2" - } + }, + "resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-2.0.8.tgz", + "version": "2.0.8" }, "csstype": { - "version": "3.1.3", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + "version": "3.1.3" }, "d": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz", "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==", "requires": { "es5-ext": "^0.10.64", "type": "^2.7.2" - } + }, + "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz", + "version": "1.0.2" }, "debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "requires": { "ms": "^2.1.3" - } + }, + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "version": "4.3.7" }, "deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "dev": true, "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "version": "0.1.4" }, "delayed-stream": { - "version": "1.0.0", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" + "version": "1.0.0" }, "dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", "dev": true, + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", "requires": { "path-type": "^4.0.0" - } + }, + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "version": "3.0.1" }, "doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "dev": true, + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "requires": { "esutils": "^2.0.2" - } + }, + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "version": "3.0.0" }, "dom-helpers": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", "requires": { "@babel/runtime": "^7.8.7", "csstype": "^3.0.2" - } + }, + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "version": "5.2.1" }, "electron-to-chromium": { - "version": "1.5.19", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.19.tgz", + "dev": true, "integrity": "sha512-kpLJJi3zxTR1U828P+LIUDZ5ohixyo68/IcYOHLqnbTPr/wdgn4i1ECvmALN9E16JPA6cvCG5UG79gVwVdEK5w==", - "dev": true + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.19.tgz", + "version": "1.5.19" }, "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", "requires": { "is-arrayish": "^0.2.1" - } + }, + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "version": "1.3.2" }, "es5-ext": { - "version": "0.10.64", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", "requires": { "es6-iterator": "^2.0.3", "es6-symbol": "^3.1.3", "esniff": "^2.0.1", "next-tick": "^1.1.0" - } + }, + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", + "version": "0.10.64" }, "es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", "requires": { "d": "1", "es5-ext": "^0.10.35", "es6-symbol": "^3.1.1" - } + }, + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "version": "2.0.3" }, "es6-symbol": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.4.tgz", "integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==", "requires": { "d": "^1.0.2", "ext": "^1.7.0" - } + }, + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.4.tgz", + "version": "3.1.4" }, "esbuild": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", - "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", "dev": true, + "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", "requires": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", @@ -1406,397 +1400,397 @@ "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" - } + }, + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", + "version": "0.18.20" }, "escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "dev": true, "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "version": "3.2.0" }, "escape-string-regexp": { - "version": "4.0.0", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" + "version": "4.0.0" }, "eslint": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", - "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", - "dev": true, - "requires": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.0", - "@humanwhocodes/config-array": "^0.11.14", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" - }, "dependencies": { "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "requires": { "color-convert": "^2.0.1" - } + }, + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "version": "4.3.0" }, "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" - } + }, + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "version": "4.1.2" }, "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "requires": { "color-name": "~1.1.4" - } + }, + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "version": "2.0.1" }, "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "dev": true, "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "version": "1.1.4" }, "eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "requires": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" - } + }, + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "version": "7.2.2" }, "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "dev": true, "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "version": "5.3.0" }, "glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "requires": { "is-glob": "^4.0.3" - } + }, + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "version": "6.0.2" }, "globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "requires": { "type-fest": "^0.20.2" - } + }, + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "version": "13.24.0" }, "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "dev": true, "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "version": "4.0.0" }, "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "requires": { "has-flag": "^4.0.0" - } + }, + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "version": "7.2.0" } - } + }, + "dev": true, + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "requires": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "version": "8.57.0" }, "eslint-plugin-react-hooks": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz", + "dev": true, "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==", - "dev": true + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz", + "version": "4.6.2" }, "eslint-plugin-react-refresh": { - "version": "0.4.11", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.11.tgz", + "dev": true, "integrity": "sha512-wrAKxMbVr8qhXTtIKfXqAn5SAtRZt0aXxe5P23Fh4pUAdC6XEsybGLB8P0PI4j1yYqOgUEUlzKAGDfo7rJOjcw==", - "dev": true + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.11.tgz", + "version": "0.4.11" }, "eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "requires": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" - } + }, + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "version": "5.1.1" }, "eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "dev": true, "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "version": "3.4.3" }, "esniff": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", "requires": { "d": "^1.0.1", "es5-ext": "^0.10.62", "event-emitter": "^0.3.5", "type": "^2.7.2" - } + }, + "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", + "version": "2.0.1" }, "espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "requires": { "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^3.4.1" - } + }, + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "version": "9.6.1" }, "esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dependencies": { + "estraverse": { + "dev": true, + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "version": "5.3.0" + } + }, "dev": true, + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", "requires": { "estraverse": "^5.1.0" }, + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "version": "1.6.0" + }, + "esrecurse": { "dependencies": { "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "dev": true, "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "version": "5.3.0" } - } - }, - "esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + }, "dev": true, + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "requires": { "estraverse": "^5.2.0" }, - "dependencies": { - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - } - } + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "version": "4.3.0" }, "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "dev": true, "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "version": "4.3.0" }, "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "dev": true, "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "version": "2.0.3" }, "event-emitter": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", "requires": { "d": "1", "es5-ext": "~0.10.14" - } + }, + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "version": "0.3.5" }, "ext": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", "requires": { "type": "^2.7.2" - } + }, + "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", + "version": "1.7.0" }, "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "dev": true, "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "version": "3.1.3" }, "fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", "dev": true, + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", "requires": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.4" - } + }, + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "version": "3.3.2" }, "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "dev": true, "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "version": "2.1.0" }, "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "dev": true, "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "version": "2.0.6" }, "fastq": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", - "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", "dev": true, + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", "requires": { "reusify": "^1.0.4" - } + }, + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "version": "1.17.1" }, "file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", "dev": true, + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", "requires": { "flat-cache": "^3.0.4" - } + }, + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "version": "6.0.1" }, "fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "requires": { "to-regex-range": "^5.0.1" - } + }, + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "version": "7.1.1" }, "find-root": { - "version": "1.1.0", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", - "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" + "version": "1.1.0" }, "find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "requires": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" - } + }, + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "version": "5.0.0" }, "flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", "dev": true, + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", "requires": { "flatted": "^3.2.9", "keyv": "^4.5.3", "rimraf": "^3.0.2" - } + }, + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "version": "3.2.0" }, "flatted": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "dev": true, "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", - "dev": true + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "version": "3.3.1" }, "follow-redirects": { - "version": "1.15.9", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", - "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==" + "version": "1.15.9" }, "form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", "requires": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "mime-types": "^2.1.12" - } + }, + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "version": "4.0.0" }, "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "dev": true, "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "version": "1.0.0" }, "fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, - "optional": true + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "optional": true, + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "version": "2.3.3" }, "function-bind": { - "version": "1.1.2", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" + "version": "1.1.2" }, "gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "dev": true, "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "version": "1.0.0-beta.2" }, "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "dev": true, + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -1804,27 +1798,27 @@ "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" - } + }, + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "version": "7.2.3" }, "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "requires": { "is-glob": "^4.0.1" - } + }, + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "version": "5.1.2" }, "globals": { - "version": "11.12.0", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" + "version": "11.12.0" }, "globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", "dev": true, + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", "requires": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", @@ -1832,35 +1826,35 @@ "ignore": "^5.2.0", "merge2": "^1.4.1", "slash": "^3.0.0" - } + }, + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "version": "11.1.0" }, "goober": { - "version": "2.1.14", + "integrity": "sha512-4UpC0NdGyAFqLNPnhCT2iHpza2q+RAY3GV85a/mRPdzyPQMsj0KmMMuetdIkzWRbJ+Hgau1EZztq8ImmiMGhsg==", "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.14.tgz", - "integrity": "sha512-4UpC0NdGyAFqLNPnhCT2iHpza2q+RAY3GV85a/mRPdzyPQMsj0KmMMuetdIkzWRbJ+Hgau1EZztq8ImmiMGhsg==" + "version": "2.1.14" }, "graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "dev": true, "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "version": "1.4.0" }, "has-flag": { - "version": "3.0.0", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" + "version": "3.0.0" }, "hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "requires": { "function-bind": "^1.1.2" - } + }, + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "version": "2.0.2" }, "history": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", "requires": { "@babel/runtime": "^7.1.2", @@ -1869,427 +1863,427 @@ "tiny-invariant": "^1.0.2", "tiny-warning": "^1.0.0", "value-equal": "^1.0.1" - } + }, + "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", + "version": "4.10.1" }, "hoist-non-react-statics": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", "requires": { "react-is": "^16.7.0" - } + }, + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "version": "3.3.2" }, "hyphenate-style-name": { - "version": "1.1.0", + "integrity": "sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==", "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.1.0.tgz", - "integrity": "sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==" + "version": "1.1.0" }, "ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "dev": true, "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "version": "5.3.2" }, "import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", "requires": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" - } + }, + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "version": "3.3.0" }, "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "dev": true, "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "version": "0.1.4" }, "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "dev": true, + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "requires": { "once": "^1.3.0", "wrappy": "1" - } + }, + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "version": "1.0.6" }, "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "dev": true, "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "version": "2.0.4" }, "is-arrayish": { - "version": "0.2.1", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + "version": "0.2.1" }, "is-core-module": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", "requires": { "hasown": "^2.0.2" - } + }, + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", + "version": "2.15.1" }, "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "dev": true, "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "version": "2.1.1" }, "is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "requires": { "is-extglob": "^2.1.1" - } + }, + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "version": "4.0.3" }, "is-in-browser": { - "version": "1.1.3", + "integrity": "sha512-FeXIBgG/CPGd/WUxuEyvgGTEfwiG9Z4EKGxjNMRqviiIIfsmgrpnHLffEDdwUHqNva1VEW91o3xBT/m8Elgl9g==", "resolved": "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz", - "integrity": "sha512-FeXIBgG/CPGd/WUxuEyvgGTEfwiG9Z4EKGxjNMRqviiIIfsmgrpnHLffEDdwUHqNva1VEW91o3xBT/m8Elgl9g==" + "version": "1.1.3" }, "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "dev": true, "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "version": "7.0.0" }, "is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "dev": true, "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "version": "3.0.3" }, "is-typedarray": { - "version": "1.0.0", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" + "version": "1.0.0" }, "isarray": { - "version": "0.0.1", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" + "version": "0.0.1" }, "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "dev": true, "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "version": "2.0.0" }, "js-sha256": { - "version": "0.9.0", + "integrity": "sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA==", "resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.9.0.tgz", - "integrity": "sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA==" + "version": "0.9.0" }, "js-tokens": { - "version": "4.0.0", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + "version": "4.0.0" }, "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "requires": { "argparse": "^2.0.1" - } + }, + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "version": "4.1.0" }, "jsesc": { - "version": "2.5.2", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" + "version": "2.5.2" }, "json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "dev": true, "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "version": "3.0.1" }, "json-parse-even-better-errors": { - "version": "2.3.1", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + "version": "2.3.1" }, "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "dev": true, "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "version": "0.4.1" }, "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "dev": true, "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "version": "1.0.1" }, "json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "dev": true, "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "version": "2.2.3" }, "jss": { - "version": "10.10.0", - "resolved": "https://registry.npmjs.org/jss/-/jss-10.10.0.tgz", "integrity": "sha512-cqsOTS7jqPsPMjtKYDUpdFC0AbhYFLTcuGRqymgmdJIeQ8cH7+AgX7YSgQy79wXloZq2VvATYxUOUQEvS1V/Zw==", "requires": { "@babel/runtime": "^7.3.1", "csstype": "^3.0.2", "is-in-browser": "^1.1.3", "tiny-warning": "^1.0.2" - } + }, + "resolved": "https://registry.npmjs.org/jss/-/jss-10.10.0.tgz", + "version": "10.10.0" }, "jss-plugin-camel-case": { - "version": "10.10.0", - "resolved": "https://registry.npmjs.org/jss-plugin-camel-case/-/jss-plugin-camel-case-10.10.0.tgz", "integrity": "sha512-z+HETfj5IYgFxh1wJnUAU8jByI48ED+v0fuTuhKrPR+pRBYS2EDwbusU8aFOpCdYhtRc9zhN+PJ7iNE8pAWyPw==", "requires": { "@babel/runtime": "^7.3.1", "hyphenate-style-name": "^1.0.3", "jss": "10.10.0" - } + }, + "resolved": "https://registry.npmjs.org/jss-plugin-camel-case/-/jss-plugin-camel-case-10.10.0.tgz", + "version": "10.10.0" }, "jss-plugin-default-unit": { - "version": "10.10.0", - "resolved": "https://registry.npmjs.org/jss-plugin-default-unit/-/jss-plugin-default-unit-10.10.0.tgz", "integrity": "sha512-SvpajxIECi4JDUbGLefvNckmI+c2VWmP43qnEy/0eiwzRUsafg5DVSIWSzZe4d2vFX1u9nRDP46WCFV/PXVBGQ==", "requires": { "@babel/runtime": "^7.3.1", "jss": "10.10.0" - } + }, + "resolved": "https://registry.npmjs.org/jss-plugin-default-unit/-/jss-plugin-default-unit-10.10.0.tgz", + "version": "10.10.0" }, "jss-plugin-global": { - "version": "10.10.0", - "resolved": "https://registry.npmjs.org/jss-plugin-global/-/jss-plugin-global-10.10.0.tgz", "integrity": "sha512-icXEYbMufiNuWfuazLeN+BNJO16Ge88OcXU5ZDC2vLqElmMybA31Wi7lZ3lf+vgufRocvPj8443irhYRgWxP+A==", "requires": { "@babel/runtime": "^7.3.1", "jss": "10.10.0" - } + }, + "resolved": "https://registry.npmjs.org/jss-plugin-global/-/jss-plugin-global-10.10.0.tgz", + "version": "10.10.0" }, "jss-plugin-nested": { - "version": "10.10.0", - "resolved": "https://registry.npmjs.org/jss-plugin-nested/-/jss-plugin-nested-10.10.0.tgz", "integrity": "sha512-9R4JHxxGgiZhurDo3q7LdIiDEgtA1bTGzAbhSPyIOWb7ZubrjQe8acwhEQ6OEKydzpl8XHMtTnEwHXCARLYqYA==", "requires": { "@babel/runtime": "^7.3.1", "jss": "10.10.0", "tiny-warning": "^1.0.2" - } + }, + "resolved": "https://registry.npmjs.org/jss-plugin-nested/-/jss-plugin-nested-10.10.0.tgz", + "version": "10.10.0" }, "jss-plugin-props-sort": { - "version": "10.10.0", - "resolved": "https://registry.npmjs.org/jss-plugin-props-sort/-/jss-plugin-props-sort-10.10.0.tgz", "integrity": "sha512-5VNJvQJbnq/vRfje6uZLe/FyaOpzP/IH1LP+0fr88QamVrGJa0hpRRyAa0ea4U/3LcorJfBFVyC4yN2QC73lJg==", "requires": { "@babel/runtime": "^7.3.1", "jss": "10.10.0" - } + }, + "resolved": "https://registry.npmjs.org/jss-plugin-props-sort/-/jss-plugin-props-sort-10.10.0.tgz", + "version": "10.10.0" }, "jss-plugin-rule-value-function": { - "version": "10.10.0", - "resolved": "https://registry.npmjs.org/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.10.0.tgz", "integrity": "sha512-uEFJFgaCtkXeIPgki8ICw3Y7VMkL9GEan6SqmT9tqpwM+/t+hxfMUdU4wQ0MtOiMNWhwnckBV0IebrKcZM9C0g==", "requires": { "@babel/runtime": "^7.3.1", "jss": "10.10.0", "tiny-warning": "^1.0.2" - } + }, + "resolved": "https://registry.npmjs.org/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.10.0.tgz", + "version": "10.10.0" }, "jss-plugin-vendor-prefixer": { - "version": "10.10.0", - "resolved": "https://registry.npmjs.org/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.10.0.tgz", "integrity": "sha512-UY/41WumgjW8r1qMCO8l1ARg7NHnfRVWRhZ2E2m0DMYsr2DD91qIXLyNhiX83hHswR7Wm4D+oDYNC1zWCJWtqg==", "requires": { "@babel/runtime": "^7.3.1", "css-vendor": "^2.0.8", "jss": "10.10.0" - } + }, + "resolved": "https://registry.npmjs.org/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.10.0.tgz", + "version": "10.10.0" }, "keycloak-js": { - "version": "22.0.5", - "resolved": "https://registry.npmjs.org/keycloak-js/-/keycloak-js-22.0.5.tgz", "integrity": "sha512-a7ZwCZeHl8tpeJBy102tZtAnHslDUOA1Nf/sHNF3HYLchKpwoDuaitwIUiS2GnNUe+tlNKLlCqZS+Mi5K79m1w==", "requires": { "base64-js": "^1.5.1", "js-sha256": "^0.9.0" - } + }, + "resolved": "https://registry.npmjs.org/keycloak-js/-/keycloak-js-22.0.5.tgz", + "version": "22.0.5" }, "keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "requires": { "json-buffer": "3.0.1" - } + }, + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "version": "4.5.4" }, "levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "requires": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" - } + }, + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "version": "0.4.1" }, "lines-and-columns": { - "version": "1.2.4", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + "version": "1.2.4" }, "locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "requires": { "p-locate": "^5.0.0" - } + }, + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "version": "6.0.0" }, "lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "dev": true, "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "version": "4.6.2" }, "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==", "requires": { "js-tokens": "^3.0.0 || ^4.0.0" - } + }, + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "version": "1.4.0" }, "lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dev": true, + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "requires": { "yallist": "^3.0.2" - } + }, + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "version": "5.1.1" }, "merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "dev": true, "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "version": "1.4.1" }, "micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "requires": { "braces": "^3.0.3", "picomatch": "^2.3.1" - } + }, + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "version": "4.0.8" }, "mime-db": { - "version": "1.52.0", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + "version": "1.52.0" }, "mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "requires": { "mime-db": "1.52.0" - } + }, + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "version": "2.1.35" }, "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "requires": { "brace-expansion": "^1.1.7" - } + }, + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "version": "3.1.2" }, "ms": { - "version": "2.1.3", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + "version": "2.1.3" }, "nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "dev": true, "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", - "dev": true + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "version": "3.3.7" }, "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "dev": true, "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "version": "1.4.0" }, "natural-compare-lite": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "dev": true, "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", - "dev": true + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "version": "1.4.0" }, "next-tick": { - "version": "1.1.0", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", - "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" + "version": "1.1.0" }, "node-gyp-build": { - "version": "4.8.4", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", - "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==" + "version": "4.8.4" }, "node-releases": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "dev": true, "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", - "dev": true + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "version": "2.0.18" }, "notistack": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/notistack/-/notistack-3.0.1.tgz", + "dependencies": { + "clsx": { + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "version": "1.2.1" + } + }, "integrity": "sha512-ntVZXXgSQH5WYfyU+3HfcXuKaapzAJ8fBLQ/G618rn3yvSzEbnOB8ZSOwhX+dAORy/lw+GC2N061JA0+gYWTVA==", "requires": { "clsx": "^1.1.0", "goober": "^2.0.33" }, - "dependencies": { - "clsx": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", - "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==" - } - } + "resolved": "https://registry.npmjs.org/notistack/-/notistack-3.0.1.tgz", + "version": "3.0.1" }, "object-assign": { - "version": "4.1.1", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" + "version": "4.1.1" }, "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "dev": true, + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "requires": { "wrappy": "1" - } + }, + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "version": "1.4.0" }, "optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "requires": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", @@ -2297,172 +2291,172 @@ "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" - } + }, + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "version": "0.9.4" }, "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "requires": { "yocto-queue": "^0.1.0" - } + }, + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "version": "3.1.0" }, "p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "requires": { "p-limit": "^3.0.2" - } + }, + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "version": "5.0.0" }, "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "requires": { "callsites": "^3.0.0" - } + }, + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "version": "1.0.1" }, "parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", "requires": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" - } + }, + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "version": "5.2.0" }, "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "dev": true, "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "version": "4.0.0" }, "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "dev": true, "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "version": "1.0.1" }, "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "dev": true, "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "version": "3.1.1" }, "path-parse": { - "version": "1.0.7", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + "version": "1.0.7" }, "path-to-regexp": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.9.0.tgz", "integrity": "sha512-xIp7/apCFJuUHdDLWe8O1HIkb0kQrOMb/0u6FXQjemHn/ii5LrIzU6bdECnsiTF/GjZkMEKg1xdiZwNqDYlZ6g==", "requires": { "isarray": "0.0.1" - } + }, + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.9.0.tgz", + "version": "1.9.0" }, "path-type": { - "version": "4.0.0", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==" + "version": "4.0.0" }, "picocolors": { - "version": "1.1.0", + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", - "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==" + "version": "1.1.0" }, "picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "dev": true, "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "version": "2.3.1" }, "postcss": { - "version": "8.4.45", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.45.tgz", - "integrity": "sha512-7KTLTdzdZZYscUc65XmjFiB73vBhBfbPztCYdUNvlaso9PrzjzcmjqBPR0lNGkcVlcO4BjiO5rK/qNz+XAen1Q==", "dev": true, + "integrity": "sha512-7KTLTdzdZZYscUc65XmjFiB73vBhBfbPztCYdUNvlaso9PrzjzcmjqBPR0lNGkcVlcO4BjiO5rK/qNz+XAen1Q==", "requires": { "nanoid": "^3.3.7", "picocolors": "^1.0.1", "source-map-js": "^1.2.0" - } + }, + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.45.tgz", + "version": "8.4.45" }, "prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "dev": true, "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "version": "1.2.1" }, "prop-types": { - "version": "15.8.1", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", "requires": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" - } + }, + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "version": "15.8.1" }, "proxy-from-env": { - "version": "1.1.0", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + "version": "1.1.0" }, "punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "dev": true, "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "version": "2.3.1" }, "queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "dev": true, "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "version": "1.2.3" }, "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==", "requires": { "loose-envify": "^1.1.0" - } + }, + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "version": "18.3.1" }, "react-dom": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "requires": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" - } + }, + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "version": "18.3.1" }, "react-fast-compare": { - "version": "3.2.2", + "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==", "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", - "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==" + "version": "3.2.2" }, "react-is": { - "version": "16.13.1", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + "version": "16.13.1" }, "react-refresh": { - "version": "0.14.2", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", + "dev": true, "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", - "dev": true + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", + "version": "0.14.2" }, "react-router": { - "version": "5.3.4", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.3.4.tgz", "integrity": "sha512-Ys9K+ppnJah3QuaRiLxk+jDWOR1MekYQrlytiXxC1RyfbdsZkS5pvKAzCCr031xHixZwpnsYNT5xysdFHQaYsA==", "requires": { "@babel/runtime": "^7.12.13", @@ -2474,11 +2468,11 @@ "react-is": "^16.6.0", "tiny-invariant": "^1.0.2", "tiny-warning": "^1.0.0" - } + }, + "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.3.4.tgz", + "version": "5.3.4" }, "react-router-dom": { - "version": "5.3.4", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.3.4.tgz", "integrity": "sha512-m4EqFMHv/Ih4kpcBCONHbkT68KoAeHN4p3lAGoNryfHi0dMy0kCzEZakiKRsvg5wHZ/JLrLW8o8KomWiz/qbYQ==", "requires": { "@babel/runtime": "^7.12.13", @@ -2488,365 +2482,371 @@ "react-router": "5.3.4", "tiny-invariant": "^1.0.2", "tiny-warning": "^1.0.0" - } + }, + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.3.4.tgz", + "version": "5.3.4" }, "react-transition-group": { - "version": "4.4.5", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", "requires": { "@babel/runtime": "^7.5.5", "dom-helpers": "^5.0.1", "loose-envify": "^1.4.0", "prop-types": "^15.6.2" - } + }, + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "version": "4.4.5" }, "react-virtuoso": { - "version": "4.10.4", + "integrity": "sha512-G/gprhTbK+lzMxoo/iStcZxVEGph/cIhc3WANEpt92RuMw+LiCZOmBfKoeoZOHlm/iyftTrDJhGaTCpxyucnkQ==", "resolved": "https://registry.npmjs.org/react-virtuoso/-/react-virtuoso-4.10.4.tgz", - "integrity": "sha512-G/gprhTbK+lzMxoo/iStcZxVEGph/cIhc3WANEpt92RuMw+LiCZOmBfKoeoZOHlm/iyftTrDJhGaTCpxyucnkQ==" + "version": "4.10.4" }, "regenerator-runtime": { - "version": "0.14.1", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + "version": "0.14.1" }, "resolve": { - "version": "1.22.8", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", "requires": { "is-core-module": "^2.13.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" - } + }, + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "version": "1.22.8" }, "resolve-from": { - "version": "4.0.0", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" + "version": "4.0.0" }, "resolve-pathname": { - "version": "3.0.0", + "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==", "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", - "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==" + "version": "3.0.0" }, "reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "dev": true, "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "version": "1.0.4" }, "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "dev": true, + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "requires": { "glob": "^7.1.3" - } + }, + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "version": "3.0.2" }, "rollup": { - "version": "3.29.4", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz", - "integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==", "dev": true, + "integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==", "requires": { "fsevents": "~2.3.2" - } + }, + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz", + "version": "3.29.4" }, "run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", "dev": true, + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", "requires": { "queue-microtask": "^1.2.2" - } + }, + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "version": "1.2.0" }, "scheduler": { - "version": "0.23.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", "requires": { "loose-envify": "^1.1.0" - } + }, + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "version": "0.23.2" }, "semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "dev": true, "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "version": "7.6.3" }, "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "requires": { "shebang-regex": "^3.0.0" - } + }, + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "version": "2.0.0" }, "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "dev": true, "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "version": "3.0.0" }, "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "dev": true, "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "version": "3.0.0" }, "source-map": { - "version": "0.5.7", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==" + "version": "0.5.7" }, "source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "dev": true, "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "version": "1.2.1" }, "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "requires": { "ansi-regex": "^5.0.1" - } + }, + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "version": "6.0.1" }, "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "dev": true, "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "version": "3.1.1" }, "stylis": { - "version": "4.2.0", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", - "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" + "version": "4.2.0" }, "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "requires": { "has-flag": "^3.0.0" - } + }, + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "version": "5.5.0" }, "supports-preserve-symlinks-flag": { - "version": "1.0.0", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" + "version": "1.0.0" }, "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "dev": true, "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "version": "0.2.0" }, "tiny-invariant": { - "version": "1.3.3", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", - "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==" + "version": "1.3.3" }, "tiny-warning": { - "version": "1.0.3", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==", "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", - "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" + "version": "1.0.3" }, "to-fast-properties": { - "version": "2.0.0", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==" + "version": "2.0.0" }, "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "requires": { "is-number": "^7.0.0" - } + }, + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "version": "5.0.1" }, "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "dev": true, "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "version": "1.14.1" }, "tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", "dev": true, + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", "requires": { "tslib": "^1.8.1" - } + }, + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "version": "3.21.0" }, "type": { - "version": "2.7.3", + "integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==", "resolved": "https://registry.npmjs.org/type/-/type-2.7.3.tgz", - "integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==" + "version": "2.7.3" }, "type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "requires": { "prelude-ls": "^1.2.1" - } + }, + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "version": "0.4.0" }, "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "dev": true, "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "version": "0.20.2" }, "typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", "requires": { "is-typedarray": "^1.0.0" - } + }, + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "version": "3.1.5" }, "typescript": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", + "dev": true, "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==", - "dev": true + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", + "version": "5.6.2" }, "undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "dev": true, "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "version": "6.21.0" }, "update-browserslist-db": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", - "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", "dev": true, + "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", "requires": { "escalade": "^3.1.2", "picocolors": "^1.0.1" - } + }, + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", + "version": "1.1.0" }, "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "requires": { "punycode": "^2.1.0" - } + }, + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "version": "4.4.1" }, "use-sync-external-store": { - "version": "1.2.2", + "integrity": "sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==", "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz", - "integrity": "sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==" + "version": "1.2.2" }, "utf-8-validate": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==", "requires": { "node-gyp-build": "^4.3.0" - } + }, + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", + "version": "5.0.10" }, "value-equal": { - "version": "1.0.1", + "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==", "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", - "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==" + "version": "1.0.1" }, "vite": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.3.tgz", - "integrity": "sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg==", "dev": true, + "integrity": "sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg==", "requires": { "esbuild": "^0.18.10", "fsevents": "~2.3.2", "postcss": "^8.4.27", "rollup": "^3.27.1" - } + }, + "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.3.tgz", + "version": "4.5.3" }, "websocket": { - "version": "1.0.35", - "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.35.tgz", - "integrity": "sha512-/REy6amwPZl44DDzvRCkaI1q1bIiQB0mEFQLUrhz3z2EK91cp3n72rAjUlrTP0zV22HJIUOVHQGPxhFRjxjt+Q==", - "requires": { - "bufferutil": "^4.0.1", - "debug": "^2.2.0", - "es5-ext": "^0.10.63", - "typedarray-to-buffer": "^3.1.5", - "utf-8-validate": "^5.0.2", - "yaeti": "^0.0.6" - }, "dependencies": { "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "requires": { "ms": "2.0.0" - } + }, + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "version": "2.6.9" }, "ms": { - "version": "2.0.0", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "version": "2.0.0" } - } + }, + "integrity": "sha512-/REy6amwPZl44DDzvRCkaI1q1bIiQB0mEFQLUrhz3z2EK91cp3n72rAjUlrTP0zV22HJIUOVHQGPxhFRjxjt+Q==", + "requires": { + "bufferutil": "^4.0.1", + "debug": "^2.2.0", + "es5-ext": "^0.10.63", + "typedarray-to-buffer": "^3.1.5", + "utf-8-validate": "^5.0.2", + "yaeti": "^0.0.6" + }, + "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.35.tgz", + "version": "1.0.35" }, "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "requires": { "isexe": "^2.0.0" - } + }, + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "version": "2.0.2" }, "word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "dev": true, "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "version": "1.2.5" }, "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "dev": true, "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "version": "1.0.2" }, "yaeti": { - "version": "0.0.6", + "integrity": "sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==", "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz", - "integrity": "sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==" + "version": "0.0.6" }, "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "dev": true, "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "version": "3.1.1" }, "yaml": { - "version": "1.10.2", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==" + "version": "1.10.2" }, "yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "dev": true, "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "version": "0.1.0" } - } + }, + "lockfileVersion": 1, + "name": "cluster-ui", + "requires": true, + "version": "0.0.0" } diff --git a/cluster-ui/package.json b/cluster-ui/package.json index 5597173..4c9625b 100644 --- a/cluster-ui/package.json +++ b/cluster-ui/package.json @@ -1,14 +1,4 @@ { - "name": "cluster-ui", - "private": true, - "version": "0.0.0", - "type": "module", - "scripts": { - "dev": "vite", - "build": "tsc && vite build", - "lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0", - "preview": "vite preview" - }, "dependencies": { "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", @@ -39,5 +29,15 @@ "eslint-plugin-react-refresh": "^0.4.1", "typescript": "^5.0.2", "vite": "^4.4.0" - } + }, + "name": "cluster-ui", + "private": true, + "scripts": { + "build": "tsc && vite build", + "dev": "vite", + "lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "preview": "vite preview" + }, + "type": "module", + "version": "0.0.0" } diff --git a/cluster-ui/public/config.js b/cluster-ui/public/config.js index 31ab705..ce6a4cc 100644 --- a/cluster-ui/public/config.js +++ b/cluster-ui/public/config.js @@ -7,4 +7,4 @@ window.config = { "01cloud":["https://console.example.io/loginsso/clustermanager","https://console.test.example.dev/loginsso/clustermanager","https://console.staging.example.dev/loginsso/clustermanager"] }, VITE_APP_WEBSOCKET_CONNECTION_URL : "wss://example.com/v1/websocket" -} \ No newline at end of file +} diff --git a/cluster-ui/public/vite.svg b/cluster-ui/public/vite.svg index ca93aa1..c5ad168 100644 --- a/cluster-ui/public/vite.svg +++ b/cluster-ui/public/vite.svg @@ -5,4 +5,4 @@ - \ No newline at end of file + diff --git a/cluster-ui/src/app.tsx b/cluster-ui/src/app.tsx index a730753..57f49ad 100644 --- a/cluster-ui/src/app.tsx +++ b/cluster-ui/src/app.tsx @@ -13,21 +13,21 @@ export const App = () => { - + - + - + diff --git a/cluster-ui/src/assets/logo.svg b/cluster-ui/src/assets/logo.svg index 94728cf..1ee365a 100644 --- a/cluster-ui/src/assets/logo.svg +++ b/cluster-ui/src/assets/logo.svg @@ -5,4 +5,4 @@ - \ No newline at end of file + diff --git a/cluster-ui/src/assets/react.svg b/cluster-ui/src/assets/react.svg index 6c87de9..8e0e0f1 100644 --- a/cluster-ui/src/assets/react.svg +++ b/cluster-ui/src/assets/react.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/cluster-ui/src/container/WebsocketConnection.tsx b/cluster-ui/src/container/WebsocketConnection.tsx index 56295f6..2d334d1 100644 --- a/cluster-ui/src/container/WebsocketConnection.tsx +++ b/cluster-ui/src/container/WebsocketConnection.tsx @@ -6,7 +6,7 @@ import { useKeycloak } from "@react-keycloak/web"; export const useWebsocketConnection = () => { const { keycloak } = useKeycloak(); const [ws, setWS] = useState(null); - const [clusterStatus, setClusterStatus] = useState([]); + const [clusterStatus, setClusterStatus] = useState([]); const config = (window as any).config; const { @@ -31,7 +31,7 @@ export const useWebsocketConnection = () => { console.info("WebSocket connection disconnected", e); setWS(null); if (e.code !== 1000) { - setTimeout(() => socketConnection(id), 5000); + setTimeout(() => socketConnection(id), 5000); } }; @@ -43,7 +43,7 @@ export const useWebsocketConnection = () => { newWs.onmessage = (event: any) => { const response = event.data; - + if (response) { try { const _data = JSON.parse(response); @@ -64,7 +64,7 @@ export const useWebsocketConnection = () => { }; }; - const hasSocketConnection = useRef(false); + const hasSocketConnection = useRef(false); useEffect(() => { if (!isClusterListError && clusterDataFromAPI && clusterDataFromAPI.data.data.length > 0) { @@ -81,7 +81,7 @@ export const useWebsocketConnection = () => { hasSocketConnection.current = true; } } else { - setClusterStatus([]); + setClusterStatus([]); } return () => { @@ -91,5 +91,5 @@ export const useWebsocketConnection = () => { } }, [clusterDataFromAPI, isClusterListError]); - return { clusterStatus }; -}; \ No newline at end of file + return { clusterStatus }; +}; diff --git a/cluster-ui/src/helpers/commonHelper.tsx b/cluster-ui/src/helpers/commonHelper.tsx index 42bcc43..eb5665a 100644 --- a/cluster-ui/src/helpers/commonHelper.tsx +++ b/cluster-ui/src/helpers/commonHelper.tsx @@ -13,4 +13,4 @@ export const StatusColorHelper = (status:string) =>{ default: return "#2196F3"; } -} \ No newline at end of file +} diff --git a/cluster-ui/src/hooks/useClusterActions.ts b/cluster-ui/src/hooks/useClusterActions.ts index ba0ed9a..2a89fff 100644 --- a/cluster-ui/src/hooks/useClusterActions.ts +++ b/cluster-ui/src/hooks/useClusterActions.ts @@ -60,4 +60,4 @@ export const useStopClusterResponse = () => { enqueueSnackbar(error.response.data.detail||"Failed to delete cluster !", { variant: 'error' }) }, }); -} \ No newline at end of file +} diff --git a/cluster-ui/src/hooks/useClusterDetails.ts b/cluster-ui/src/hooks/useClusterDetails.ts index ef627b5..9b91c40 100644 --- a/cluster-ui/src/hooks/useClusterDetails.ts +++ b/cluster-ui/src/hooks/useClusterDetails.ts @@ -10,4 +10,4 @@ export const useClusterDetailsResponse=(id:string,token:string)=>{ return fetchClusterById(id, token); }, }); -} \ No newline at end of file +} diff --git a/cluster-ui/src/hooks/useClusterListResponse.ts b/cluster-ui/src/hooks/useClusterListResponse.ts index 1584e60..babd149 100644 --- a/cluster-ui/src/hooks/useClusterListResponse.ts +++ b/cluster-ui/src/hooks/useClusterListResponse.ts @@ -12,4 +12,4 @@ export const useClusterListResponse = (token: string) => { }); -} \ No newline at end of file +} diff --git a/cluster-ui/src/hooks/useClusterVersionList.ts b/cluster-ui/src/hooks/useClusterVersionList.ts index cd1e716..af878a8 100644 --- a/cluster-ui/src/hooks/useClusterVersionList.ts +++ b/cluster-ui/src/hooks/useClusterVersionList.ts @@ -36,4 +36,4 @@ export const useUpdateClusterVersion = () => { }, }); -} \ No newline at end of file +} diff --git a/cluster-ui/src/hooks/useCreateClusterResponse.ts b/cluster-ui/src/hooks/useCreateClusterResponse.ts index bd626f9..6faae7b 100644 --- a/cluster-ui/src/hooks/useCreateClusterResponse.ts +++ b/cluster-ui/src/hooks/useCreateClusterResponse.ts @@ -19,4 +19,4 @@ export const useCreateClusterResponse = () => { enqueueSnackbar(error.response.data.detail||"Cluster creation failed !", { variant: "error" }) }, }); -} \ No newline at end of file +} diff --git a/cluster-ui/src/hooks/useHostClusterListResponse.ts b/cluster-ui/src/hooks/useHostClusterListResponse.ts index 7fba68e..adbd5ae 100644 --- a/cluster-ui/src/hooks/useHostClusterListResponse.ts +++ b/cluster-ui/src/hooks/useHostClusterListResponse.ts @@ -9,4 +9,4 @@ export const useHostClusterListResponse = (token: string) => { }, }); -} \ No newline at end of file +} diff --git a/cluster-ui/src/hooks/useKubeConfigDownload.ts b/cluster-ui/src/hooks/useKubeConfigDownload.ts index bed1740..3c446e9 100644 --- a/cluster-ui/src/hooks/useKubeConfigDownload.ts +++ b/cluster-ui/src/hooks/useKubeConfigDownload.ts @@ -37,4 +37,4 @@ export const useKubeConfigDownload = () => { }, }) -} \ No newline at end of file +} diff --git a/cluster-ui/src/hooks/useSubscriptionsResponse.ts b/cluster-ui/src/hooks/useSubscriptionsResponse.ts index 4675263..5423ee7 100644 --- a/cluster-ui/src/hooks/useSubscriptionsResponse.ts +++ b/cluster-ui/src/hooks/useSubscriptionsResponse.ts @@ -36,4 +36,4 @@ export const useSubscriptionReq = () => { enqueueSnackbar(error.response.data.detail||"Subscription requested failed !", { variant: "error" }) }, }); -} \ No newline at end of file +} diff --git a/cluster-ui/src/index.css b/cluster-ui/src/index.css index be4c762..d54469c 100644 --- a/cluster-ui/src/index.css +++ b/cluster-ui/src/index.css @@ -1,7 +1,7 @@ body { margin: 0; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', - 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, + Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; @@ -12,4 +12,4 @@ body { display: flex; align-items: center; justify-content: center; -} \ No newline at end of file +} diff --git a/cluster-ui/src/main.tsx b/cluster-ui/src/main.tsx index 1c2bcad..ab8ba74 100644 --- a/cluster-ui/src/main.tsx +++ b/cluster-ui/src/main.tsx @@ -39,4 +39,4 @@ if (rootElement) { ); } else { console.error("Root element not found"); -} \ No newline at end of file +} diff --git a/cluster-ui/src/models/clusters.d.ts b/cluster-ui/src/models/clusters.d.ts index d4959fe..033a83a 100644 --- a/cluster-ui/src/models/clusters.d.ts +++ b/cluster-ui/src/models/clusters.d.ts @@ -69,4 +69,4 @@ export interface I_get_cluster_list_response extends I_api_list_response{ export interface I_get_host_cluster_list_response extends I_api_list_response{ data: I_hostcluster[] -} \ No newline at end of file +} diff --git a/cluster-ui/src/models/response.d.ts b/cluster-ui/src/models/response.d.ts index 42c0545..5b4aa99 100644 --- a/cluster-ui/src/models/response.d.ts +++ b/cluster-ui/src/models/response.d.ts @@ -8,6 +8,3 @@ export interface I_api_list_response extends I_api_response { page: number; size: number; } - - - diff --git a/cluster-ui/src/models/subscriptions.ts b/cluster-ui/src/models/subscriptions.ts index 52ef470..d95fea5 100644 --- a/cluster-ui/src/models/subscriptions.ts +++ b/cluster-ui/src/models/subscriptions.ts @@ -34,12 +34,12 @@ export interface I_subscriptioncheck { } export interface I_get_subscriptions_response extends I_api_response{ - + data:I_subscription[]; } export interface I_subscription_check_response extends I_api_response{ - + data:I_subscriptioncheck; -} \ No newline at end of file +} diff --git a/cluster-ui/src/models/user.d.ts b/cluster-ui/src/models/user.d.ts index bc07e83..368a884 100644 --- a/cluster-ui/src/models/user.d.ts +++ b/cluster-ui/src/models/user.d.ts @@ -2,4 +2,4 @@ export type T_user = { name: null | string; email: null | string; email_verified: null | string -} \ No newline at end of file +} diff --git a/cluster-ui/src/services/clusterService.ts b/cluster-ui/src/services/clusterService.ts index bc1e9ca..816e411 100644 --- a/cluster-ui/src/services/clusterService.ts +++ b/cluster-ui/src/services/clusterService.ts @@ -129,4 +129,4 @@ export const updateClusterVersion = ( payload, getAuthHeaderConfig(token) ); -}; \ No newline at end of file +}; diff --git a/cluster-ui/src/services/keycloak.tsx b/cluster-ui/src/services/keycloak.tsx index 5d353be..30b1be7 100644 --- a/cluster-ui/src/services/keycloak.tsx +++ b/cluster-ui/src/services/keycloak.tsx @@ -3,7 +3,7 @@ const config=(window as any).config const keycloakConfig = { url: config.VITE_APP_KEYCLOAK_URL, realm: config.VITE_APP_REALM, - clientId:config.VITE_APP_CLIENT_ID, + clientId:config.VITE_APP_CLIENT_ID, }; const keycloak = new Keycloak(keycloakConfig); diff --git a/cluster-ui/src/store/useSubscription.ts b/cluster-ui/src/store/useSubscription.ts index 9e580b3..8f53676 100644 --- a/cluster-ui/src/store/useSubscription.ts +++ b/cluster-ui/src/store/useSubscription.ts @@ -13,4 +13,4 @@ // })) -// ? example to setup store from zustand - currently zustand is pruned \ No newline at end of file +// ? example to setup store from zustand - currently zustand is pruned diff --git a/cluster-ui/src/views/components/clusterActionArea/clusterActionArea.tsx b/cluster-ui/src/views/components/clusterActionArea/clusterActionArea.tsx index 7db25c3..a125f50 100644 --- a/cluster-ui/src/views/components/clusterActionArea/clusterActionArea.tsx +++ b/cluster-ui/src/views/components/clusterActionArea/clusterActionArea.tsx @@ -123,10 +123,10 @@ export const ClusterActionArea = ({ clusterDetails }: I_props) => { try { await updateClusterVersionAsync({id:params.id,cluster:{kube_version:clusterDetails.kube_version}}); - + } catch (error) { console.log(error); - + } } diff --git a/cluster-ui/src/views/components/clusterInfoArea/clusterInfoArea.tsx b/cluster-ui/src/views/components/clusterInfoArea/clusterInfoArea.tsx index 111cbe2..41bfe30 100644 --- a/cluster-ui/src/views/components/clusterInfoArea/clusterInfoArea.tsx +++ b/cluster-ui/src/views/components/clusterInfoArea/clusterInfoArea.tsx @@ -37,7 +37,7 @@ export const ClusterInfoArea = ({ clusterDetails }: I_props) => { justifyContent: "space-between", alignItems: "center", gap:2 - + }} > diff --git a/cluster-ui/src/views/components/clusterList/clusterList.tsx b/cluster-ui/src/views/components/clusterList/clusterList.tsx index 8756618..aeffb28 100644 --- a/cluster-ui/src/views/components/clusterList/clusterList.tsx +++ b/cluster-ui/src/views/components/clusterList/clusterList.tsx @@ -125,7 +125,7 @@ export const ClusterList = () => { const refreshClusterList = () => { queryClient.invalidateQueries({ queryKey: ["clusterList"] }); }; - + if (rest.fetchStatus === "fetching") return ( @@ -171,7 +171,7 @@ export const ClusterList = () => { handleClose={handleCloseDownloadDialog} handleClickOnDownloadConfig={handleClickOnDownloadConfig} /> - + ); diff --git a/cluster-ui/src/views/components/clusterList/table.d.ts b/cluster-ui/src/views/components/clusterList/table.d.ts index 667d2e1..a5fb3c4 100644 --- a/cluster-ui/src/views/components/clusterList/table.d.ts +++ b/cluster-ui/src/views/components/clusterList/table.d.ts @@ -9,4 +9,3 @@ export interface Data { actions: JSX.Element; download: JSX.Element; } - diff --git a/cluster-ui/src/views/components/clusterList/table.utils.ts b/cluster-ui/src/views/components/clusterList/table.utils.ts index 1a19c6a..51dda9a 100644 --- a/cluster-ui/src/views/components/clusterList/table.utils.ts +++ b/cluster-ui/src/views/components/clusterList/table.utils.ts @@ -40,5 +40,3 @@ export const formatDate = (dateString: string): string => { const seconds = String(date.getSeconds()).padStart(2, "0"); return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; }; - - diff --git a/cluster-ui/src/views/components/clusterList/tableContents.utils.tsx b/cluster-ui/src/views/components/clusterList/tableContents.utils.tsx index d99f413..0c61559 100644 --- a/cluster-ui/src/views/components/clusterList/tableContents.utils.tsx +++ b/cluster-ui/src/views/components/clusterList/tableContents.utils.tsx @@ -22,7 +22,7 @@ const columns: ColumnData[] = [ label: "SN", dataKey: "sn", }, - + { width: 200, label: "Name", @@ -51,7 +51,7 @@ const columns: ColumnData[] = [ dataKey: "status", numeric: true, }, - + { width: 120, label: "Download", diff --git a/cluster-ui/src/views/components/createCluster/createCluster.tsx b/cluster-ui/src/views/components/createCluster/createCluster.tsx index 329bc5c..ba573c6 100644 --- a/cluster-ui/src/views/components/createCluster/createCluster.tsx +++ b/cluster-ui/src/views/components/createCluster/createCluster.tsx @@ -178,7 +178,7 @@ if(subsCheck.user_groups.length === 0) return setClusterVersion(clusterVersionList[0]); } }, [clusterVersionList]); - + return (
@@ -212,9 +212,9 @@ if(subsCheck.user_groups.length === 0) return } > {subsCheck && subsCheck.user_groups?.length > 0 - ? + ? "Your request has been received, waiting for admin approval" - : + : "You are not authorized to create cluster request for cluster creation" } diff --git a/cluster-ui/src/views/components/header/header.tsx b/cluster-ui/src/views/components/header/header.tsx index 29c558e..0f4d3d8 100644 --- a/cluster-ui/src/views/components/header/header.tsx +++ b/cluster-ui/src/views/components/header/header.tsx @@ -101,7 +101,7 @@ function ResponsiveAppBar({ userDetails }: I_props) { sx={{ fontSize: "20px", color: "#fff", - + "&:hover": { cursor: "pointer", // background:"#5d92f5", diff --git a/cluster-ui/src/views/components/profileMenu/profileMenu.tsx b/cluster-ui/src/views/components/profileMenu/profileMenu.tsx index 0fcf8a9..efb0b91 100644 --- a/cluster-ui/src/views/components/profileMenu/profileMenu.tsx +++ b/cluster-ui/src/views/components/profileMenu/profileMenu.tsx @@ -75,7 +75,7 @@ export const ProfileMenu = ({ overflow: "hidden", textOverflow: "ellipsis",maxWidth:320}} > - {userDetails && userDetails.email ? userDetails.email : ""} + {userDetails && userDetails.email ? userDetails.email : ""}
@@ -139,7 +139,7 @@ export const ProfileMenu = ({ } /> - + diff --git a/cluster-ui/src/views/pages/ClusterDetail/clusterInfo.tsx b/cluster-ui/src/views/pages/ClusterDetail/clusterInfo.tsx index f66567a..d636a87 100644 --- a/cluster-ui/src/views/pages/ClusterDetail/clusterInfo.tsx +++ b/cluster-ui/src/views/pages/ClusterDetail/clusterInfo.tsx @@ -10,7 +10,7 @@ export const ClusterInfo = () => {
- + ); }; diff --git a/cluster-ui/tsconfig.node.json b/cluster-ui/tsconfig.node.json index 42872c5..485dcfa 100644 --- a/cluster-ui/tsconfig.node.json +++ b/cluster-ui/tsconfig.node.json @@ -1,10 +1,12 @@ { "compilerOptions": { + "allowSyntheticDefaultImports": true, "composite": true, - "skipLibCheck": true, "module": "ESNext", "moduleResolution": "bundler", - "allowSyntheticDefaultImports": true + "skipLibCheck": true }, - "include": ["vite.config.ts"] + "include": [ + "vite.config.ts" + ] } diff --git a/cluster-ui/vite.config.ts b/cluster-ui/vite.config.ts index 5c32366..8ca5565 100644 --- a/cluster-ui/vite.config.ts +++ b/cluster-ui/vite.config.ts @@ -7,6 +7,6 @@ export default defineConfig({ host: '0.0.0.0', port: 3000, }, - - + + }) diff --git a/commitlint.config.js b/commitlint.config.js index 2cc6e05..e8aa642 100644 --- a/commitlint.config.js +++ b/commitlint.config.js @@ -19,6 +19,6 @@ module.exports = { 'scope-case': [2, 'always', 'lowerCase'], 'subject-empty': [2, 'never'], 'subject-full-stop': [2, 'never', '.'], - 'header-max-length': [2, 'always', 72], + 'header-max-length': [2, 'always', 72], }, }; diff --git a/docs/content/contributing/submitting-pull-requests.md b/docs/content/contributing/submitting-pull-requests.md index 5d31fce..25ac1b3 100644 --- a/docs/content/contributing/submitting-pull-requests.md +++ b/docs/content/contributing/submitting-pull-requests.md @@ -15,7 +15,7 @@ Here’s how you can get started: - Check out the [Good First Issues](https://github.com/01cloud/ClusterManager/issues). - Browse the list of [Confirmed Bugs](https://github.com/01cloud/ClusterManager/issues). -- **Set Up Your Environment:** +- **Set Up Your Environment:** Follow the [Development Guide](https://github.com/01cloud/ClusterManager/) to set up your developer environment. --- @@ -36,7 +36,7 @@ Before submitting a PR: - Use semantic line breaks for documentation updates. 4. **Dependencies:** - Reference a tag in `requirements.txt`. If not possible, explain why. -5. **Allow Maintainers to Edit:** +5. **Allow Maintainers to Edit:** Keep the "Allow edits from maintainers" checkbox checked. ### 🚦 **Validation Requirements** @@ -94,13 +94,13 @@ For more details, see the [Lobicornis README](https://github.com/traefik/lobicor Your PR may be closed if: -1. **Design Conflicts:** +1. **Design Conflicts:** The work needed to make it mergeable is too extensive. - **Solution:** Create an issue first and involve maintainers during the design phase. -2. **Out-of-Scope Enhancements:** +2. **Out-of-Scope Enhancements:** The PR is for a feature that doesn't align with project goals. - **Solution:** Always create an issue before submitting a PR. -3. **Inactivity:** +3. **Inactivity:** If no feedback is received from the contributor for over **90 days**. --- @@ -124,13 +124,13 @@ Your PR may be closed if: ## 🔑 **Best Practices for PRs** -- **Create Issues First:** +- **Create Issues First:** Discuss your proposal with the team before starting work. -- **Submit Small PRs:** +- **Submit Small PRs:** Break large ideas into smaller, logical PRs. -- **Comment Everything:** +- **Comment Everything:** Assume reviewers are unfamiliar with your perspective or approach. -- **Write Tests:** +- **Write Tests:** Include adequate tests or ask for help with them. ### Need Help? @@ -142,9 +142,9 @@ Your PR may be closed if: ## 🤝 **Handling Feedback** -- **Disagreements:** +- **Disagreements:** If you believe a requested change is unnecessary, explain your reasoning politely. -- **Overwhelming Comments:** +- **Overwhelming Comments:** Focus on feedback from the primary reviewer (assignee). If needed, ask to fork a new PR to clear outdated comments. --- diff --git a/docs/content/contributing/thank-you.md b/docs/content/contributing/thank-you.md index 2e6a530..d3efbe9 100644 --- a/docs/content/contributing/thank-you.md +++ b/docs/content/contributing/thank-you.md @@ -4,7 +4,7 @@ ### 🚀 Thank You for Contributing! -_**You** Made It._ +_**You** Made It._ {: .subtitle} ClusterManager thrives because of its passionate community of contributors like **you**! Whether it's code, documentation, feedback, or simply sharing ClusterManager with others, your support has made it what it is today — a robust, open-source project used worldwide. diff --git a/docs/script/clusterManager_setup-local.sh b/docs/script/clusterManager_setup-local.sh index 9a23a49..daa3d14 100644 --- a/docs/script/clusterManager_setup-local.sh +++ b/docs/script/clusterManager_setup-local.sh @@ -339,18 +339,18 @@ function setup_metallb_host() { echo "Detecting network configuration..." local network="" local control_plane_id - + # First try: Get network from control plane container control_plane_id=$(docker ps -a | grep "kind-control-plane" | grep "$HOST_CLUSTER" | awk '{print $1}') if [ -n "$control_plane_id" ]; then network=$(docker inspect "$control_plane_id" | grep -oP '"Gateway": "\K[0-9]+\.[0-9]+' | head -1) fi - + # Second try: Get network from kind network directly if [ -z "$network" ]; then network=$(docker network inspect kind | grep -oP '"Gateway": "\K[0-9]+\.[0-9]+' | head -1) fi - + # Fallback to default if both methods fail if [ -z "$network" ]; then network="172.18" @@ -364,7 +364,7 @@ function setup_metallb_host() { # Configure IPAddressPool if not already configured if ! kubectl get ipaddresspool -n metallb-system example --kubeconfig="$KUBECONFIG" >/dev/null 2>&1; then echo "Creating IPAddressPool with range $IP_START-$IP_END..." - + # Apply IPAddressPool cat </dev/null 2>&1 && \ kubectl get l2advertisement -n metallb-system default --kubeconfig="$KUBECONFIG" >/dev/null 2>&1; then break fi - + if (( SECONDS - verify_start >= verify_timeout )); then echo "Error: Timeout waiting for MetalLB configuration verification" return 1 fi - + echo "Waiting for MetalLB configuration to be ready..." sleep 5 done @@ -483,18 +483,18 @@ function setup_metallb_service() { echo "Detecting network configuration..." local network="" local control_plane_id - + # First try: Get network from control plane container control_plane_id=$(docker ps -a | grep "kind-control-plane" | grep "$SERVICE_CLUSTER" | awk '{print $1}') if [ -n "$control_plane_id" ]; then network=$(docker inspect "$control_plane_id" | grep -oP '"Gateway": "\K[0-9]+\.[0-9]+' | head -1) fi - + # Second try: Get network from kind network directly if [ -z "$network" ]; then network=$(docker network inspect kind | grep -oP '"Gateway": "\K[0-9]+\.[0-9]+' | head -1) fi - + # Fallback to default if both methods fail if [ -z "$network" ]; then network="172.18" @@ -737,8 +737,8 @@ spec: value: /etc/tls/ca-certificates/ca.crt - name: SERVICE_URL value: https://cluster-service-python.clustermanager.local - - name: CLIENT_ID - value: clustermanagerclient + - name: CLIENT_ID + value: clustermanagerclient image: umesh1212/cluster-api:tag16 imagePullPolicy: Always livenessProbe: @@ -769,14 +769,14 @@ spec: requests: cpu: 20m memory: 20Mi - volumeMounts: - - mountPath: /etc/tls/ca-certificates/ - name: mkcert-ca - volumes: - - configMap: - defaultMode: 420 - name: mkcert-ca - name: mkcert-ca + volumeMounts: + - mountPath: /etc/tls/ca-certificates/ + name: mkcert-ca + volumes: + - configMap: + defaultMode: 420 + name: mkcert-ca + name: mkcert-ca --- apiVersion: v1 kind: Service @@ -849,7 +849,7 @@ spec: metadata: labels: app: node - app.kubernetes.io/provider: zerone + app.kubernetes.io/provider: zerone spec: containers: - image: rajivgs/cluster-ui:v20 @@ -996,8 +996,8 @@ apiVersion: networking.k8s.io/v1 kind: Ingress metadata: annotations: - nginx.ingress.kubernetes.io/proxy-buffer-size: 128k - nginx.ingress.kubernetes.io/proxy-buffers: 4 256k + nginx.ingress.kubernetes.io/proxy-buffer-size: 128k + nginx.ingress.kubernetes.io/proxy-buffers: 4 256k name: keycloak spec: ingressClassName: nginx @@ -1376,7 +1376,7 @@ sleep 10 function setup_metrics_server() { echo "Installing metrics server in $HOST_CLUSTER..." export KUBECONFIG="$HOST_CLUSTER-kubeconfig" - + # Install metrics server components kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml @@ -1435,5 +1435,3 @@ update_hosts_file_with_ingress $SERVICE_CLUSTER update_hosts_file_with_ingress $HOST_CLUSTER sleep 10 setup_metrics_server - - \ No newline at end of file diff --git a/manifest/charts/dapr/charts/dapr_operator/templates/dapr_operator_service.yaml b/manifest/charts/dapr/charts/dapr_operator/templates/dapr_operator_service.yaml index fb994a6..2d8bc1e 100644 --- a/manifest/charts/dapr/charts/dapr_operator/templates/dapr_operator_service.yaml +++ b/manifest/charts/dapr/charts/dapr_operator/templates/dapr_operator_service.yaml @@ -11,7 +11,7 @@ spec: app: dapr-operator ports: - protocol: TCP - port: {{ .Values.ports.port }} + port: {{ .Values.ports.port }} targetPort: {{ .Values.ports.targetPort }} --- apiVersion: v1 @@ -28,4 +28,4 @@ spec: targetPort: 19443 protocol: TCP selector: - app: dapr-operator \ No newline at end of file + app: dapr-operator diff --git a/manifest/charts/dapr/charts/dapr_placement/templates/dapr_placement_service.yaml b/manifest/charts/dapr/charts/dapr_placement/templates/dapr_placement_service.yaml index 7b7e443..0eb934b 100644 --- a/manifest/charts/dapr/charts/dapr_placement/templates/dapr_placement_service.yaml +++ b/manifest/charts/dapr/charts/dapr_placement/templates/dapr_placement_service.yaml @@ -20,4 +20,4 @@ spec: - name: raft-node port: {{ .Values.ports.raftRPCPort }} clusterIP: None -{{- end }} \ No newline at end of file +{{- end }} diff --git a/manifest/charts/dapr/charts/dapr_sentry/templates/dapr_sentry_service.yaml b/manifest/charts/dapr/charts/dapr_sentry/templates/dapr_sentry_service.yaml index 3f81a0c..decc97f 100644 --- a/manifest/charts/dapr/charts/dapr_sentry/templates/dapr_sentry_service.yaml +++ b/manifest/charts/dapr/charts/dapr_sentry/templates/dapr_sentry_service.yaml @@ -11,5 +11,5 @@ spec: app: dapr-sentry ports: - protocol: TCP - port: {{ .Values.ports.port }} - targetPort: {{ .Values.ports.targetPort }} \ No newline at end of file + port: {{ .Values.ports.port }} + targetPort: {{ .Values.ports.targetPort }} diff --git a/manifest/charts/dapr/charts/dapr_sidecar_injector/templates/dapr_sidecar_injector_deployment.yaml b/manifest/charts/dapr/charts/dapr_sidecar_injector/templates/dapr_sidecar_injector_deployment.yaml index dda4051..7a7375a 100644 --- a/manifest/charts/dapr/charts/dapr_sidecar_injector/templates/dapr_sidecar_injector_deployment.yaml +++ b/manifest/charts/dapr/charts/dapr_sidecar_injector/templates/dapr_sidecar_injector_deployment.yaml @@ -12,7 +12,7 @@ spec: {{- if eq .Values.global.ha.enabled true }} replicas: {{ .Values.global.ha.replicaCount }} {{- else }} - replicas: {{ .Values.replicaCount }} + replicas: {{ .Values.replicaCount }} {{- end }} selector: matchLabels: diff --git a/manifest/charts/dapr/crds/httpendpoints.yaml b/manifest/charts/dapr/crds/httpendpoints.yaml index 2266759..dbbfb44 100644 --- a/manifest/charts/dapr/crds/httpendpoints.yaml +++ b/manifest/charts/dapr/crds/httpendpoints.yaml @@ -79,4 +79,4 @@ status: kind: "" plural: "" conditions: [] - storedVersions: [] \ No newline at end of file + storedVersions: [] diff --git a/manifest/charts/dapr/crds/resiliency.yaml b/manifest/charts/dapr/crds/resiliency.yaml index ecc7a03..342315d 100644 --- a/manifest/charts/dapr/crds/resiliency.yaml +++ b/manifest/charts/dapr/crds/resiliency.yaml @@ -132,4 +132,3 @@ spec: type: object served: true storage: true - diff --git a/manifest/charts/dapr/crds/subscription.yaml b/manifest/charts/dapr/crds/subscription.yaml index cdf42c6..bbaee15 100644 --- a/manifest/charts/dapr/crds/subscription.yaml +++ b/manifest/charts/dapr/crds/subscription.yaml @@ -56,7 +56,7 @@ spec: bulkSubscribe: description: Represents bulk subscribe properies properties: - enabled: + enabled: type: boolean maxMessagesCount: type: integer @@ -146,7 +146,7 @@ spec: bulkSubscribe: description: Represents bulk subscribe properies properties: - enabled: + enabled: type: boolean maxMessagesCount: type: integer diff --git a/manifest/charts/keycloak/README.md b/manifest/charts/keycloak/README.md index 3d8ff5f..85b4458 100644 --- a/manifest/charts/keycloak/README.md +++ b/manifest/charts/keycloak/README.md @@ -495,4 +495,4 @@ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and -limitations under the License. \ No newline at end of file +limitations under the License. diff --git a/manifest/charts/keycloak/charts/common/templates/_utils.tpl b/manifest/charts/keycloak/charts/common/templates/_utils.tpl index c87040c..b9cc8f9 100644 --- a/manifest/charts/keycloak/charts/common/templates/_utils.tpl +++ b/manifest/charts/keycloak/charts/common/templates/_utils.tpl @@ -46,7 +46,7 @@ Usage: {{- $value = ( index $latestObj . ) -}} {{- $latestObj = $value -}} {{- end -}} -{{- printf "%v" (default "" $value) -}} +{{- printf "%v" (default "" $value) -}} {{- end -}} {{/* @@ -63,5 +63,5 @@ Usage: {{- $key = . }} {{- end -}} {{- end -}} -{{- printf "%s" $key -}} +{{- printf "%s" $key -}} {{- end -}} diff --git a/manifest/charts/keycloak/charts/postgresql/charts/common/templates/_utils.tpl b/manifest/charts/keycloak/charts/postgresql/charts/common/templates/_utils.tpl index c87040c..b9cc8f9 100644 --- a/manifest/charts/keycloak/charts/postgresql/charts/common/templates/_utils.tpl +++ b/manifest/charts/keycloak/charts/postgresql/charts/common/templates/_utils.tpl @@ -46,7 +46,7 @@ Usage: {{- $value = ( index $latestObj . ) -}} {{- $latestObj = $value -}} {{- end -}} -{{- printf "%v" (default "" $value) -}} +{{- printf "%v" (default "" $value) -}} {{- end -}} {{/* @@ -63,5 +63,5 @@ Usage: {{- $key = . }} {{- end -}} {{- end -}} -{{- printf "%s" $key -}} +{{- printf "%s" $key -}} {{- end -}} diff --git a/manifest/charts/keycloak/charts/postgresql/values.schema.json b/manifest/charts/keycloak/charts/postgresql/values.schema.json index fc41483..51b63a1 100644 --- a/manifest/charts/keycloak/charts/postgresql/values.schema.json +++ b/manifest/charts/keycloak/charts/postgresql/values.schema.json @@ -1,156 +1,156 @@ { "$schema": "http://json-schema.org/schema#", - "type": "object", "properties": { "architecture": { - "type": "string", - "title": "PostgreSQL architecture", + "description": "Allowed values: `standalone` or `replication`", "form": true, - "description": "Allowed values: `standalone` or `replication`" + "title": "PostgreSQL architecture", + "type": "string" }, "auth": { - "type": "object", - "title": "Authentication configuration", "form": true, "properties": { - "enablePostgresUser": { - "type": "boolean", - "title": "Enable \"postgres\" admin user", - "description": "Assign a password to the \"postgres\" admin user. Otherwise, remote access will be blocked for this user", - "form": true - }, - "postgresPassword": { - "type": "string", - "title": "Password for the \"postgres\" admin user", - "description": "Defaults to a random 10-character alphanumeric string if not set", - "form": true - }, "database": { - "type": "string", - "title": "PostgreSQL custom database", "description": "Name of the custom database to be created during the 1st initialization of PostgreSQL", - "form": true + "form": true, + "title": "PostgreSQL custom database", + "type": "string" }, - "username": { - "type": "string", - "title": "PostgreSQL custom user", - "description": "Name of the custom user to be created during the 1st initialization of PostgreSQL. This user only has permissions on the PostgreSQL custom database", - "form": true + "enablePostgresUser": { + "description": "Assign a password to the \"postgres\" admin user. Otherwise, remote access will be blocked for this user", + "form": true, + "title": "Enable \"postgres\" admin user", + "type": "boolean" }, "password": { - "type": "string", + "description": "Defaults to a random 10-character alphanumeric string if not set", + "form": true, "title": "Password for the custom user to create", + "type": "string" + }, + "postgresPassword": { "description": "Defaults to a random 10-character alphanumeric string if not set", - "form": true + "form": true, + "title": "Password for the \"postgres\" admin user", + "type": "string" + }, + "replicationPassword": { + "description": "Defaults to a random 10-character alphanumeric string if not set", + "form": true, + "hidden": { + "path": "architecture", + "value": "standalone" + }, + "title": "Password for PostgreSQL replication user", + "type": "string" }, "replicationUsername": { - "type": "string", - "title": "PostgreSQL replication user", "description": "Name of user used to manage replication.", "form": true, "hidden": { - "value": "standalone", - "path": "architecture" - } + "path": "architecture", + "value": "standalone" + }, + "title": "PostgreSQL replication user", + "type": "string" }, - "replicationPassword": { - "type": "string", - "title": "Password for PostgreSQL replication user", - "description": "Defaults to a random 10-character alphanumeric string if not set", + "username": { + "description": "Name of the custom user to be created during the 1st initialization of PostgreSQL. This user only has permissions on the PostgreSQL custom database", "form": true, - "hidden": { - "value": "standalone", - "path": "architecture" - } + "title": "PostgreSQL custom user", + "type": "string" + } + }, + "title": "Authentication configuration", + "type": "object" + }, + "metrics": { + "properties": { + "enabled": { + "form": true, + "title": "Configure metrics exporter", + "type": "boolean" } - } + }, + "type": "object" }, "persistence": { - "type": "object", "properties": { "size": { - "type": "string", - "title": "Persistent Volume Size", "form": true, "render": "slider", - "sliderMin": 1, "sliderMax": 100, - "sliderUnit": "Gi" + "sliderMin": 1, + "sliderUnit": "Gi", + "title": "Persistent Volume Size", + "type": "string" } - } + }, + "type": "object" + }, + "replication": { + "form": true, + "properties": { + "enabled": { + "form": true, + "title": "Enable Replication", + "type": "boolean" + }, + "readReplicas": { + "form": true, + "hidden": { + "path": "architecture", + "value": "standalone" + }, + "title": "read Replicas", + "type": "integer" + } + }, + "title": "Replication Details", + "type": "object" }, "resources": { - "type": "object", - "title": "Required Resources", "description": "Configure resource requests", "form": true, "properties": { "requests": { - "type": "object", "properties": { - "memory": { - "type": "string", + "cpu": { "form": true, "render": "slider", - "title": "Memory Request", + "sliderMax": 2000, "sliderMin": 10, - "sliderMax": 2048, - "sliderUnit": "Mi" + "sliderUnit": "m", + "title": "CPU Request", + "type": "string" }, - "cpu": { - "type": "string", + "memory": { "form": true, "render": "slider", - "title": "CPU Request", + "sliderMax": 2048, "sliderMin": 10, - "sliderMax": 2000, - "sliderUnit": "m" + "sliderUnit": "Mi", + "title": "Memory Request", + "type": "string" } - } + }, + "type": "object" } - } - }, - "replication": { - "type": "object", - "form": true, - "title": "Replication Details", - "properties": { - "enabled": { - "type": "boolean", - "title": "Enable Replication", - "form": true - }, - "readReplicas": { - "type": "integer", - "title": "read Replicas", - "form": true, - "hidden": { - "value": "standalone", - "path": "architecture" - } - } - } + }, + "title": "Required Resources", + "type": "object" }, "volumePermissions": { - "type": "object", "properties": { "enabled": { - "type": "boolean", + "description": "Change the owner of the persist volume mountpoint to RunAsUser:fsGroup", "form": true, "title": "Enable Init Containers", - "description": "Change the owner of the persist volume mountpoint to RunAsUser:fsGroup" - } - } - }, - "metrics": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean", - "title": "Configure metrics exporter", - "form": true + "type": "boolean" } - } + }, + "type": "object" } - } + }, + "type": "object" } diff --git a/manifest/charts/keycloak/templates/configmap-env-vars.yaml b/manifest/charts/keycloak/templates/configmap-env-vars.yaml index b21b6a0..667e2e7 100644 --- a/manifest/charts/keycloak/templates/configmap-env-vars.yaml +++ b/manifest/charts/keycloak/templates/configmap-env-vars.yaml @@ -55,10 +55,10 @@ data: {{- if .Values.cache.enabled }} KEYCLOAK_CACHE_TYPE: "ispn" {{- if .Values.cache.stackName }} - KEYCLOAK_CACHE_STACK: {{ .Values.cache.stackName | quote }} + KEYCLOAK_CACHE_STACK: {{ .Values.cache.stackName | quote }} {{- end }} {{- if .Values.cache.stackFile }} - KEYCLOAK_CACHE_CONFIG_FILE: {{ .Values.cache.stackFile | quote }} + KEYCLOAK_CACHE_CONFIG_FILE: {{ .Values.cache.stackFile | quote }} {{- end }} JAVA_OPTS_APPEND: {{ printf "-Djgroups.dns.query=%s-headless.%s.svc.%s" (include "common.names.fullname" .) (include "common.names.namespace" .) .Values.clusterDomain | quote }} {{- else }} @@ -68,4 +68,3 @@ data: KEYCLOAK_LOG_OUTPUT: {{ .Values.logging.output | quote }} KC_LOG_LEVEL: {{ .Values.logging.level | quote }} {{- end }} - diff --git a/manifest/charts/keycloak/templates/tls-secret.yaml b/manifest/charts/keycloak/templates/tls-secret.yaml index cd90df3..0db835b 100644 --- a/manifest/charts/keycloak/templates/tls-secret.yaml +++ b/manifest/charts/keycloak/templates/tls-secret.yaml @@ -77,4 +77,3 @@ data: tls.key: {{ include "common.secrets.lookup" (dict "secret" $secretName "key" "tls.key" "defaultValue" $cert.Key "context" $) }} ca.crt: {{ include "common.secrets.lookup" (dict "secret" $secretName "key" "ca.crt" "defaultValue" $ca.Cert "context" $) }} {{- end }} - diff --git a/manifest/charts/mongodb/README.md b/manifest/charts/mongodb/README.md index d7e3f4f..1e78532 100644 --- a/manifest/charts/mongodb/README.md +++ b/manifest/charts/mongodb/README.md @@ -1323,4 +1323,4 @@ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and -limitations under the License. \ No newline at end of file +limitations under the License. diff --git a/manifest/charts/mongodb/charts/common/templates/_compatibility.tpl b/manifest/charts/mongodb/charts/common/templates/_compatibility.tpl index a61588d..0fc2f22 100644 --- a/manifest/charts/mongodb/charts/common/templates/_compatibility.tpl +++ b/manifest/charts/mongodb/charts/common/templates/_compatibility.tpl @@ -5,7 +5,7 @@ SPDX-License-Identifier: APACHE-2.0 {{/* vim: set filetype=mustache: */}} -{{/* +{{/* Return true if the detected platform is Openshift Usage: {{- include "common.compatibility.isOpenshift" . -}} diff --git a/manifest/charts/mongodb/charts/common/templates/_errors.tpl b/manifest/charts/mongodb/charts/common/templates/_errors.tpl index 93f3ffc..95b8b8e 100644 --- a/manifest/charts/mongodb/charts/common/templates/_errors.tpl +++ b/manifest/charts/mongodb/charts/common/templates/_errors.tpl @@ -82,4 +82,4 @@ Usage: {{- end -}} {{- print $warnString -}} {{- end -}} -{{- end -}} \ No newline at end of file +{{- end -}} diff --git a/manifest/charts/mongodb/charts/common/templates/_images.tpl b/manifest/charts/mongodb/charts/common/templates/_images.tpl index 76bb7ce..6eedf75 100644 --- a/manifest/charts/mongodb/charts/common/templates/_images.tpl +++ b/manifest/charts/mongodb/charts/common/templates/_images.tpl @@ -112,4 +112,3 @@ Return the proper image version (ingores image revision/prerelease info & fallba {{- print .chart.AppVersion -}} {{- end -}} {{- end -}} - diff --git a/manifest/charts/mongodb/charts/common/templates/_resources.tpl b/manifest/charts/mongodb/charts/common/templates/_resources.tpl index d8a43e1..2aaf575 100644 --- a/manifest/charts/mongodb/charts/common/templates/_resources.tpl +++ b/manifest/charts/mongodb/charts/common/templates/_resources.tpl @@ -12,32 +12,32 @@ These presets are for basic testing and not meant to be used in production */}} {{- define "common.resources.preset" -}} {{/* The limits are the requests increased by 50% (except ephemeral-storage and xlarge/2xlarge sizes)*/}} -{{- $presets := dict - "nano" (dict +{{- $presets := dict + "nano" (dict "requests" (dict "cpu" "100m" "memory" "128Mi" "ephemeral-storage" "50Mi") "limits" (dict "cpu" "150m" "memory" "192Mi" "ephemeral-storage" "2Gi") ) - "micro" (dict + "micro" (dict "requests" (dict "cpu" "250m" "memory" "256Mi" "ephemeral-storage" "50Mi") "limits" (dict "cpu" "375m" "memory" "384Mi" "ephemeral-storage" "2Gi") ) - "small" (dict + "small" (dict "requests" (dict "cpu" "500m" "memory" "512Mi" "ephemeral-storage" "50Mi") "limits" (dict "cpu" "750m" "memory" "768Mi" "ephemeral-storage" "2Gi") ) - "medium" (dict + "medium" (dict "requests" (dict "cpu" "500m" "memory" "1024Mi" "ephemeral-storage" "50Mi") "limits" (dict "cpu" "750m" "memory" "1536Mi" "ephemeral-storage" "2Gi") ) - "large" (dict + "large" (dict "requests" (dict "cpu" "1.0" "memory" "2048Mi" "ephemeral-storage" "50Mi") "limits" (dict "cpu" "1.5" "memory" "3072Mi" "ephemeral-storage" "2Gi") ) - "xlarge" (dict + "xlarge" (dict "requests" (dict "cpu" "1.0" "memory" "3072Mi" "ephemeral-storage" "50Mi") "limits" (dict "cpu" "3.0" "memory" "6144Mi" "ephemeral-storage" "2Gi") ) - "2xlarge" (dict + "2xlarge" (dict "requests" (dict "cpu" "1.0" "memory" "3072Mi" "ephemeral-storage" "50Mi") "limits" (dict "cpu" "6.0" "memory" "12288Mi" "ephemeral-storage" "2Gi") ) diff --git a/manifest/charts/mongodb/charts/common/templates/_utils.tpl b/manifest/charts/mongodb/charts/common/templates/_utils.tpl index d53c74a..0a5a5bc 100644 --- a/manifest/charts/mongodb/charts/common/templates/_utils.tpl +++ b/manifest/charts/mongodb/charts/common/templates/_utils.tpl @@ -46,7 +46,7 @@ Usage: {{- $value = ( index $latestObj . ) -}} {{- $latestObj = $value -}} {{- end -}} -{{- printf "%v" (default "" $value) -}} +{{- printf "%v" (default "" $value) -}} {{- end -}} {{/* @@ -63,7 +63,7 @@ Usage: {{- $key = . }} {{- end -}} {{- end -}} -{{- printf "%s" $key -}} +{{- printf "%s" $key -}} {{- end -}} {{/* diff --git a/manifest/charts/mongodb/templates/_helpers.tpl b/manifest/charts/mongodb/templates/_helpers.tpl index 5d8cb29..5655a0b 100644 --- a/manifest/charts/mongodb/templates/_helpers.tpl +++ b/manifest/charts/mongodb/templates/_helpers.tpl @@ -572,7 +572,7 @@ Validate values of MongoDB® - number of replicas must be the same than NodeP {{- if and (not .Values.externalAccess.autoDiscovery.enabled) (eq $nodePortListLength 0) -}} mongodb: .Values.externalAccess.service.nodePorts externalAccess.service.nodePorts or externalAccess.autoDiscovery.enabled are required when externalAccess is enabled. -{{- else if and (not .Values.externalAccess.autoDiscovery.enabled) (not (eq $replicaCount $nodePortListLength )) -}} +{{- else if and (not .Values.externalAccess.autoDiscovery.enabled) (not (eq $replicaCount $nodePortListLength )) -}} mongodb: .Values.externalAccess.service.nodePorts Number of replicas ({{ $replicaCount }}) and nodePorts ({{ $nodePortListLength }}) array length must be the same. {{- end -}} diff --git a/manifest/charts/mongodb/templates/networkpolicy.yaml b/manifest/charts/mongodb/templates/networkpolicy.yaml index 4e9f2f2..40af5ef 100644 --- a/manifest/charts/mongodb/templates/networkpolicy.yaml +++ b/manifest/charts/mongodb/templates/networkpolicy.yaml @@ -73,7 +73,7 @@ spec: app.kubernetes.io/component: mongodb {{- if .Values.networkPolicy.addExternalClientAccess }} - podSelector: - matchLabels: + matchLabels: {{ template "common.names.fullname" . }}-client: "true" {{- end }} {{- if .Values.networkPolicy.ingressPodMatchLabels }} @@ -95,4 +95,4 @@ spec: {{- if $extraIngress }} {{- include "common.tplvalues.render" ( dict "value" $extraIngress "context" $ ) | nindent 4 }} {{- end }} -{{- end }} \ No newline at end of file +{{- end }} diff --git a/manifest/charts/mongodb/templates/replicaset/scripts-configmap.yaml b/manifest/charts/mongodb/templates/replicaset/scripts-configmap.yaml index ec70ecb..5ba42a4 100644 --- a/manifest/charts/mongodb/templates/replicaset/scripts-configmap.yaml +++ b/manifest/charts/mongodb/templates/replicaset/scripts-configmap.yaml @@ -313,4 +313,3 @@ data: done {{- end }} {{- end }} - \ No newline at end of file diff --git a/manifest/charts/mongodb/values.schema.json b/manifest/charts/mongodb/values.schema.json index 3eb6a64..0c3b745 100644 --- a/manifest/charts/mongodb/values.schema.json +++ b/manifest/charts/mongodb/values.schema.json @@ -1,232 +1,232 @@ { "$schema": "http://json-schema.org/schema#", - "type": "object", "properties": { + "arbiter": { + "form": true, + "properties": { + "configuration": { + "form": true, + "hidden": { + "path": "architecture", + "value": "standalone" + }, + "render": "textArea", + "title": "Arbiter Custom Configuration", + "type": "string" + } + }, + "title": "Arbiter configuration", + "type": "object" + }, "architecture": { - "type": "string", - "title": "MongoDB® architecture", + "description": "Allowed values: `standalone` or `replicaset`", "form": true, - "description": "Allowed values: `standalone` or `replicaset`" + "title": "MongoDB® architecture", + "type": "string" }, "auth": { - "type": "object", - "title": "Authentication configuration", "form": true, "properties": { + "database": { + "description": "Name of the custom database to be created during the 1st initialization of MongoDB®", + "form": true, + "title": "MongoDB® custom database", + "type": "string" + }, "enabled": { - "type": "boolean", + "form": true, "title": "Enable Authentication", - "form": true + "type": "boolean" }, - "rootUser": { - "type": "string", - "title": "MongoDB® admin user", + "password": { + "description": "Defaults to a random 10-character alphanumeric string if not set", "form": true, - "description": "Name of the admin user. Default is root" + "hidden": { + "path": "auth/enabled", + "value": false + }, + "title": "Password for MongoDB® custom user", + "type": "string" }, - "rootPassword": { - "type": "string", - "title": "MongoDB® admin password", + "replicaSetKey": { + "description": "Defaults to a random 10-character alphanumeric string if not set", "form": true, + "hidden": { + "path": "architecture", + "value": "standalone" + }, + "title": "Key used for replica set authentication", + "type": "string" + }, + "rootPassword": { "description": "Defaults to a random 10-character alphanumeric string if not set", + "form": true, "hidden": { - "value": false, - "path": "auth/enabled" - } + "path": "auth/enabled", + "value": false + }, + "title": "MongoDB® admin password", + "type": "string" }, - "database": { - "type": "string", - "title": "MongoDB® custom database", - "description": "Name of the custom database to be created during the 1st initialization of MongoDB®", - "form": true + "rootUser": { + "description": "Name of the admin user. Default is root", + "form": true, + "title": "MongoDB® admin user", + "type": "string" }, "username": { - "type": "string", - "title": "MongoDB® custom user", "description": "Name of the custom user to be created during the 1st initialization of MongoDB®. This user only has permissions on the MongoDB® custom database", - "form": true - }, - "password": { - "type": "string", - "title": "Password for MongoDB® custom user", "form": true, - "description": "Defaults to a random 10-character alphanumeric string if not set", - "hidden": { - "value": false, - "path": "auth/enabled" - } - }, - "replicaSetKey": { - "type": "string", - "title": "Key used for replica set authentication", - "form": true, - "description": "Defaults to a random 10-character alphanumeric string if not set", - "hidden": { - "value": "standalone", - "path": "architecture" - } + "title": "MongoDB® custom user", + "type": "string" } - } - }, - "replicaCount": { - "type": "integer", - "form": true, - "title": "Number of MongoDB® replicas", - "hidden": { - "value": "standalone", - "path": "architecture" - } + }, + "title": "Authentication configuration", + "type": "object" }, "configuration": { - "type": "string", - "title": "MongoDB® Custom Configuration", "form": true, - "render": "textArea" + "render": "textArea", + "title": "MongoDB® Custom Configuration", + "type": "string" }, - "arbiter": { - "type": "object", - "title": "Arbiter configuration", + "metrics": { "form": true, "properties": { - "configuration": { - "type": "string", - "title": "Arbiter Custom Configuration", + "enabled": { + "description": "Create a side-car container to expose Prometheus metrics", "form": true, - "render": "textArea", - "hidden": { - "value": "standalone", - "path": "architecture" - } + "title": "Create Prometheus metrics exporter", + "type": "boolean" + }, + "serviceMonitor": { + "properties": { + "enabled": { + "description": "Create a ServiceMonitor to track metrics using Prometheus Operator", + "form": true, + "hidden": { + "path": "metrics/enabled", + "value": false + }, + "title": "Create Prometheus Operator ServiceMonitor", + "type": "boolean" + } + }, + "type": "object" } - } + }, + "title": "Prometheus metrics details", + "type": "object" }, "networkPolicy": { - "type": "object", - "title": "Network policy configuration", "form": true, "properties": { + "egress": { + "properties": { + "customRules": { + "hidden": { + "path": "networkPolicy/egress/customRules", + "value": [] + }, + "title": "Custom rules for egress network policy", + "type": "array" + } + }, + "type": "object" + }, "enabled": { - "type": "boolean", - "form": true, - "title": "Enable network policy", "description": "Enable network policy using Kubernetes native NP", + "form": true, "hidden": { - "value": false, - "path": "networkPolicy/enabled" - } + "path": "networkPolicy/enabled", + "value": false + }, + "title": "Enable network policy", + "type": "boolean" }, "ingress": { - "type": "object", "properties": { - "namespaceSelector": { - "type": "object", - "title": "Namespace selector label that is allowed to access this instance", + "customRules": { "hidden": { - "value": {}, - "path": "networkPolicy/ingress/namespaceSelector" - } + "path": "networkPolicy/ingress/customRules", + "value": [] + }, + "title": "Custom rules for ingress network policy", + "type": "array" }, - "podSelector": { - "type": "object", - "title": "Pod selector label that is allowed to access this instance", + "namespaceSelector": { "hidden": { - "value": {}, - "path": "networkPolicy/ingress/podSelector" - } + "path": "networkPolicy/ingress/namespaceSelector", + "value": {} + }, + "title": "Namespace selector label that is allowed to access this instance", + "type": "object" }, - "customRules": { - "type": "array", - "title": "Custom rules for ingress network policy", - "hidden": { - "value": [], - "path": "networkPolicy/ingress/customRules" - } - } - } - }, - "egress": { - "type": "object", - "properties": { - "customRules": { - "type": "array", - "title": "Custom rules for egress network policy", + "podSelector": { "hidden": { - "value": [], - "path": "networkPolicy/egress/customRules" - } + "path": "networkPolicy/ingress/podSelector", + "value": {} + }, + "title": "Pod selector label that is allowed to access this instance", + "type": "object" } - } + }, + "type": "object" } - } + }, + "title": "Network policy configuration", + "type": "object" }, "persistence": { - "type": "object", - "title": "Persistence configuration", "form": true, "properties": { "enabled": { - "type": "boolean", + "description": "Enable persistence using Persistent Volume Claims", "form": true, "title": "Enable persistence", - "description": "Enable persistence using Persistent Volume Claims" + "type": "boolean" }, "size": { - "type": "string", - "title": "Persistent Volume Size", "form": true, + "hidden": { + "path": "persistence/enabled", + "value": false + }, "render": "slider", - "sliderMin": 1, "sliderMax": 100, + "sliderMin": 1, "sliderUnit": "Gi", - "hidden": { - "value": false, - "path": "persistence/enabled" - } + "title": "Persistent Volume Size", + "type": "string" } - } + }, + "title": "Persistence configuration", + "type": "object" + }, + "replicaCount": { + "form": true, + "hidden": { + "path": "architecture", + "value": "standalone" + }, + "title": "Number of MongoDB® replicas", + "type": "integer" }, "volumePermissions": { - "type": "object", "hidden": { - "value": false, - "path": "persistence/enabled" + "path": "persistence/enabled", + "value": false }, "properties": { "enabled": { - "type": "boolean", + "description": "Use an init container to set required folder permissions on the data volume before mounting it in the final destination", "form": true, "title": "Enable Init Containers", - "description": "Use an init container to set required folder permissions on the data volume before mounting it in the final destination" + "type": "boolean" } - } - }, - "metrics": { - "type": "object", - "form": true, - "title": "Prometheus metrics details", - "properties": { - "enabled": { - "type": "boolean", - "title": "Create Prometheus metrics exporter", - "description": "Create a side-car container to expose Prometheus metrics", - "form": true - }, - "serviceMonitor": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean", - "title": "Create Prometheus Operator ServiceMonitor", - "description": "Create a ServiceMonitor to track metrics using Prometheus Operator", - "form": true, - "hidden": { - "value": false, - "path": "metrics/enabled" - } - } - } - } - } + }, + "type": "object" } - } + }, + "type": "object" } diff --git a/manifest/charts/rabbitmq/charts/common/templates/_compatibility.tpl b/manifest/charts/rabbitmq/charts/common/templates/_compatibility.tpl index a61588d..0fc2f22 100644 --- a/manifest/charts/rabbitmq/charts/common/templates/_compatibility.tpl +++ b/manifest/charts/rabbitmq/charts/common/templates/_compatibility.tpl @@ -5,7 +5,7 @@ SPDX-License-Identifier: APACHE-2.0 {{/* vim: set filetype=mustache: */}} -{{/* +{{/* Return true if the detected platform is Openshift Usage: {{- include "common.compatibility.isOpenshift" . -}} diff --git a/manifest/charts/rabbitmq/charts/common/templates/_errors.tpl b/manifest/charts/rabbitmq/charts/common/templates/_errors.tpl index 93f3ffc..95b8b8e 100644 --- a/manifest/charts/rabbitmq/charts/common/templates/_errors.tpl +++ b/manifest/charts/rabbitmq/charts/common/templates/_errors.tpl @@ -82,4 +82,4 @@ Usage: {{- end -}} {{- print $warnString -}} {{- end -}} -{{- end -}} \ No newline at end of file +{{- end -}} diff --git a/manifest/charts/rabbitmq/charts/common/templates/_images.tpl b/manifest/charts/rabbitmq/charts/common/templates/_images.tpl index 76bb7ce..6eedf75 100644 --- a/manifest/charts/rabbitmq/charts/common/templates/_images.tpl +++ b/manifest/charts/rabbitmq/charts/common/templates/_images.tpl @@ -112,4 +112,3 @@ Return the proper image version (ingores image revision/prerelease info & fallba {{- print .chart.AppVersion -}} {{- end -}} {{- end -}} - diff --git a/manifest/charts/rabbitmq/charts/common/templates/_resources.tpl b/manifest/charts/rabbitmq/charts/common/templates/_resources.tpl index d8a43e1..2aaf575 100644 --- a/manifest/charts/rabbitmq/charts/common/templates/_resources.tpl +++ b/manifest/charts/rabbitmq/charts/common/templates/_resources.tpl @@ -12,32 +12,32 @@ These presets are for basic testing and not meant to be used in production */}} {{- define "common.resources.preset" -}} {{/* The limits are the requests increased by 50% (except ephemeral-storage and xlarge/2xlarge sizes)*/}} -{{- $presets := dict - "nano" (dict +{{- $presets := dict + "nano" (dict "requests" (dict "cpu" "100m" "memory" "128Mi" "ephemeral-storage" "50Mi") "limits" (dict "cpu" "150m" "memory" "192Mi" "ephemeral-storage" "2Gi") ) - "micro" (dict + "micro" (dict "requests" (dict "cpu" "250m" "memory" "256Mi" "ephemeral-storage" "50Mi") "limits" (dict "cpu" "375m" "memory" "384Mi" "ephemeral-storage" "2Gi") ) - "small" (dict + "small" (dict "requests" (dict "cpu" "500m" "memory" "512Mi" "ephemeral-storage" "50Mi") "limits" (dict "cpu" "750m" "memory" "768Mi" "ephemeral-storage" "2Gi") ) - "medium" (dict + "medium" (dict "requests" (dict "cpu" "500m" "memory" "1024Mi" "ephemeral-storage" "50Mi") "limits" (dict "cpu" "750m" "memory" "1536Mi" "ephemeral-storage" "2Gi") ) - "large" (dict + "large" (dict "requests" (dict "cpu" "1.0" "memory" "2048Mi" "ephemeral-storage" "50Mi") "limits" (dict "cpu" "1.5" "memory" "3072Mi" "ephemeral-storage" "2Gi") ) - "xlarge" (dict + "xlarge" (dict "requests" (dict "cpu" "1.0" "memory" "3072Mi" "ephemeral-storage" "50Mi") "limits" (dict "cpu" "3.0" "memory" "6144Mi" "ephemeral-storage" "2Gi") ) - "2xlarge" (dict + "2xlarge" (dict "requests" (dict "cpu" "1.0" "memory" "3072Mi" "ephemeral-storage" "50Mi") "limits" (dict "cpu" "6.0" "memory" "12288Mi" "ephemeral-storage" "2Gi") ) diff --git a/manifest/charts/rabbitmq/charts/common/templates/_utils.tpl b/manifest/charts/rabbitmq/charts/common/templates/_utils.tpl index d53c74a..0a5a5bc 100644 --- a/manifest/charts/rabbitmq/charts/common/templates/_utils.tpl +++ b/manifest/charts/rabbitmq/charts/common/templates/_utils.tpl @@ -46,7 +46,7 @@ Usage: {{- $value = ( index $latestObj . ) -}} {{- $latestObj = $value -}} {{- end -}} -{{- printf "%v" (default "" $value) -}} +{{- printf "%v" (default "" $value) -}} {{- end -}} {{/* @@ -63,7 +63,7 @@ Usage: {{- $key = . }} {{- end -}} {{- end -}} -{{- printf "%s" $key -}} +{{- printf "%s" $key -}} {{- end -}} {{/* diff --git a/manifest/charts/rabbitmq/templates/config-secret.yaml b/manifest/charts/rabbitmq/templates/config-secret.yaml index 8b816ab..c234fed 100644 --- a/manifest/charts/rabbitmq/templates/config-secret.yaml +++ b/manifest/charts/rabbitmq/templates/config-secret.yaml @@ -18,7 +18,7 @@ data: {{- if empty .Values.configurationExistingSecret }} rabbitmq.conf: |- {{- include "common.tplvalues.render" (dict "value" .Values.configuration "context" $) | b64enc | nindent 4 }} - {{- end }} + {{- end }} {{- if .Values.advancedConfiguration }} advanced.config: |- {{- include "common.tplvalues.render" (dict "value" .Values.advancedConfiguration "context" $) | b64enc | nindent 4 }} diff --git a/manifest/charts/rabbitmq/templates/serviceaccount.yaml b/manifest/charts/rabbitmq/templates/serviceaccount.yaml index b0348ee..72063d8 100644 --- a/manifest/charts/rabbitmq/templates/serviceaccount.yaml +++ b/manifest/charts/rabbitmq/templates/serviceaccount.yaml @@ -18,4 +18,3 @@ automountServiceAccountToken: {{ .Values.serviceAccount.automountServiceAccountT secrets: - name: {{ template "rabbitmq.secretPasswordName" . }} {{- end }} - diff --git a/manifest/charts/rabbitmq/templates/statefulset.yaml b/manifest/charts/rabbitmq/templates/statefulset.yaml index e56f6b3..a5487d7 100644 --- a/manifest/charts/rabbitmq/templates/statefulset.yaml +++ b/manifest/charts/rabbitmq/templates/statefulset.yaml @@ -417,10 +417,10 @@ spec: - secret: name: {{ template "rabbitmq.tlsSecretName" . }} items: - {{- if not .Values.auth.tls.overrideCaCertificate }} + {{- if not .Values.auth.tls.overrideCaCertificate }} - key: {{ ternary "tls.crt" "ca.crt" .Values.auth.tls.existingSecretFullChain }} path: ca_certificate.pem - {{- end }} + {{- end }} - key: tls.crt path: server_certificate.pem - key: tls.key @@ -444,7 +444,7 @@ spec: {{- if or (and (empty .Values.configurationExistingSecret) .Values.configuration) (and (not .Values.advancedConfigurationExistingSecret) .Values.advancedConfiguration) }} - secret: name: {{ printf "%s-config" (include "common.names.fullname" .) }} - {{- end }} + {{- end }} {{- if and .Values.advancedConfigurationExistingSecret (not .Values.advancedConfiguration) }} - secret: name: {{ tpl .Values.advancedConfigurationExistingSecret . | quote }} @@ -486,7 +486,7 @@ spec: {{- with .Values.persistence.existingClaim }} claimName: {{ tpl . $ }} {{- end }} - {{- else }} + {{- else }} {{- if .Values.persistentVolumeClaimRetentionPolicy.enabled }} persistentVolumeClaimRetentionPolicy: whenDeleted: {{ .Values.persistentVolumeClaimRetentionPolicy.whenDeleted }} diff --git a/manifest/charts/rabbitmq/templates/validation.yaml b/manifest/charts/rabbitmq/templates/validation.yaml index ecf3cab..c82160d 100644 --- a/manifest/charts/rabbitmq/templates/validation.yaml +++ b/manifest/charts/rabbitmq/templates/validation.yaml @@ -4,4 +4,3 @@ SPDX-License-Identifier: APACHE-2.0 */}} {{- include "rabbitmq.validateValues" . }} - diff --git a/manifest/charts/rabbitmq/values.schema.json b/manifest/charts/rabbitmq/values.schema.json index 8ef33ef..a6d0002 100644 --- a/manifest/charts/rabbitmq/values.schema.json +++ b/manifest/charts/rabbitmq/values.schema.json @@ -1,100 +1,100 @@ { "$schema": "http://json-schema.org/schema#", - "type": "object", "properties": { "auth": { - "type": "object", "properties": { - "username": { - "type": "string", - "title": "RabbitMQ user", - "form": true - }, "password": { - "type": "string", + "description": "Defaults to a random 10-character alphanumeric string if not set", + "form": true, "title": "RabbitMQ password", + "type": "string" + }, + "username": { "form": true, - "description": "Defaults to a random 10-character alphanumeric string if not set" + "title": "RabbitMQ user", + "type": "string" } - } + }, + "type": "object" }, "extraConfiguration": { - "type": "string", - "title": "Extra RabbitMQ Configuration", + "description": "Extra configuration to be appended to RabbitMQ Configuration", "form": true, "render": "textArea", - "description": "Extra configuration to be appended to RabbitMQ Configuration" + "title": "Extra RabbitMQ Configuration", + "type": "string" }, - "replicaCount": { - "type": "integer", + "metrics": { "form": true, - "title": "Number of replicas", - "description": "Number of replicas to deploy" + "properties": { + "enabled": { + "description": "Install Prometheus plugin in the RabbitMQ container", + "form": true, + "title": "Enable Prometheus metrics for RabbitMQ", + "type": "boolean" + }, + "serviceMonitor": { + "properties": { + "enabled": { + "description": "Create a ServiceMonitor to track metrics using Prometheus Operator", + "form": true, + "hidden": { + "path": "metrics/enabled", + "value": false + }, + "title": "Create Prometheus Operator ServiceMonitor", + "type": "boolean" + } + }, + "type": "object" + } + }, + "title": "Prometheus metrics details", + "type": "object" }, "persistence": { - "type": "object", - "title": "Persistence configuration", "form": true, "properties": { "enabled": { - "type": "boolean", + "description": "Enable persistence using Persistent Volume Claims", "form": true, "title": "Enable persistence", - "description": "Enable persistence using Persistent Volume Claims" + "type": "boolean" }, "size": { - "type": "string", - "title": "Persistent Volume Size", "form": true, + "hidden": { + "path": "persistence/enabled", + "value": false + }, "render": "slider", - "sliderMin": 1, "sliderMax": 100, + "sliderMin": 1, "sliderUnit": "Gi", - "hidden": { - "value": false, - "path": "persistence/enabled" - } + "title": "Persistent Volume Size", + "type": "string" } - } + }, + "title": "Persistence configuration", + "type": "object" + }, + "replicaCount": { + "description": "Number of replicas to deploy", + "form": true, + "title": "Number of replicas", + "type": "integer" }, "volumePermissions": { - "type": "object", "properties": { "enabled": { - "type": "boolean", + "description": "Use an init container to set required folder permissions on the data volume before mounting it in the final destination", "form": true, "title": "Enable Init Containers", - "description": "Use an init container to set required folder permissions on the data volume before mounting it in the final destination" - } - } - }, - "metrics": { - "type": "object", - "form": true, - "title": "Prometheus metrics details", - "properties": { - "enabled": { - "type": "boolean", - "title": "Enable Prometheus metrics for RabbitMQ", - "description": "Install Prometheus plugin in the RabbitMQ container", - "form": true - }, - "serviceMonitor": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean", - "title": "Create Prometheus Operator ServiceMonitor", - "description": "Create a ServiceMonitor to track metrics using Prometheus Operator", - "form": true, - "hidden": { - "value": false, - "path": "metrics/enabled" - } - } - } + "type": "boolean" } - } + }, + "type": "object" } - } + }, + "type": "object" } diff --git a/manifest/charts/vault/CONTRIBUTING.md b/manifest/charts/vault/CONTRIBUTING.md index ad31ac9..95d907c 100644 --- a/manifest/charts/vault/CONTRIBUTING.md +++ b/manifest/charts/vault/CONTRIBUTING.md @@ -85,7 +85,7 @@ Next, execute the tests with the following commands: ```shell docker run -it --rm -v "${PWD}:/test" vault-helm-test bats /test/test/unit ``` -It's possible to only run specific bats tests using regular expressions. +It's possible to only run specific bats tests using regular expressions. For example, the following will run only tests with "injector" in the name: ```shell docker run -it --rm -v "${PWD}:/test" vault-helm-test bats /test/test/unit -f "injector" diff --git a/manifest/charts/vault/README.md b/manifest/charts/vault/README.md index 6e70143..8dca4c4 100644 --- a/manifest/charts/vault/README.md +++ b/manifest/charts/vault/README.md @@ -1,7 +1,7 @@ # Vault Helm Chart -> :warning: **Please note**: We take Vault's security and our users' trust very seriously. If -you believe you have found a security issue in Vault Helm, _please responsibly disclose_ +> :warning: **Please note**: We take Vault's security and our users' trust very seriously. If +you believe you have found a security issue in Vault Helm, _please responsibly disclose_ by contacting us at [security@hashicorp.com](mailto:security@hashicorp.com). This repository contains the official HashiCorp Helm chart for installing diff --git a/manifest/charts/vault/templates/NOTES.txt b/manifest/charts/vault/templates/NOTES.txt index 8e26712..4948e4c 100644 --- a/manifest/charts/vault/templates/NOTES.txt +++ b/manifest/charts/vault/templates/NOTES.txt @@ -11,4 +11,3 @@ Your release is named {{ .Release.Name }}. To learn more about the release, try: $ helm status {{ .Release.Name }} $ helm get manifest {{ .Release.Name }} - diff --git a/manifest/charts/vault/templates/injector-certs-secret.yaml b/manifest/charts/vault/templates/injector-certs-secret.yaml index 3e5ddb7..fabfcd9 100644 --- a/manifest/charts/vault/templates/injector-certs-secret.yaml +++ b/manifest/charts/vault/templates/injector-certs-secret.yaml @@ -16,4 +16,4 @@ metadata: app.kubernetes.io/instance: {{ .Release.Name }} app.kubernetes.io/managed-by: {{ .Release.Service }} {{- end }} -{{- end }} \ No newline at end of file +{{- end }} diff --git a/manifest/charts/vault/templates/injector-psp-rolebinding.yaml b/manifest/charts/vault/templates/injector-psp-rolebinding.yaml index 48a3a26..4661996 100644 --- a/manifest/charts/vault/templates/injector-psp-rolebinding.yaml +++ b/manifest/charts/vault/templates/injector-psp-rolebinding.yaml @@ -23,4 +23,4 @@ subjects: - kind: ServiceAccount name: {{ template "vault.fullname" . }}-agent-injector {{- end }} -{{- end }} \ No newline at end of file +{{- end }} diff --git a/manifest/charts/vault/templates/injector-psp.yaml b/manifest/charts/vault/templates/injector-psp.yaml index 0eca9a8..a45307e 100644 --- a/manifest/charts/vault/templates/injector-psp.yaml +++ b/manifest/charts/vault/templates/injector-psp.yaml @@ -48,4 +48,4 @@ spec: max: 65535 readOnlyRootFilesystem: false {{- end }} -{{- end }} \ No newline at end of file +{{- end }} diff --git a/manifest/charts/vault/templates/injector-role.yaml b/manifest/charts/vault/templates/injector-role.yaml index df7b0ed..7c4d0a0 100644 --- a/manifest/charts/vault/templates/injector-role.yaml +++ b/manifest/charts/vault/templates/injector-role.yaml @@ -31,4 +31,4 @@ rules: - "patch" - "delete" {{- end }} -{{- end }} \ No newline at end of file +{{- end }} diff --git a/manifest/charts/vault/templates/injector-rolebinding.yaml b/manifest/charts/vault/templates/injector-rolebinding.yaml index 0848e43..823a9af 100644 --- a/manifest/charts/vault/templates/injector-rolebinding.yaml +++ b/manifest/charts/vault/templates/injector-rolebinding.yaml @@ -24,4 +24,4 @@ subjects: name: {{ template "vault.fullname" . }}-agent-injector namespace: {{ .Release.Namespace }} {{- end }} -{{- end }} \ No newline at end of file +{{- end }} diff --git a/manifest/charts/vault/templates/server-clusterrolebinding.yaml b/manifest/charts/vault/templates/server-clusterrolebinding.yaml index b694129..24288df 100644 --- a/manifest/charts/vault/templates/server-clusterrolebinding.yaml +++ b/manifest/charts/vault/templates/server-clusterrolebinding.yaml @@ -26,4 +26,4 @@ subjects: - kind: ServiceAccount name: {{ template "vault.serviceAccount.name" . }} namespace: {{ .Release.Namespace }} -{{ end }} \ No newline at end of file +{{ end }} diff --git a/manifest/charts/vault/values.schema.json b/manifest/charts/vault/values.schema.json index ecb97de..a156478 100644 --- a/manifest/charts/vault/values.schema.json +++ b/manifest/charts/vault/values.schema.json @@ -1,1144 +1,1144 @@ { - "$schema": "http://json-schema.org/schema#", - "type": "object", - "properties": { - "csi": { - "type": "object", - "properties": { - "agent": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean" - }, - "extraArgs": { - "type": "array" - }, - "image": { - "type": "object", - "properties": { - "pullPolicy": { - "type": "string" - }, - "repository": { - "type": "string" - }, - "tag": { - "type": "string" - } - } - }, - "logFormat": { - "type": "string" - }, - "logLevel": { - "type": "string" - }, - "resources": { - "type": "object" - } - } - }, - "daemonSet": { - "type": "object", - "properties": { - "annotations": { - "type": [ - "object", - "string" - ] - }, - "extraLabels": { - "type": "object" - }, - "kubeletRootDir": { - "type": "string" - }, - "providersDir": { - "type": "string" - }, - "securityContext": { - "type": "object", - "properties": { - "container": { - "type": [ - "object", - "string" - ] - }, - "pod": { - "type": [ - "object", - "string" - ] - } - } - }, - "updateStrategy": { - "type": "object", - "properties": { - "maxUnavailable": { - "type": "string" - }, - "type": { - "type": "string" - } - } - } - } - }, - "debug": { - "type": "boolean" - }, - "enabled": { - "type": [ - "boolean", - "string" - ] - }, - "extraArgs": { - "type": "array" - }, - "image": { - "type": "object", - "properties": { - "pullPolicy": { - "type": "string" - }, - "repository": { - "type": "string" - }, - "tag": { - "type": "string" - } - } - }, - "livenessProbe": { - "type": "object", - "properties": { - "failureThreshold": { - "type": "integer" - }, - "initialDelaySeconds": { - "type": "integer" - }, - "periodSeconds": { - "type": "integer" - }, - "successThreshold": { - "type": "integer" - }, - "timeoutSeconds": { - "type": "integer" - } - } + "$schema": "http://json-schema.org/schema#", + "properties": { + "csi": { + "properties": { + "agent": { + "properties": { + "enabled": { + "type": "boolean" + }, + "extraArgs": { + "type": "array" + }, + "image": { + "properties": { + "pullPolicy": { + "type": "string" + }, + "repository": { + "type": "string" + }, + "tag": { + "type": "string" + } + }, + "type": "object" + }, + "logFormat": { + "type": "string" + }, + "logLevel": { + "type": "string" + }, + "resources": { + "type": "object" + } + }, + "type": "object" + }, + "daemonSet": { + "properties": { + "annotations": { + "type": [ + "object", + "string" + ] + }, + "extraLabels": { + "type": "object" + }, + "kubeletRootDir": { + "type": "string" + }, + "providersDir": { + "type": "string" + }, + "securityContext": { + "properties": { + "container": { + "type": [ + "object", + "string" + ] }, "pod": { - "type": "object", - "properties": { - "affinity": { - "type": [ - "null", - "object", - "string" - ] - }, - "annotations": { - "type": [ - "object", - "string" - ] - }, - "extraLabels": { - "type": "object" - }, - "nodeSelector": { - "type": [ - "null", - "object", - "string" - ] - }, - "tolerations": { - "type": [ - "null", - "array", - "string" - ] - } - } - }, - "priorityClassName": { - "type": "string" - }, - "readinessProbe": { - "type": "object", - "properties": { - "failureThreshold": { - "type": "integer" - }, - "initialDelaySeconds": { - "type": "integer" - }, - "periodSeconds": { - "type": "integer" - }, - "successThreshold": { - "type": "integer" - }, - "timeoutSeconds": { - "type": "integer" - } - } - }, - "resources": { - "type": "object" - }, - "serviceAccount": { - "type": "object", - "properties": { - "annotations": { - "type": [ - "object", - "string" - ] - }, - "extraLabels": { - "type": "object" - } - } - }, - "volumeMounts": { - "type": [ - "null", - "array" - ] - }, - "volumes": { - "type": [ - "null", - "array" - ] + "type": [ + "object", + "string" + ] + } + }, + "type": "object" + }, + "updateStrategy": { + "properties": { + "maxUnavailable": { + "type": "string" + }, + "type": { + "type": "string" } + }, + "type": "object" } + }, + "type": "object" }, - "global": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean" - }, - "externalVaultAddr": { - "type": "string" - }, - "imagePullSecrets": { - "type": "array" - }, - "openshift": { - "type": "boolean" - }, - "psp": { - "type": "object", - "properties": { - "annotations": { - "type": [ - "object", - "string" - ] - }, - "enable": { - "type": "boolean" - } - } - }, - "tlsDisable": { - "type": "boolean" + "debug": { + "type": "boolean" + }, + "enabled": { + "type": [ + "boolean", + "string" + ] + }, + "extraArgs": { + "type": "array" + }, + "image": { + "properties": { + "pullPolicy": { + "type": "string" + }, + "repository": { + "type": "string" + }, + "tag": { + "type": "string" + } + }, + "type": "object" + }, + "livenessProbe": { + "properties": { + "failureThreshold": { + "type": "integer" + }, + "initialDelaySeconds": { + "type": "integer" + }, + "periodSeconds": { + "type": "integer" + }, + "successThreshold": { + "type": "integer" + }, + "timeoutSeconds": { + "type": "integer" + } + }, + "type": "object" + }, + "pod": { + "properties": { + "affinity": { + "type": [ + "null", + "object", + "string" + ] + }, + "annotations": { + "type": [ + "object", + "string" + ] + }, + "extraLabels": { + "type": "object" + }, + "nodeSelector": { + "type": [ + "null", + "object", + "string" + ] + }, + "tolerations": { + "type": [ + "null", + "array", + "string" + ] + } + }, + "type": "object" + }, + "priorityClassName": { + "type": "string" + }, + "readinessProbe": { + "properties": { + "failureThreshold": { + "type": "integer" + }, + "initialDelaySeconds": { + "type": "integer" + }, + "periodSeconds": { + "type": "integer" + }, + "successThreshold": { + "type": "integer" + }, + "timeoutSeconds": { + "type": "integer" + } + }, + "type": "object" + }, + "resources": { + "type": "object" + }, + "serviceAccount": { + "properties": { + "annotations": { + "type": [ + "object", + "string" + ] + }, + "extraLabels": { + "type": "object" + } + }, + "type": "object" + }, + "volumeMounts": { + "type": [ + "null", + "array" + ] + }, + "volumes": { + "type": [ + "null", + "array" + ] + } + }, + "type": "object" + }, + "global": { + "properties": { + "enabled": { + "type": "boolean" + }, + "externalVaultAddr": { + "type": "string" + }, + "imagePullSecrets": { + "type": "array" + }, + "openshift": { + "type": "boolean" + }, + "psp": { + "properties": { + "annotations": { + "type": [ + "object", + "string" + ] + }, + "enable": { + "type": "boolean" + } + }, + "type": "object" + }, + "tlsDisable": { + "type": "boolean" + } + }, + "type": "object" + }, + "injector": { + "properties": { + "affinity": { + "type": [ + "object", + "string" + ] + }, + "agentDefaults": { + "properties": { + "cpuLimit": { + "type": "string" + }, + "cpuRequest": { + "type": "string" + }, + "ephemeralLimit": { + "type": "string" + }, + "ephemeralRequest": { + "type": "string" + }, + "memLimit": { + "type": "string" + }, + "memRequest": { + "type": "string" + }, + "template": { + "type": "string" + }, + "templateConfig": { + "properties": { + "exitOnRetryFailure": { + "type": "boolean" + }, + "staticSecretRenderInterval": { + "type": "string" } + }, + "type": "object" } + }, + "type": "object" }, - "injector": { - "type": "object", - "properties": { - "affinity": { - "type": [ - "object", - "string" - ] - }, - "agentDefaults": { - "type": "object", - "properties": { - "cpuLimit": { - "type": "string" - }, - "cpuRequest": { - "type": "string" - }, - "memLimit": { - "type": "string" - }, - "memRequest": { - "type": "string" - }, - "ephemeralLimit": { - "type": "string" - }, - "ephemeralRequest": { - "type": "string" - }, - "template": { - "type": "string" - }, - "templateConfig": { - "type": "object", - "properties": { - "exitOnRetryFailure": { - "type": "boolean" - }, - "staticSecretRenderInterval": { - "type": "string" - } - } - } - } - }, - "agentImage": { - "type": "object", - "properties": { - "repository": { - "type": "string" - }, - "tag": { - "type": "string" - } - } - }, - "annotations": { - "type": [ - "object", - "string" - ] - }, - "authPath": { - "type": "string" - }, - "certs": { - "type": "object", - "properties": { - "caBundle": { - "type": "string" - }, - "certName": { - "type": "string" - }, - "keyName": { - "type": "string" - }, - "secretName": { - "type": [ - "null", - "string" - ] - } - } - }, + "agentImage": { + "properties": { + "repository": { + "type": "string" + }, + "tag": { + "type": "string" + } + }, + "type": "object" + }, + "annotations": { + "type": [ + "object", + "string" + ] + }, + "authPath": { + "type": "string" + }, + "certs": { + "properties": { + "caBundle": { + "type": "string" + }, + "certName": { + "type": "string" + }, + "keyName": { + "type": "string" + }, + "secretName": { + "type": [ + "null", + "string" + ] + } + }, + "type": "object" + }, + "enabled": { + "type": [ + "boolean", + "string" + ] + }, + "externalVaultAddr": { + "type": "string" + }, + "extraEnvironmentVars": { + "type": "object" + }, + "extraLabels": { + "type": "object" + }, + "failurePolicy": { + "type": "string" + }, + "hostNetwork": { + "type": "boolean" + }, + "image": { + "properties": { + "pullPolicy": { + "type": "string" + }, + "repository": { + "type": "string" + }, + "tag": { + "type": "string" + } + }, + "type": "object" + }, + "leaderElector": { + "properties": { + "enabled": { + "type": "boolean" + } + }, + "type": "object" + }, + "logFormat": { + "type": "string" + }, + "logLevel": { + "type": "string" + }, + "metrics": { + "properties": { + "enabled": { + "type": "boolean" + } + }, + "type": "object" + }, + "namespaceSelector": { + "type": "object" + }, + "nodeSelector": { + "type": [ + "null", + "object", + "string" + ] + }, + "objectSelector": { + "type": [ + "object", + "string" + ] + }, + "podDisruptionBudget": { + "type": "object" + }, + "port": { + "type": "integer" + }, + "priorityClassName": { + "type": "string" + }, + "replicas": { + "type": "integer" + }, + "resources": { + "type": "object" + }, + "revokeOnShutdown": { + "type": "boolean" + }, + "securityContext": { + "properties": { + "container": { + "type": [ + "object", + "string" + ] + }, + "pod": { + "type": [ + "object", + "string" + ] + } + }, + "type": "object" + }, + "service": { + "properties": { + "annotations": { + "type": [ + "object", + "string" + ] + } + }, + "type": "object" + }, + "serviceAccount": { + "properties": { + "annotations": { + "type": [ + "object", + "string" + ] + } + }, + "type": "object" + }, + "strategy": { + "type": [ + "object", + "string" + ] + }, + "tolerations": { + "type": [ + "null", + "array", + "string" + ] + }, + "topologySpreadConstraints": { + "type": [ + "null", + "array", + "string" + ] + }, + "webhook": { + "properties": { + "annotations": { + "type": [ + "object", + "string" + ] + }, + "failurePolicy": { + "type": "string" + }, + "matchPolicy": { + "type": "string" + }, + "namespaceSelector": { + "type": "object" + }, + "objectSelector": { + "type": [ + "object", + "string" + ] + }, + "timeoutSeconds": { + "type": "integer" + } + }, + "type": "object" + }, + "webhookAnnotations": { + "type": [ + "object", + "string" + ] + } + }, + "type": "object" + }, + "server": { + "properties": { + "affinity": { + "type": [ + "object", + "string" + ] + }, + "annotations": { + "type": [ + "object", + "string" + ] + }, + "auditStorage": { + "properties": { + "accessMode": { + "type": "string" + }, + "annotations": { + "type": [ + "object", + "string" + ] + }, + "enabled": { + "type": [ + "boolean", + "string" + ] + }, + "mountPath": { + "type": "string" + }, + "size": { + "type": "string" + }, + "storageClass": { + "type": [ + "null", + "string" + ] + } + }, + "type": "object" + }, + "authDelegator": { + "properties": { + "enabled": { + "type": "boolean" + } + }, + "type": "object" + }, + "dataStorage": { + "properties": { + "accessMode": { + "type": "string" + }, + "annotations": { + "type": [ + "object", + "string" + ] + }, + "enabled": { + "type": [ + "boolean", + "string" + ] + }, + "mountPath": { + "type": "string" + }, + "size": { + "type": "string" + }, + "storageClass": { + "type": [ + "null", + "string" + ] + } + }, + "type": "object" + }, + "dev": { + "properties": { + "devRootToken": { + "type": "string" + }, + "enabled": { + "type": "boolean" + } + }, + "type": "object" + }, + "enabled": { + "type": [ + "boolean", + "string" + ] + }, + "enterpriseLicense": { + "properties": { + "secretKey": { + "type": "string" + }, + "secretName": { + "type": "string" + } + }, + "type": "object" + }, + "extraArgs": { + "type": "string" + }, + "extraContainers": { + "type": [ + "null", + "array" + ] + }, + "extraEnvironmentVars": { + "type": "object" + }, + "extraInitContainers": { + "type": [ + "null", + "array" + ] + }, + "extraLabels": { + "type": "object" + }, + "extraPorts": { + "type": [ + "null", + "array" + ] + }, + "extraSecretEnvironmentVars": { + "type": "array" + }, + "extraVolumes": { + "type": "array" + }, + "ha": { + "properties": { + "apiAddr": { + "type": [ + "null", + "string" + ] + }, + "clusterAddr": { + "type": [ + "null", + "string" + ] + }, + "config": { + "type": [ + "string", + "object" + ] + }, + "disruptionBudget": { + "properties": { "enabled": { - "type": [ - "boolean", - "string" - ] - }, - "externalVaultAddr": { - "type": "string" - }, - "extraEnvironmentVars": { - "type": "object" - }, - "extraLabels": { - "type": "object" - }, - "failurePolicy": { - "type": "string" - }, - "hostNetwork": { - "type": "boolean" - }, - "image": { - "type": "object", - "properties": { - "pullPolicy": { - "type": "string" - }, - "repository": { - "type": "string" - }, - "tag": { - "type": "string" - } - } + "type": "boolean" }, - "leaderElector": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean" - } - } - }, - "logFormat": { - "type": "string" - }, - "logLevel": { - "type": "string" - }, - "metrics": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean" - } - } - }, - "namespaceSelector": { - "type": "object" - }, - "nodeSelector": { - "type": [ - "null", - "object", - "string" - ] - }, - "objectSelector": { - "type": [ - "object", - "string" - ] - }, - "podDisruptionBudget": { - "type": "object" - }, - "port": { - "type": "integer" - }, - "priorityClassName": { - "type": "string" - }, - "replicas": { - "type": "integer" - }, - "resources": { - "type": "object" - }, - "revokeOnShutdown": { - "type": "boolean" - }, - "securityContext": { - "type": "object", - "properties": { - "container": { - "type": [ - "object", - "string" - ] - }, - "pod": { - "type": [ - "object", - "string" - ] - } - } - }, - "service": { - "type": "object", - "properties": { - "annotations": { - "type": [ - "object", - "string" - ] - } - } - }, - "serviceAccount": { - "type": "object", - "properties": { - "annotations": { - "type": [ - "object", - "string" - ] - } - } - }, - "strategy": { - "type": [ - "object", - "string" - ] - }, - "tolerations": { - "type": [ - "null", - "array", - "string" - ] - }, - "topologySpreadConstraints": { - "type": [ - "null", - "array", - "string" - ] + "maxUnavailable": { + "type": [ + "null", + "integer" + ] + } + }, + "type": "object" + }, + "enabled": { + "type": "boolean" + }, + "raft": { + "properties": { + "config": { + "type": [ + "string", + "object" + ] }, - "webhook": { - "type": "object", - "properties": { - "annotations": { - "type": [ - "object", - "string" - ] - }, - "failurePolicy": { - "type": "string" - }, - "matchPolicy": { - "type": "string" - }, - "namespaceSelector": { - "type": "object" - }, - "objectSelector": { - "type": [ - "object", - "string" - ] - }, - "timeoutSeconds": { - "type": "integer" - } - } + "enabled": { + "type": "boolean" }, - "webhookAnnotations": { - "type": [ - "object", - "string" - ] + "setNodeId": { + "type": "boolean" } + }, + "type": "object" + }, + "replicas": { + "type": "integer" } + }, + "type": "object" }, - "server": { - "type": "object", - "properties": { - "affinity": { - "type": [ - "object", - "string" - ] - }, - "annotations": { - "type": [ - "object", - "string" - ] - }, - "auditStorage": { - "type": "object", - "properties": { - "accessMode": { - "type": "string" - }, - "annotations": { - "type": [ - "object", - "string" - ] - }, - "enabled": { - "type": [ - "boolean", - "string" - ] - }, - "mountPath": { - "type": "string" - }, - "size": { - "type": "string" - }, - "storageClass": { - "type": [ - "null", - "string" - ] - } - } - }, - "authDelegator": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean" - } - } - }, - "dataStorage": { - "type": "object", - "properties": { - "accessMode": { - "type": "string" - }, - "annotations": { - "type": [ - "object", - "string" - ] - }, - "enabled": { - "type": [ - "boolean", - "string" - ] - }, - "mountPath": { - "type": "string" - }, - "size": { - "type": "string" - }, - "storageClass": { - "type": [ - "null", - "string" - ] - } - } - }, - "dev": { - "type": "object", - "properties": { - "devRootToken": { - "type": "string" - }, - "enabled": { - "type": "boolean" - } - } - }, - "enabled": { - "type": [ - "boolean", - "string" - ] - }, - "enterpriseLicense": { - "type": "object", - "properties": { - "secretKey": { - "type": "string" - }, - "secretName": { - "type": "string" - } - } - }, - "extraArgs": { - "type": "string" - }, - "extraPorts": { - "type": [ - "null", - "array" - ] - }, - "extraContainers": { - "type": [ - "null", - "array" - ] - }, - "extraEnvironmentVars": { - "type": "object" - }, - "extraInitContainers": { - "type": [ - "null", - "array" - ] - }, - "extraLabels": { - "type": "object" - }, - "extraSecretEnvironmentVars": { - "type": "array" - }, - "extraVolumes": { - "type": "array" - }, - "ha": { - "type": "object", - "properties": { - "apiAddr": { - "type": [ - "null", - "string" - ] - }, - "clusterAddr": { - "type": [ - "null", - "string" - ] - }, - "config": { - "type": [ - "string", - "object" - ] - }, - "disruptionBudget": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean" - }, - "maxUnavailable": { - "type": [ - "null", - "integer" - ] - } - } - }, - "enabled": { - "type": "boolean" - }, - "raft": { - "type": "object", - "properties": { - "config": { - "type": [ - "string", - "object" - ] - }, - "enabled": { - "type": "boolean" - }, - "setNodeId": { - "type": "boolean" - } - } - }, - "replicas": { - "type": "integer" - } - } - }, - "image": { - "type": "object", - "properties": { - "pullPolicy": { - "type": "string" - }, - "repository": { - "type": "string" - }, - "tag": { - "type": "string" - } - } - }, - "ingress": { - "type": "object", - "properties": { - "activeService": { - "type": "boolean" - }, - "annotations": { - "type": [ - "object", - "string" - ] - }, - "enabled": { - "type": "boolean" - }, - "extraPaths": { - "type": "array" - }, - "hosts": { - "type": "array", - "items": { - "type": "object", - "properties": { - "host": { - "type": "string" - }, - "paths": { - "type": "array" - } - } - } - }, - "ingressClassName": { - "type": "string" - }, - "labels": { - "type": "object" - }, - "pathType": { - "type": "string" - }, - "tls": { - "type": "array" - } - } - }, - "livenessProbe": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean" - }, - "failureThreshold": { - "type": "integer" - }, - "initialDelaySeconds": { - "type": "integer" - }, - "path": { - "type": "string" - }, - "periodSeconds": { - "type": "integer" - }, - "successThreshold": { - "type": "integer" - }, - "timeoutSeconds": { - "type": "integer" - } - } - }, - "logFormat": { - "type": "string" - }, - "logLevel": { + "hostNetwork": { + "type": "boolean" + }, + "image": { + "properties": { + "pullPolicy": { + "type": "string" + }, + "repository": { + "type": "string" + }, + "tag": { + "type": "string" + } + }, + "type": "object" + }, + "ingress": { + "properties": { + "activeService": { + "type": "boolean" + }, + "annotations": { + "type": [ + "object", + "string" + ] + }, + "enabled": { + "type": "boolean" + }, + "extraPaths": { + "type": "array" + }, + "hosts": { + "items": { + "properties": { + "host": { "type": "string" - }, - "networkPolicy": { - "type": "object", - "properties": { - "egress": { - "type": "array" - }, - "enabled": { - "type": "boolean" - } - } - }, - "nodeSelector": { - "type": [ - "null", - "object", - "string" - ] - }, - "postStart": { + }, + "paths": { "type": "array" - }, - "preStopSleepSeconds": { - "type": "integer" - }, - "priorityClassName": { - "type": "string" - }, - "readinessProbe": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean" - }, - "failureThreshold": { - "type": "integer" - }, - "initialDelaySeconds": { - "type": "integer" - }, - "periodSeconds": { - "type": "integer" - }, - "successThreshold": { - "type": "integer" - }, - "timeoutSeconds": { - "type": "integer" - } - } - }, - "resources": { - "type": "object" - }, - "route": { - "type": "object", - "properties": { - "activeService": { - "type": "boolean" - }, - "annotations": { - "type": [ - "object", - "string" - ] - }, - "enabled": { - "type": "boolean" - }, - "host": { - "type": "string" - }, - "labels": { - "type": "object" - }, - "tls": { - "type": "object" - } - } - }, - "service": { - "type": "object", - "properties": { - "active": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean" - } - } - }, - "annotations": { - "type": [ - "object", - "string" - ] - }, - "enabled": { - "type": "boolean" - }, - "externalTrafficPolicy": { - "type": "string" - }, - "instanceSelector": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean" - } - } - }, - "port": { - "type": "integer" - }, - "publishNotReadyAddresses": { - "type": "boolean" - }, - "standby": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean" - } - } - }, - "targetPort": { - "type": "integer" - }, - "nodePort": { - "type": "integer" - }, - "activeNodePort": { - "type": "integer" - }, - "standbyNodePort": { - "type": "integer" - } - } - }, - "serviceAccount": { - "type": "object", - "properties": { - "annotations": { - "type": [ - "object", - "string" - ] - }, - "create": { - "type": "boolean" - }, - "extraLabels": { - "type": "object" - }, - "name": { - "type": "string" - }, - "serviceDiscovery": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean" - } - } - } - } - }, - "shareProcessNamespace": { - "type": "boolean" - }, - "standalone": { - "type": "object", - "properties": { - "config": { - "type": [ - "string", - "object" - ] - }, - "enabled": { - "type": [ - "string", - "boolean" - ] - } - } - }, - "statefulSet": { - "type": "object", - "properties": { - "annotations": { - "type": [ - "object", - "string" - ] - }, - "securityContext": { - "type": "object", - "properties": { - "container": { - "type": [ - "object", - "string" - ] - }, - "pod": { - "type": [ - "object", - "string" - ] - } - } - } - } - }, - "terminationGracePeriodSeconds": { - "type": "integer" - }, - "tolerations": { - "type": [ - "null", - "array", - "string" - ] - }, - "topologySpreadConstraints": { - "type": [ - "null", - "array", - "string" - ] - }, - "updateStrategyType": { - "type": "string" - }, - "volumeMounts": { - "type": [ - "null", - "array" - ] - }, - "volumes": { - "type": [ - "null", - "array" - ] - }, - "hostNetwork": { - "type": "boolean" - } + } + }, + "type": "object" + }, + "type": "array" + }, + "ingressClassName": { + "type": "string" + }, + "labels": { + "type": "object" + }, + "pathType": { + "type": "string" + }, + "tls": { + "type": "array" + } + }, + "type": "object" + }, + "livenessProbe": { + "properties": { + "enabled": { + "type": "boolean" + }, + "failureThreshold": { + "type": "integer" + }, + "initialDelaySeconds": { + "type": "integer" + }, + "path": { + "type": "string" + }, + "periodSeconds": { + "type": "integer" + }, + "successThreshold": { + "type": "integer" + }, + "timeoutSeconds": { + "type": "integer" + } + }, + "type": "object" + }, + "logFormat": { + "type": "string" + }, + "logLevel": { + "type": "string" + }, + "networkPolicy": { + "properties": { + "egress": { + "type": "array" + }, + "enabled": { + "type": "boolean" + } + }, + "type": "object" + }, + "nodeSelector": { + "type": [ + "null", + "object", + "string" + ] + }, + "postStart": { + "type": "array" + }, + "preStopSleepSeconds": { + "type": "integer" + }, + "priorityClassName": { + "type": "string" + }, + "readinessProbe": { + "properties": { + "enabled": { + "type": "boolean" + }, + "failureThreshold": { + "type": "integer" + }, + "initialDelaySeconds": { + "type": "integer" + }, + "periodSeconds": { + "type": "integer" + }, + "successThreshold": { + "type": "integer" + }, + "timeoutSeconds": { + "type": "integer" + } + }, + "type": "object" + }, + "resources": { + "type": "object" + }, + "route": { + "properties": { + "activeService": { + "type": "boolean" + }, + "annotations": { + "type": [ + "object", + "string" + ] + }, + "enabled": { + "type": "boolean" + }, + "host": { + "type": "string" + }, + "labels": { + "type": "object" + }, + "tls": { + "type": "object" } + }, + "type": "object" }, - "serverTelemetry": { - "type": "object", - "properties": { - "prometheusRules": { - "type": "object", - "properties": { - "enabled": { - "type": "boolean" - }, - "rules": { - "type": "array" - }, - "selectors": { - "type": "object" - } - } + "service": { + "properties": { + "active": { + "properties": { + "enabled": { + "type": "boolean" + } + }, + "type": "object" + }, + "activeNodePort": { + "type": "integer" + }, + "annotations": { + "type": [ + "object", + "string" + ] + }, + "enabled": { + "type": "boolean" + }, + "externalTrafficPolicy": { + "type": "string" + }, + "instanceSelector": { + "properties": { + "enabled": { + "type": "boolean" + } + }, + "type": "object" + }, + "nodePort": { + "type": "integer" + }, + "port": { + "type": "integer" + }, + "publishNotReadyAddresses": { + "type": "boolean" + }, + "standby": { + "properties": { + "enabled": { + "type": "boolean" } + }, + "type": "object" + }, + "standbyNodePort": { + "type": "integer" + }, + "targetPort": { + "type": "integer" } + }, + "type": "object" }, - "ui": { - "type": "object", - "properties": { - "activeVaultPodOnly": { - "type": "boolean" - }, - "annotations": { - "type": [ - "object", - "string" - ] - }, + "serviceAccount": { + "properties": { + "annotations": { + "type": [ + "object", + "string" + ] + }, + "create": { + "type": "boolean" + }, + "extraLabels": { + "type": "object" + }, + "name": { + "type": "string" + }, + "serviceDiscovery": { + "properties": { "enabled": { - "type": [ - "boolean", - "string" - ] - }, - "externalPort": { - "type": "integer" - }, - "externalTrafficPolicy": { - "type": "string" - }, - "publishNotReadyAddresses": { - "type": "boolean" - }, - "serviceNodePort": { - "type": [ - "null", - "integer" - ] - }, - "serviceType": { - "type": "string" + "type": "boolean" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "shareProcessNamespace": { + "type": "boolean" + }, + "standalone": { + "properties": { + "config": { + "type": [ + "string", + "object" + ] + }, + "enabled": { + "type": [ + "string", + "boolean" + ] + } + }, + "type": "object" + }, + "statefulSet": { + "properties": { + "annotations": { + "type": [ + "object", + "string" + ] + }, + "securityContext": { + "properties": { + "container": { + "type": [ + "object", + "string" + ] }, - "targetPort": { - "type": "integer" + "pod": { + "type": [ + "object", + "string" + ] } + }, + "type": "object" + } + }, + "type": "object" + }, + "terminationGracePeriodSeconds": { + "type": "integer" + }, + "tolerations": { + "type": [ + "null", + "array", + "string" + ] + }, + "topologySpreadConstraints": { + "type": [ + "null", + "array", + "string" + ] + }, + "updateStrategyType": { + "type": "string" + }, + "volumeMounts": { + "type": [ + "null", + "array" + ] + }, + "volumes": { + "type": [ + "null", + "array" + ] + } + }, + "type": "object" + }, + "serverTelemetry": { + "properties": { + "prometheusRules": { + "properties": { + "enabled": { + "type": "boolean" + }, + "rules": { + "type": "array" + }, + "selectors": { + "type": "object" } + }, + "type": "object" + } + }, + "type": "object" + }, + "ui": { + "properties": { + "activeVaultPodOnly": { + "type": "boolean" + }, + "annotations": { + "type": [ + "object", + "string" + ] + }, + "enabled": { + "type": [ + "boolean", + "string" + ] + }, + "externalPort": { + "type": "integer" + }, + "externalTrafficPolicy": { + "type": "string" + }, + "publishNotReadyAddresses": { + "type": "boolean" + }, + "serviceNodePort": { + "type": [ + "null", + "integer" + ] + }, + "serviceType": { + "type": "string" + }, + "targetPort": { + "type": "integer" } + }, + "type": "object" } + }, + "type": "object" }