diff --git a/.github/workflows/auth_test.yml b/.github/workflows/auth_test.yml index c76d7d84..6b3982c5 100644 --- a/.github/workflows/auth_test.yml +++ b/.github/workflows/auth_test.yml @@ -16,7 +16,7 @@ jobs: - name: Installing dependencies run: | sudo apt-get update - pip install -r front/requirements.txt + pip install -r front/requirements.lock - name: Running tests run: | diff --git a/.github/workflows/dependency_review.yml b/.github/workflows/dependency_review.yml index 8b3b4fb2..5fb816de 100644 --- a/.github/workflows/dependency_review.yml +++ b/.github/workflows/dependency_review.yml @@ -12,3 +12,5 @@ jobs: uses: actions/checkout@v4 - name: Dependency Review uses: actions/dependency-review-action@v4 + with: + fail-on-severity: critical diff --git a/.github/workflows/full_test.yml b/.github/workflows/full_test.yml index 8e6ff1f3..b2125c93 100644 --- a/.github/workflows/full_test.yml +++ b/.github/workflows/full_test.yml @@ -33,8 +33,8 @@ jobs: - name: Install dependencies run: | - sudo python -m pip install pip pytest selenium --ignore-installed urllib3 - sudo python -m pip install -r front/requirements.txt --ignore-installed + sudo python -m pip install pip pytest pytest-mock selenium --ignore-installed urllib3 + sudo python -m pip install -r front/requirements.lock --ignore-installed - name: Check availability run: | diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index 0b57736f..b45de221 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -18,7 +18,7 @@ jobs: with: python-version: ${{ matrix.python-version }} cache: 'pip' - cache-dependency-path: ${{matrix.node}}/requirements.txt + cache-dependency-path: ${{matrix.node}}/${{ matrix.node == 'front' && 'requirements.lock' || 'requirements.txt' }} - name: Install dependencies run: | @@ -30,7 +30,7 @@ jobs: sudo apt-get install -y --no-install-recommends mininet mkdir -p /opt/mininet_dependencies fi - pip install -r ${{matrix.node}}/requirements.txt + pip install -r ${{matrix.node}}/${{ matrix.node == 'front' && 'requirements.lock' || 'requirements.txt' }} - name: Lint with mypy run: | diff --git a/.gitignore b/.gitignore index 0f6a4998..1987e778 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,6 @@ back/rabbitmq/rabbitmq/ .env *.log *.ini -front/rabbitmq/rabbitmq \ No newline at end of file +front/rabbitmq/rabbitmq +front/src/static/dist/ +front/web/node_modules/ \ No newline at end of file diff --git a/front/.dockerignore b/front/.dockerignore index d03d2623..8f6c15f4 100644 --- a/front/.dockerignore +++ b/front/.dockerignore @@ -5,4 +5,6 @@ src/static/video src/static/pcaps src/static/assets src/static/avatar -src/static/quiz_images \ No newline at end of file +src/static/quiz_images +src/static/dist +web/node_modules \ No newline at end of file diff --git a/front/.python-version b/front/.python-version new file mode 100644 index 00000000..2c073331 --- /dev/null +++ b/front/.python-version @@ -0,0 +1 @@ +3.11 diff --git a/front/Dockerfile b/front/Dockerfile index 14fec740..df3e4b2a 100644 --- a/front/Dockerfile +++ b/front/Dockerfile @@ -1,17 +1,29 @@ +FROM node:22-slim AS web-build + +WORKDIR /front +COPY ./web/ /front/web/ +COPY ./src/static/netfront/ /front/src/static/netfront/ +COPY ./src/static/config_forms/ /front/src/static/config_forms/ + +WORKDIR /front/web +RUN npm install --no-audit --no-fund --prefer-offline +RUN npm run build + FROM python:3.11 WORKDIR /app -RUN pip install setuptools --upgrade -RUN pip install wheel uwsgi -ADD ./requirements.txt /app/requirements.txt -RUN pip install -r requirements.txt +COPY requirements.lock /app/requirements.lock +RUN pip install --no-cache-dir -r /app/requirements.lock + RUN mkdir -p /app/.postgresql && \ wget "https://storage.yandexcloud.net/cloud-certs/CA.pem" -O /app/.postgresql/root.crt && \ chmod 0600 /app/.postgresql/root.crt ADD ./src /app +COPY --from=web-build /front/src/static/dist/ /app/static/dist/ + ADD run_app.sh /app/run_app.sh RUN chmod +x /app/run_app.sh diff --git a/front/pyproject.toml b/front/pyproject.toml new file mode 100644 index 00000000..03072126 --- /dev/null +++ b/front/pyproject.toml @@ -0,0 +1,70 @@ +[project] +name = "miminet-front" +version = "0.1.0" +description = "Miminet front-end Flask application." +requires-python = ">=3.11,<3.13" +dependencies = [ + "Flask==3.1.2", + "Flask-Admin==2.0.1", + "Flask-Cors==6.0.2", + "Flask-JWT-Extended==4.7.1", + "Flask-Login==0.6.3", + "Flask-Migrate==4.1.0", + "Flask-SQLAlchemy==3.1.1", + "Jinja2==3.1.6", + "Mako>=1.3.11", + "MarkupSafe==3.0.2", + "Pillow==11.3.0", + "PySocks==1.7.1", + "SQLAlchemy==2.0.43", + "Werkzeug==3.1.3", + "alembic==1.16.5", + "beautifulsoup4==4.13.5", + "cachecontrol>=0.14", + "celery==5.5.3", + "certifi==2025.10.5", + "charset-normalizer==3.4.3", + "click==8.1.8", + "colorama==0.4.6", + "dpkt==1.9.8", + "google==3.0.0", + "google-auth==2.40.3", + "google-auth-oauthlib==1.2.2", + "greenlet==3.2.4", + "h11==0.16.0", + "idna==3.10", + "importlib-metadata==8.7.0", + "importlib-resources==6.5.2", + "itsdangerous==2.2.0", + "jsonschema==4.25.1", + "kombu==5.5.4", + "oauthlib==3.3.1", + "psycopg2-binary==2.9.10", + "pyasn1==0.6.1", + "pyasn1-modules==0.4.2", + "python-dateutil==2.9.0.post0", + "python-dotenv==1.1.1", + "requests==2.32.5", + "requests-oauthlib==2.0.0", + "rsa==4.9.1", + "six==1.17.0", + "soupsieve==2.8", + "urllib3==2.5.0", + "uWSGI>=2.0", + "vine==5.1.0", + "wcwidth==0.2.13", + "websocket-client==1.8.0", + "wsproto==1.2.0", + "wtforms==3.2.1", + "zipp==3.23.0", +] + +[project.optional-dependencies] +test = [ + "pytest==8.4.2", + "pytest-mock==3.15.1", + "selenium", +] + +[tool.uv] +package = false diff --git a/front/requirements.lock b/front/requirements.lock new file mode 100644 index 00000000..061582c8 --- /dev/null +++ b/front/requirements.lock @@ -0,0 +1,242 @@ +# This file was autogenerated by uv via the following command: +# uv export --frozen --no-dev --no-hashes --no-emit-project --format requirements.txt -o requirements.lock +alembic==1.16.5 + # via + # flask-migrate + # miminet-front +amqp==5.3.1 + # via kombu +attrs==26.1.0 + # via + # jsonschema + # referencing +beautifulsoup4==4.13.5 + # via + # google + # miminet-front +billiard==4.2.4 + # via celery +blinker==1.9.0 + # via flask +cachecontrol==0.14.4 + # via miminet-front +cachetools==5.5.2 + # via google-auth +celery==5.5.3 + # via miminet-front +certifi==2025.10.5 + # via + # miminet-front + # requests +charset-normalizer==3.4.3 + # via + # miminet-front + # requests +click==8.1.8 + # via + # celery + # click-didyoumean + # click-plugins + # click-repl + # flask + # miminet-front +click-didyoumean==0.3.1 + # via celery +click-plugins==1.1.1.2 + # via celery +click-repl==0.3.0 + # via celery +colorama==0.4.6 + # via + # click + # miminet-front +dpkt==1.9.8 + # via miminet-front +flask==3.1.2 + # via + # flask-admin + # flask-cors + # flask-jwt-extended + # flask-login + # flask-migrate + # flask-sqlalchemy + # miminet-front +flask-admin==2.0.1 + # via miminet-front +flask-cors==6.0.2 + # via miminet-front +flask-jwt-extended==4.7.1 + # via miminet-front +flask-login==0.6.3 + # via miminet-front +flask-migrate==4.1.0 + # via miminet-front +flask-sqlalchemy==3.1.1 + # via + # flask-migrate + # miminet-front +google==3.0.0 + # via miminet-front +google-auth==2.40.3 + # via + # google-auth-oauthlib + # miminet-front +google-auth-oauthlib==1.2.2 + # via miminet-front +greenlet==3.2.4 + # via + # miminet-front + # sqlalchemy +h11==0.16.0 + # via + # miminet-front + # wsproto +idna==3.10 + # via + # miminet-front + # requests +importlib-metadata==8.7.0 + # via miminet-front +importlib-resources==6.5.2 + # via miminet-front +itsdangerous==2.2.0 + # via + # flask + # miminet-front +jinja2==3.1.6 + # via + # flask + # flask-admin + # miminet-front +jsonschema==4.25.1 + # via miminet-front +jsonschema-specifications==2025.9.1 + # via jsonschema +kombu==5.5.4 + # via + # celery + # miminet-front +mako==1.3.10 + # via + # alembic + # miminet-front +markupsafe==3.0.2 + # via + # flask + # flask-admin + # jinja2 + # mako + # miminet-front + # werkzeug + # wtforms +msgpack==1.1.2 + # via cachecontrol +oauthlib==3.3.1 + # via + # miminet-front + # requests-oauthlib +packaging==26.2 + # via kombu +pillow==11.3.0 + # via miminet-front +prompt-toolkit==3.0.52 + # via click-repl +psycopg2-binary==2.9.10 + # via miminet-front +pyasn1==0.6.1 + # via + # miminet-front + # pyasn1-modules + # rsa +pyasn1-modules==0.4.2 + # via + # google-auth + # miminet-front +pyjwt==2.12.1 + # via flask-jwt-extended +pysocks==1.7.1 + # via miminet-front +python-dateutil==2.9.0.post0 + # via + # celery + # miminet-front +python-dotenv==1.1.1 + # via miminet-front +referencing==0.37.0 + # via + # jsonschema + # jsonschema-specifications +requests==2.32.5 + # via + # cachecontrol + # miminet-front + # requests-oauthlib +requests-oauthlib==2.0.0 + # via + # google-auth-oauthlib + # miminet-front +rpds-py==0.30.0 + # via + # jsonschema + # referencing +rsa==4.9.1 + # via + # google-auth + # miminet-front +six==1.17.0 + # via + # miminet-front + # python-dateutil +soupsieve==2.8 + # via + # beautifulsoup4 + # miminet-front +sqlalchemy==2.0.43 + # via + # alembic + # flask-sqlalchemy + # miminet-front +typing-extensions==4.15.0 + # via + # alembic + # beautifulsoup4 + # referencing + # sqlalchemy +tzdata==2026.2 + # via kombu +urllib3==2.5.0 + # via + # miminet-front + # requests +uwsgi==2.0.31 + # via miminet-front +vine==5.1.0 + # via + # amqp + # celery + # kombu + # miminet-front +wcwidth==0.2.13 + # via + # miminet-front + # prompt-toolkit +websocket-client==1.8.0 + # via miminet-front +werkzeug==3.1.3 + # via + # flask + # flask-admin + # flask-cors + # flask-jwt-extended + # flask-login + # miminet-front +wsproto==1.2.0 + # via miminet-front +wtforms==3.2.1 + # via + # flask-admin + # miminet-front +zipp==3.23.0 + # via + # importlib-metadata + # miminet-front diff --git a/front/requirements.txt b/front/requirements.txt deleted file mode 100644 index 4ad25f44..00000000 --- a/front/requirements.txt +++ /dev/null @@ -1,57 +0,0 @@ -alembic==1.16.5 -beautifulsoup4==4.13.5 -certifi==2025.10.5 -charset-normalizer==3.4.3 -click==8.1.8 -colorama==0.4.6 -dpkt==1.9.8 -Flask==3.1.2 -Flask-Login==0.6.3 -Flask-Migrate==4.1.0 -Flask-SQLAlchemy==3.1.1 -google==3.0.0 -google-auth==2.40.3 -google-auth-oauthlib==1.2.2 -greenlet==3.2.4 -h11==0.16.0 -idna==3.10 -importlib-metadata==8.7.0 -importlib-resources==6.5.2 -ipaddress==1.0.23 -itsdangerous==2.2.0 -kombu==5.5.4 -Jinja2==3.1.6 -Mako==1.3.10 -MarkupSafe==3.0.2 -oauthlib==3.3.1 -Pillow==11.3.0 -pyasn1==0.6.1 -pyasn1_modules==0.4.2 -PySocks==1.7.1 -pytest==8.4.2 -pytest-mock==3.15.1 -python-dateutil==2.9.0.post0 -python-dotenv==1.1.1 -requests==2.32.5 -requests-oauthlib==2.0.0 -rsa==4.9.1 -six==1.17.0 -soupsieve==2.8 -SQLAlchemy==2.0.43 -urllib3==2.5.0 -uuid==1.30 -vine==5.1.0 -wcwidth==0.2.13 -websocket-client==1.8.0 -wsproto==1.2.0 -Werkzeug==3.1.3 -zipp==3.23.0 -celery==5.5.3 -python-dotenv==1.1.1 -wtforms==3.2.1 -jsonschema==4.25.1 -Flask-Admin==2.0.1 -psycopg2-binary==2.9.10 -flask_jwt_extended==4.7.1 -flask_cors==6.0.2 -openai>=1.0.0 diff --git a/front/src/miminet_auth.py b/front/src/miminet_auth.py index 83269cb4..5a38e4be 100644 --- a/front/src/miminet_auth.py +++ b/front/src/miminet_auth.py @@ -38,7 +38,7 @@ from miminet_config import make_example_net_switch_and_hub from miminet_model import Network, User, db from oauthlib.oauth2 import TokenExpiredError -from pip._vendor import cachecontrol +import cachecontrol from requests_oauthlib import OAuth2Session from sqlalchemy.exc import SQLAlchemyError from werkzeug.security import check_password_hash, generate_password_hash diff --git a/front/src/static/config_devices.js b/front/src/static/config_devices.js deleted file mode 100644 index 25a3d274..00000000 --- a/front/src/static/config_devices.js +++ /dev/null @@ -1,1702 +0,0 @@ -$('#config_host').load(ExternalUrlFor("/config_host.html")); -$('#config_hub').load(ExternalUrlFor("/config_hub.html")); -$('#config_switch').load(ExternalUrlFor("/config_switch.html")); -$('#config_edge').load(ExternalUrlFor("/config_edge.html")); -$('#config_router').load(ExternalUrlFor("/config_router.html")); -$('#config_server').load(ExternalUrlFor("/config_server.html")); -$('#config_vlan').load(ExternalUrlFor("/config_vlan.html")); -$('#config_vxlan').load(ExternalUrlFor("/config_vxlan.html")); -$("#config_textbox").load(ExternalUrlFor("/config_textbox.html")) - -const config_content_id = "#config_content"; -const config_main_form_id = "#config_main_form"; -const config_router_main_form_id = "#config_router_main_form"; -const config_server_main_form_id = "#config_server_main_form"; -const config_hub_main_form_id = "#config_hub_main_form"; -const config_switch_main_form_id = "#config_switch_main_form"; -const config_edge_main_form_id = "#config_edge_main_form"; -const config_content_save_tag = "#config_content_save"; -const config_content_save_id = "config_content_save"; - -const ClearConfigForm = function (text) { - - let txt = '' - - if (!text) { - txt = 'Тут будут настройки устройств. Выделите любое на схеме.'; - } - - // Clear all child - $(config_content_id).empty(); - $(config_content_save_tag).empty(); - $(config_content_id).append('' + txt + ''); - document.getElementById(config_content_save_id).style.display='none'; - - // Update grid to reclaim full width - if (typeof updateGridForConfigPanel === 'function') { - updateGridForConfigPanel(); - } -} - -const HostWarningMsg = function (msg) { - - let warning_msg = ''; - - $(config_content_id).prepend(warning_msg); -} -const SwitchWarningMsg = function (msg) { - - let warning_msg = ''; - - $(config_content_id).prepend(warning_msg); -} - -const ServerWarningMsg = function (msg) { - - let warning_msg = ''; - - $(config_content_id).prepend(warning_msg); -} - -const HostErrorMsg = function (msg) { - - $(config_content_id).find('.alert-info, .alert-danger').remove(); - - let error_msg = ''; - - $(config_content_id).prepend(error_msg); - - $("#config_main_form :input").prop("disabled", false); - $("#config_router_main_form :input").prop("disabled", false); - $("#config_server_main_form :input").prop("disabled", false); - $("config_switch_main_form :input").prop("disabled", false); - - $('#config_host_main_form_submit_button').text('Сохранить').removeClass('disabled'); - $('#config_router_main_form_submit_button').text('Сохранить').removeClass('disabled'); - $('#config_server_main_form_submit_button').text('Сохранить').removeClass('disabled'); - $('#config_switch_main_form_submit_button').text('Сохранить').removeClass('disabled'); -} - -const UpdateJobCounter = function (counterId, deviceId = null) { - const counter = document.getElementById(counterId); - if (!counter) { - return; - } - - counter.style.display = 'none'; -} - -const UpdateHostConfigurationForm = function(host_id) { - let data = $('#config_main_form').serialize(); - - // Disable all input fields - $("#config_main_form :input").prop("disabled", true); - - // Set loading spinner - $('#config_host_main_form_submit_button').text(''); - $('#config_host_main_form_submit_button').append('Сохранение...'); - - // Use unified delete and save function - DeleteAndSaveJob('host', UpdateHostConfiguration, data, host_id); -}; - -const UpdateTextboxConfigurationForm = function (textbox_id) { - let data = $("#config_textbox_main_form").serialize(); - - // Disable all input fields - $("#config_textbox_main_form :input").prop("disabled", true); - - // Set loading spinner - $("#config_textbox_main_form_submit_button").text(""); - $("#config_textbox_main_form_submit_button").append( - 'Сохранение...', - ); - - UpdateTextboxConfiguration(data, textbox_id); -}; - -const ConfigTextboxForm = function (textbox_id) { - - var form = document.getElementById( - "config_textbox_main_form_script", - ).innerHTML; - var button = document.getElementById( - "config_textbox_save_script", - ).innerHTML; - - $(config_content_id).empty(); - $(config_content_save_tag).empty(); - - document.getElementById(config_content_save_id).style.display = "block"; - - $(config_content_id).append(form); - $(config_content_save_tag).append(button); - - $("#textbox_id").val(textbox_id); - $("#net_guid").val(network_guid); - - function handleTextboxClick(event) { - event.preventDefault(); - UpdateTextboxConfigurationForm(textbox_id); - } - - $("#config_textbox_main_form_submit_button, #config_textbox_end_form").on( - "click", - handleTextboxClick, - ); -}; - -const ConfigHostForm = function (host_id) { - var form = document.getElementById( - "config_host_main_form_script", - ).innerHTML; - var button = document.getElementById("config_host_save_script").innerHTML; - var banner = document.getElementById( - "config_host_edit_banner_script", - ).innerHTML; - - // Clear all child - $(config_content_id).empty(); - $(config_content_save_tag).empty(); - - document.getElementById(config_content_save_id).style.display='block'; - - // Add new form - $(config_content_id).append(form); - $(config_content_id).append(banner); - $(config_content_save_tag).append(button); - - addIpFieldHandlers(); - - // Set host_id - $('#host_id').val(host_id); - $('#net_guid').val(network_guid); - - function handleHostClick(event) { - event.preventDefault(); - UpdateHostConfigurationForm(host_id); - } - - $('#config_host_main_form_submit_button, #config_host_end_form').on('click', handleHostClick); - - // Update grid to exclude config panel area - if (typeof updateGridForConfigPanel === 'function') { - updateGridForConfigPanel(); - } -} - -const ConfigRouterForm = function (router_id) { - var form = document.getElementById('config_router_main_form_script').innerHTML; - var button = document.getElementById('config_router_save_script').innerHTML; - var banner = document.getElementById('config_router_edit_banner_script').innerHTML; - - // Clear all child - $(config_content_id).empty(); - $(config_content_save_tag).empty(); - - document.getElementById(config_content_save_id).style.display='block'; - - // Add new form - $(config_content_id).append(form); - $(config_content_id).append(banner); - $(config_content_save_tag).append(button); - - addIpFieldHandlers(); - - // Set host_id - $('#router_id').val(router_id); - $('#net_guid').val(network_guid); - - function handleRouterClick(event) { - event.preventDefault(); - let data = $('#config_main_form').serialize(); - - // Disable all input fields - $("#config_main_form :input").prop("disabled", true); - - // Set loading spinner - $('#config_router_main_form_submit_button').text(''); - $('#config_router_main_form_submit_button').append('Сохранение...'); - - // Use unified delete and save function - DeleteAndSaveJob('router', UpdateRouterConfiguration, data, router_id); - } - - $('#config_router_main_form_submit_button, #config_router_end_form').on('click', handleRouterClick); - - // Update grid to exclude config panel area - if (typeof updateGridForConfigPanel === 'function') { - updateGridForConfigPanel(); - } -} - -const ConfigServerForm = function (server_id) { - var form = document.getElementById('config_server_main_form_script').innerHTML; - var button = document.getElementById('config_server_save_script').innerHTML; - var banner = document.getElementById('config_server_edit_banner_script').innerHTML; - - // Clear all child - $(config_content_id).empty(); - $(config_content_save_tag).empty(); - - document.getElementById(config_content_save_id).style.display='block'; - - // Add new form - $(config_content_id).append(form); - $(config_content_id).append(banner); - $(config_content_save_tag).append(button); - - addIpFieldHandlers(); - - // Set host_id - $('#server_id').val(server_id); - $('#net_guid').val(network_guid); - - function handleServerClick(event) { - event.preventDefault(); - let data = $('#config_main_form').serialize(); - - // Disable all input fields - $("#config_main_form :input").prop("disabled", true); - - // Set loading spinner - $('#config_server_main_form_submit_button').text(''); - $('#config_server_main_form_submit_button').append('Сохранение...'); - - // Use unified delete and save function - DeleteAndSaveJob('server', UpdateServerConfiguration, data, server_id); - } - - $('#config_server_main_form_submit_button, #config_server_end_form').on('click', handleServerClick); - - // Update grid to exclude config panel area - if (typeof updateGridForConfigPanel === 'function') { - updateGridForConfigPanel(); - } -} - -const ConfigHubForm = function (hub_id) { - var form = document.getElementById('config_hub_main_form_script').innerHTML; - var button = document.getElementById('config_hub_save_script').innerHTML; - - // Clear all child - $(config_content_id).empty(); - $(config_content_save_tag).empty(); - - document.getElementById(config_content_save_id).style.display='block'; - - // Add new form - $(config_content_id).append(form); - $(config_content_save_tag).append(button); - - addIpFieldHandlers(); - - // Set host_id - $('#hub_id').val(hub_id); - $('#net_guid').val(network_guid); - - function handleHubClick(event) { - event.preventDefault(); - let data = $('#config_hub_main_form').serialize(); - - // Disable all input fields - $("#config_hub_main_form :input").prop("disabled", true); - - // Set loading spinner - $('#config_hub_main_form_submit_button').text(''); - $('#config_hub_main_form_submit_button').append('Сохранение...'); - - UpdateHubConfiguration(data, hub_id); - } - - $('#config_hub_main_form_submit_button, #config_hub_end_form').on('click', handleHubClick); - - // Update grid to exclude config panel area - if (typeof updateGridForConfigPanel === 'function') { - updateGridForConfigPanel(); - } -} - -const ConfigSwitchForm = function (switch_id) { - var form = document.getElementById('config_switch_main_form_script').innerHTML; - var button = document.getElementById('config_switch_save_script').innerHTML; - - // Clear all child - $(config_content_id).empty(); - $(config_content_save_tag).empty(); - - document.getElementById(config_content_save_id).style.display='block'; - - // Add new form - $(config_content_id).append(form); - $(config_content_save_tag).append(button); - - addIpFieldHandlers(); - - // Add href for mimishark - // var url = "/MimiShark?guid="+network_guid - // $(needhref).attr('href',url) - - // Set host_id - $('#switch_id').val(switch_id); - $('#net_guid').val(network_guid); - - function handleSwitchClick(event) { - $("#config_switch_main_form [name='config_rstp_stp']").val($('#config_button_rstp').val()); - event.preventDefault(); - let data = $('#config_switch_main_form').serialize(); - - // Disable all input fields - $("#config_switch_main_form :input").prop("disabled", true); - - // Set loading spinner - $('#config_switch_main_form_submit_button').text(''); - $('#config_switch_main_form_submit_button').append('Сохранение...'); - - DeleteAndSaveJob('switch', UpdateSwitchConfiguration, data, switch_id); - } - - $('#config_switch_main_form_submit_button, #config_switch_end_form').on('click', handleSwitchClick); - - // Update grid to exclude config panel area - if (typeof updateGridForConfigPanel === 'function') { - updateGridForConfigPanel(); - } -} - -const ConfigEdgeForm = function (edge_id) { - let edgeSaveXHR = null; - var form = document.getElementById('config_edge_main_form_script').innerHTML; - var button = document.getElementById('config_edge_save_script').innerHTML; - - - // Clear all child - $(config_content_id).empty(); - $(config_content_save_tag).empty(); - - document.getElementById(config_content_save_id).style.display='block'; - - // Add new form - $(config_content_id).append(form); - $(config_content_save_tag).append(button); - - // Set host_id - $('#edge_id').val(edge_id); - $('#net_guid').val(network_guid); - - function handleEdgeClick(event) { - event.preventDefault(); - - if (edgeSaveXHR) { - edgeSaveXHR.abort(); - } - - let data = $('#config_edge_main_form').serialize(); - const edge = edges.find(e => e.data.id === edge_id); - console.log(edge); - const lossValue = $("#edge_loss").val(); - const duplicateValue = $("#edge_duplicate").val(); - - if (edge) { - edge.data.loss_percentage = lossValue; - edge.data.duplicate_percentage = duplicateValue; - } - const inputsToDisable = $('#edge_loss, #edge_duplicate, #config_edge_main_form_submit_button'); - inputsToDisable.prop("disabled", true); - - $('#config_edge_main_form_submit_button').html( - ' Сохранение...' - ); - - edgeSaveXHR = UpdateEdgeConfiguration(data); - inputsToDisable.prop("disabled", false); - } - - $('#config_edge_main_form_submit_button, #config_edge_end_form').off('click').on('click', handleEdgeClick); - - // Update grid to exclude config panel area - if (typeof updateGridForConfigPanel === 'function') { - updateGridForConfigPanel(); - } -} - -const ConfigHubName = function (hostname) { - - var text = document.getElementById('config_hub_name_script').innerHTML; - - $(config_hub_main_form_id).prepend(text); - $("#config_hub_name").val(hostname); -}; - -const ConfigTextboxContent = function(textbox_name) { - var text = document.getElementById("config_textbox_content_script").innerHTML; - - $("#config_textbox_main_form").prepend(text); - $("#config_textbox_content").val(textbox_name) -} - -const ConfigTextboxFontColor = function(textbox_font_color) { - var colorScript = document.getElementById("config_textbox_font_color_script").innerHTML; - $("#config_textbox_main_form").prepend(colorScript); - - var input = $("#config_textbox_font_color"); - - var currentColor = textbox_font_color || "#000000"; - input.val(currentColor); - - const highlightColor = (selectedColor) => { - if (!selectedColor) return; - selectedColor = selectedColor.toLowerCase(); - - var $form = $("#config_textbox_main_form"); - var $presets = $form.find(".color-preset"); - var $customWrapper = $form.find(".custom-color-wrapper"); - - $presets.add($customWrapper).css({ - "outline": "none" - }); - - let foundPreset = false; - $presets.each(function() { - var $el = $(this); - var presetColor = $el.data("color"); - if (presetColor && presetColor.toLowerCase() === selectedColor) { - $el.css({ - "outline": "2px solid #0d6efd", - "outline-offset": "2px" - }); - foundPreset = true; - return false; - } - }); - - if (!foundPreset) { - $customWrapper.css({ - "outline": "2px solid #0d6efd", - "outline-offset": "2px" - }); - } - }; - - highlightColor(currentColor); - - $("#config_textbox_main_form").on("click", ".color-preset", function() { - var selectedColor = $(this).data("color"); - input.val(selectedColor); - highlightColor(selectedColor); - }); - - input.on("input change", function() { - highlightColor($(this).val()); - }); -} - -const ConfigTextboxFontControls = function(size, style, weight) { - if ($("#config_textbox_font_size").length === 0) { - var controls = document.getElementById("config_textbox_font_controls_script").innerHTML; - $("#config_textbox_main_form").prepend(controls); - } - - $("#config_textbox_font_size").val(size); - - var inputStyle = $("#config_textbox_font_style"); - var btnStyle = $("#btn_toggle_style"); - var inputWeight = $("#config_textbox_font_weight"); - var btnWeight = $("#btn_toggle_weight"); - - inputStyle.val(style || 'normal'); - if (style === 'italic') { - btnStyle.removeClass('btn-outline-secondary').addClass('btn-primary active'); - } else { - btnStyle.removeClass('btn-primary active').addClass('btn-outline-secondary'); - } - - inputWeight.val(weight || 'normal'); - if (weight === 'bold') { - btnWeight.removeClass('btn-outline-secondary').addClass('btn-primary active'); - } else { - btnWeight.removeClass('btn-primary active').addClass('btn-outline-secondary'); - } - - $("#config_textbox_main_form").off('click', '#btn_toggle_style').on('click', '#btn_toggle_style', function() { - var btn = $(this); - var input = $("#config_textbox_font_style"); - if (input.val() === 'italic') { - input.val('normal'); - btn.removeClass('btn-primary active').addClass('btn-outline-secondary'); - } else { - input.val('italic'); - btn.removeClass('btn-outline-secondary').addClass('btn-primary active'); - } - }); - - $("#config_textbox_main_form").off('click', '#btn_toggle_weight').on('click', '#btn_toggle_weight', function() { - var btn = $(this); - var input = $("#config_textbox_font_weight"); - if (input.val() === 'bold') { - input.val('normal'); - btn.removeClass('btn-primary active').addClass('btn-outline-secondary'); - } else { - input.val('bold'); - btn.removeClass('btn-outline-secondary').addClass('btn-primary active'); - } - }); -} - -const ConfigEdgeNetworkIssues = function (edge_loss, edge_duplicate) { - var text = document.getElementById('config_edge_set_network_issues_script').innerHTML; - $(config_edge_main_form_id).prepend(text); - $('#edge_loss').val(edge_loss); - $('#edge_duplicate').val(edge_duplicate); -}; - -const ConfigEdgeEndpoints = function (edge_source, edge_target) { - - var text = document.getElementById('config_edge_edpoint_script').innerHTML; - - $(config_edge_main_form_id).prepend((text)); - $('#edge_source').val(edge_source); - $('#edge_target').val(edge_target); -} - -const ConfigSwitchName = function (hostname) { - - var text = document.getElementById('config_switch_name_script').innerHTML; - - $(config_switch_main_form_id).prepend((text)); - $('#switch_name').val(hostname); -} - -const ConfigSwtichSTP = function (stp) { - var elem = document.getElementById('config_switch_checkbox_stp_script'); - - $(elem.innerHTML).insertBefore('#config_switch_end_form'); - - if (stp === 1) { - $('#config_switch_stp').attr('checked', 'checked'); - } - - var warning_text = document.getElementById('config_switch_warning_stp_script').innerHTML; - $('#config_switch_stp').on('click', function () { - if ($(this).is(':checked')) { - $(warning_text).insertBefore('#config_switch_end_form'); - } else { - $('#config_warning_stp').remove(); - } - }); -} - -const ConfigSwtichRSTP = function (rstp) { - var elem = document.getElementById('config_switch_checkbox_rstp_script'); - - $(elem.innerHTML).insertBefore('#config_switch_end_form'); - - if (rstp === 1) { - $('#config_switch_rstp').attr('checked', 'checked'); - } - - var warning_text = document.getElementById('config_switch_warning_rstp_script').innerHTML; - $('#config_switch_rstp').on('click', function () { - if ($(this).is(':checked')) { - $(warning_text).insertBefore('#config_switch_end_form'); - } else { - $('#config_warning_rstp').remove(); - } - }); -} - -const SharedConfigHostForm = function(host_id){ - var form = document.getElementById('config_host_main_form_script').innerHTML; - - // Clear all child - $(config_content_id).empty(); - $(config_content_save_tag).empty(); - document.getElementById(config_content_save_id).style.display='none'; - - // Add new form - $(config_content_id).append(form); - - // Set host_id - $('#host_id').val( host_id ); - $('#net_guid').val( network_guid ); - $('#config_host_main_form_submit_button').prop('disabled', true); -} - -const SharedConfigRouterForm = function (router_id) { - var form = document.getElementById('config_router_main_form_script').innerHTML; - - // Clear all child - $(config_content_id).empty(); - $(config_content_save_tag).empty(); - document.getElementById(config_content_save_id).style.display='none'; - - // Add new form - $(config_content_id).append(form); - - // Set host_id - $('#router_id').val(router_id); - $('#net_guid').val(network_guid); - - $('#config_router_main_form_submit_button').prop('disabled', true); -} - -const SharedConfigServerForm = function (router_id) { - var form = document.getElementById('config_server_main_form_script').innerHTML; - - // Clear all child - $(config_content_id).empty(); - $(config_content_save_tag).empty(); - document.getElementById(config_content_save_id).style.display='none'; - - // Add new form - $(config_content_id).append(form); - - // Set host_id - $('#router_id').val(router_id); - $('#net_guid').val(network_guid); - - $('#config_server_main_form_submit_button').prop('disabled', true); -} - -const SharedConfigHubForm = function (hub_id) { - var form = document.getElementById('config_hub_main_form_script').innerHTML; - - // Clear all child - $(config_content_id).empty(); - $(config_content_save_tag).empty(); - document.getElementById(config_content_save_id).style.display='none'; - - // Add new form - $(config_content_id).append(form); - $('#config_hub_main_form_submit_button').prop('disabled', true); -} - -const SharedConfigSwitchForm = function (switch_id) { - var form = document.getElementById('config_switch_main_form_script').innerHTML; - - // Clear all child - $(config_content_id).empty(); - $(config_content_save_tag).empty(); - document.getElementById(config_content_save_id).style.display='none'; - - // Add new form - $(config_content_id).append(form); - $('#config_switch_main_form_submit_button').prop('disabled', true); -} - -const SharedConfigEdgeForm = function (edge_id) { - var form = document.getElementById('config_edge_main_form_script').innerHTML; - - // Clear all child - $(config_content_id).empty(); - $(config_content_save_tag).empty(); - document.getElementById(config_content_save_id).style.display='block'; - - // Add new form - $(config_content_id).append(form); - $('#config_edge_main_form_submit_button').prop('disabled', true); -} - - -const ConfigHostName = function (hostname) { - - var text = document.getElementById('config_host_name_script').innerHTML; - - $(config_main_form_id).prepend((text)); - $('#config_host_name').val(hostname); -} - -const ConfigRouterName = function (hostname) { - - var text = document.getElementById('config_router_name_script').innerHTML; - - $(config_main_form_id).prepend((text)); - $('#config_router_name').val(hostname); -} - -const ConfigServerName = function (hostname) { - - var text = document.getElementById('config_server_name_script').innerHTML; - - $(config_main_form_id).prepend((text)); - $('#config_server_name').val(hostname); -} - -const ConfigItemInterface = function (name, ip, netmask, connected_to, item) { - - let conf_item = 'config_' + item; - let elem = document.getElementById(conf_item + '_interface_script'); - let eth = jQuery.extend({}, elem); - - if (!name) { - return; - } - - let ids = ["_iface_name_label_", "_iface_name_", "_ip_", "_mask_"]; - ids.forEach(function (id) { - eth.innerHTML = eth.innerHTML.replace(RegExp(conf_item + id + 'example', "g"), conf_item + id + name); - }); - - let tag = '#' + conf_item; - let text = eth.innerHTML; - $(text).insertBefore(tag + '_end_form'); - - $('').insertBefore(tag + ids[1] + name); - $(tag + ids[1] + name).attr("placeholder", connected_to); - $(tag + ids[2] + name).val(ip); - $(tag + ids[3] + name).val(netmask); - - if (Array.isArray(pcaps) && pcaps.includes(name)) { - $(tag + '_iface_name_label_' + name).html('Линк к (pcap)'); - } else { - console.warn('pcaps не определен или не является массивом:', pcaps); - } -} - -const ConfigHostInterface = function (name, ip, netmask, connected_to) { - ConfigItemInterface(name, ip, netmask, connected_to, "host"); -} - -const ConfigRouterInterface = function (name, ip, netmask, connected_to) { - ConfigItemInterface(name, ip, netmask, connected_to, "router"); -} - -const ConfigServerInterface = function (name, ip, netmask, connected_to) { - ConfigItemInterface(name, ip, netmask, connected_to, "server"); -} - -const ConfigHubInterface = function (name, ip, netmask, connected_to) { - ConfigItemInterface(name, ip, netmask, connected_to, "hub"); -} - -const ConfigSwitchInterface = function (name, ip, netmask, connected_to) { - ConfigItemInterface(name, ip, netmask, connected_to, "switch"); -} - -const ConfigItemIndent = function (item) { - let conf_item = 'config_' + item - let text = document.getElementById(conf_item + '_indent_script').innerHTML; - $(text).insertBefore('#' + conf_item + '_end_form'); -} - -const ConfigHubIndent = function () { - ConfigItemIndent("hub"); -} - -const ConfigSwitchIndent = function () { - ConfigItemIndent("switch"); -} - -const addIpFieldHandlers = function () { - document.addEventListener('input', function (e) { - const input = e.target; - - if (!input.matches('input[type="text"][id*="ip"], input[type="text"][name*="ip"], input[type="text"][id*="gw"], input[type="text"][name*="gw"]')) { - return; - } - - const newValue = input.value.replace(/,/g, '.').replace(/ю/g, '.'); - - input.value = newValue; - }); -}; - -const UpdateHostForm = function(name) { - elem = document.getElementById(name).innerHTML; - host_job_list = document.getElementById('config_host_job_list'); - - if (!elem || !host_job_list) { - return; - } - - $('div[name="config_host_select_input"]').remove(); - $(elem).insertBefore(host_job_list); -}; - -const ConfigHostJobOnChange = function (evnt) { - - let elem = null; - let host_job_list = null; - - switch (evnt.target.value) { - case '1': - UpdateHostForm('config_host_ping_c_1_script'); - break; - - case '2': - UpdateHostForm('config_host_ping_with_options_script'); - break; - - case '3': - UpdateHostForm('config_host_send_udp_data_script'); - break; - - case '4': - UpdateHostForm('config_host_send_tcp_data_script'); - break; - - case '5': - UpdateHostForm('config_host_traceroute_with_options_script'); - break; - - case '102': - UpdateHostForm('config_host_add_route_script'); - break; - - case '103': - UpdateHostForm('config_host_add_arp_cache_script'); - break; - - case '108': - UpdateHostForm('config_host_add_dhclient'); - FillDeviceSelectIntf('#config_host_add_dhclient_interface_select_iface_field', '#host_id', "Выберите линк", false) - break; - - case '0': - $('div[name="config_host_select_input"]').remove(); - break; - - default: - console.log("Unknown target.value"); - } - -} - -const ConfigHostJob = function (host_jobs, shared = 0) { - - let elem = document.getElementById('config_host_job_script').innerHTML; - let host_id = document.getElementById('host_id'); - - if (!elem || !host_id) { - return; - } - - $(elem).insertBefore(host_id); - - // Set onchange - document.getElementById('config_host_job_select_field').addEventListener('change', ConfigHostJobOnChange); - - // Update job counter with device ID - UpdateJobCounter('config_host_job_counter', host_id.value); - - elem = document.getElementById('config_host_job_list_script').innerHTML; - if (!elem) { - return; - } - - $(elem).insertBefore(host_id); - - // Print jobs if we have - if (!host_jobs) { - return; - } - - $.each(host_jobs, function (i) { - let jid = host_jobs[i].id; - - if (i == 0) { - $('#config_host_job_list').append(''); - } - - elem = document.getElementById('config_host_job_list_elem_script'); - - if (!elem) { - return; - } - - let job_elem = jQuery.extend({}, elem); - job_elem.innerHTML = job_elem.innerHTML.replace(/config_host_job_delete/g, 'config_host_job_delete_' + jid); - job_elem.innerHTML = job_elem.innerHTML.replace(/config_host_job_edit/g, 'config_host_job_edit_' + jid); - job_elem.innerHTML = job_elem.innerHTML.replace(/justify-content-between align-items-center\">/, 'justify-content-between align-items-center\">' + host_jobs[i].print_cmd + ''); - - let text = job_elem.innerHTML; - //$(text).insertBefore(host_id); - $('#config_host_job_list').append(text); - - $('#config_host_job_delete_' + jid).click(function (event) { - event.preventDefault(); - if (!shared) { - DeleteJobFromHost(host_id.value, jid, network_guid); - } - }); - - $('#config_host_job_edit_' + jid).click(function (event) { - event.preventDefault(); - if (!shared) { - EditJobInHost(host_id.value, jid, network_guid); - } - }); - }); -} - -const ConfigHostGateway = function (gw) { - - var text = document.getElementById('config_host_default_gw_script').innerHTML; - - $(text).insertBefore('#config_host_end_form'); - $('#config_host_default_gw').val(gw); -} - -const ConfigRouterGateway = function (gw) { - - var text = document.getElementById('config_router_default_gw_script').innerHTML; - - $(text).insertBefore('#config_router_end_form'); - $('#config_router_default_gw').val(gw); -} - -const ConfigServerGateway = function (gw) { - - var text = document.getElementById('config_server_default_gw_script').innerHTML; - - $(text).insertBefore('#config_server_end_form'); - $('#config_server_default_gw').val(gw); -} - -const UpdateSwitchForm = function(name) { - elem = document.getElementById(name).innerHTML; - switch_job_list = document.getElementById('config_switch_job_list'); - - if (!elem || !switch_job_list) { - return; - } - - $('div[name="config_switch_select_input"]').remove(); - $(elem).insertBefore(switch_job_list); -}; - -const ConfigSwitchJobOnChange = function(evnt) { - switch (evnt.target.value) { - case '0': - $('div[name="config_switch_select_input"]').remove(); - - break; - case '6': - UpdateSwitchForm('config_switch_link_down_script'); - FillDeviceSelectIntf("#config_switch_link_down_iface_select_field", '#switch_id', "Выберите линк", false); - break; - case '7': - UpdateSwitchForm('config_switch_sleep_script'); - } -} -const ConfigSwitchJob = function (switch_jobs, shared = 0) { - - let elem = document.getElementById('config_switch_job_script').innerHTML; - let switch_id = document.getElementById('switch_id'); - - if (!elem || !switch_id) { - return; - } - - $(elem).insertBefore(switch_id); - - // Set onchange - document.getElementById('config_switch_job_select_field').addEventListener('change', ConfigSwitchJobOnChange); - - // Update job counter with device ID - UpdateJobCounter('config_switch_job_counter', switch_id.value); - - elem = document.getElementById('config_switch_job_list_script').innerHTML; - if (!elem) { - return; - } - - $(elem).insertBefore(switch_id); - - // Print jobs if we have - if (!switch_jobs) { - return; - } - - $.each(switch_jobs, function (i) { - let jid = switch_jobs[i].id; - - if (i == 0) { - $('#config_switch_job_list').append(''); - } - - elem = document.getElementById('config_switch_job_list_elem_script'); - - if (!elem) { - return; - } - - let job_elem = jQuery.extend({}, elem); - job_elem.innerHTML = job_elem.innerHTML.replace(/config_switch_job_delete/g, 'config_switch_job_delete_' + jid); - job_elem.innerHTML = job_elem.innerHTML.replace(/config_switch_job_edit/g, 'config_switch_job_edit_' + jid); - job_elem.innerHTML = job_elem.innerHTML.replace(/justify-content-between align-items-center\">/, 'justify-content-between align-items-center\">' + switch_jobs[i].print_cmd + ''); - - let text = job_elem.innerHTML; - //$(text).insertBefore(host_id); - $('#config_switch_job_list').append(text); - - $('#config_switch_job_delete_' + jid).click(function (event) { - event.preventDefault(); - if (!shared) { - DeleteJobFromSwitch(switch_id.value, jid, network_guid); - } - }); - - $('#config_switch_job_edit_' + jid).click(function (event) { - event.preventDefault(); - if (!shared) { - EditJobInSwitch(switch_id.value, jid, network_guid); - } - }); - }); -} - -const ConfigRouterJobOnChange = function(evnt) { - - switch (evnt.target.value) { - case '0': - $('div[name="config_router_select_input"]').remove(); - - break; - case '1': - UpdateRouterForm('config_router_ping_c_1_script'); - - break; - case '100': - UpdateRouterForm('config_router_add_ip_mask_script'); - FillDeviceSelectIntf("#config_router_add_ip_mask_iface_select_field", '#router_id', "Выберите линк", false); - - break; - case '101': - UpdateRouterForm('config_router_add_nat_masquerade_script'); - FillDeviceSelectIntf("#config_router_add_nat_masquerade_iface_select_field", '#router_id', "Выберите линк", false); - - break; - case '102': - UpdateRouterForm('config_router_add_route_script'); - - break; - case '104': - UpdateRouterForm('config_router_add_subinterface_script'); - FillDeviceSelectIntf("#config_router_add_subinterface_iface_select_field", '#router_id', "Выберите линк" ,false); - - break; - case '105': - UpdateRouterForm('config_router_add_ipip_tunnel_script'); - FillDeviceSelectIntf("#config_router_add_ipip_tunnel_iface_select_ip_field", '#router_id'); - - break; - case '106': - UpdateRouterForm('config_router_add_gre_interface_script'); - FillDeviceSelectIntf("#config_router_add_gre_interface_select_ip_field", '#router_id'); - - break; - case '107': - UpdateRouterForm('config_router_add_arp_proxy_script'); - FillDeviceSelectIntf("#config_router_add_arp_proxy_iface_select_field", '#router_id', "Выберите линк", false); - case '109': - UpdateRouterForm('config_router_add_port_forwarding_tcp_script'); - FillDeviceSelectIntf("#config_router_add_port_forwarding_tcp_iface_select_field", "#router_id", "Выберите линк", false) - break; - case '110': - UpdateRouterForm('config_router_add_port_forwarding_udp_script'); - FillDeviceSelectIntf("#config_router_add_port_forwarding_udp_iface_select_field", "#router_id", "Выберите линк", false) - break; - default: - console.log("Unknown target.value"); - } -} - -const ConfigRouterJob = function (router_jobs, shared = 0) { - - let elem = document.getElementById('config_router_job_script').innerHTML; - let router_id = document.getElementById('router_id'); - - if (!elem || !router_id) { - return; - } - - $(elem).insertBefore(router_id); - - // Set onchange - document.getElementById('config_router_job_select_field').addEventListener('change', ConfigRouterJobOnChange); - - // Update job counter with device ID - UpdateJobCounter('config_router_job_counter', router_id.value); - - elem = document.getElementById('config_router_job_list_script').innerHTML; - if (!elem) { - return; - } - - $(elem).insertBefore(router_id); - - // Print jobs if we have - if (!router_jobs) { - return; - } - - $.each(router_jobs, function (i) { - let jid = router_jobs[i].id; - - if (i == 0) { - $('#config_router_job_list').append(''); - } - - elem = document.getElementById('config_router_job_list_elem_script'); - - if (!elem) { - return; - } - - let job_elem = jQuery.extend({}, elem); - job_elem.innerHTML = job_elem.innerHTML.replace(/config_router_job_delete/g, 'config_router_job_delete_' + jid); - job_elem.innerHTML = job_elem.innerHTML.replace(/config_router_job_edit/g, 'config_router_job_edit_' + jid); - job_elem.innerHTML = job_elem.innerHTML.replace(/justify-content-between align-items-center\">/, 'justify-content-between align-items-center\">' + router_jobs[i].print_cmd + ''); - - let text = job_elem.innerHTML; - //$(text).insertBefore(host_id); - $('#config_router_job_list').append(text); - - $('#config_router_job_delete_' + jid).click(function (event) { - event.preventDefault(); - if (!shared) { - DeleteJobFromRouter(router_id.value, jid, network_guid); - } - }); - - $('#config_router_job_edit_' + jid).click(function (event) { - event.preventDefault(); - if (!shared) { - EditJobInRouter(router_id.value, jid, network_guid); - } - }); - }); -} - -const ConfigServerJob = function (server_jobs, shared = 0) { - - let elem = document.getElementById('config_server_job_script').innerHTML; - let server_id = document.getElementById('server_id'); - - if (!elem || !server_id) { - return; - } - - $(elem).insertBefore(server_id); - - // Set onchange - document.getElementById('config_server_job_select_field').addEventListener('change', ConfigServerJobOnChange); - - // Update job counter with device ID - UpdateJobCounter('config_server_job_counter', server_id.value); - - elem = document.getElementById('config_server_job_list_script').innerHTML; - if (!elem) { - return; - } - - $(elem).insertBefore(server_id); - - // Print jobs if we have - if (!server_jobs) { - return; - } - - $.each(server_jobs, function (i) { - let jid = server_jobs[i].id; - - if (i == 0) { - $('#config_server_job_list').append(''); - } - - elem = document.getElementById('config_server_job_list_elem_script'); - - if (!elem) { - return; - } - - let job_elem = jQuery.extend({}, elem); - job_elem.innerHTML = job_elem.innerHTML.replace(/config_server_job_delete/g, 'config_server_job_delete_' + jid); - job_elem.innerHTML = job_elem.innerHTML.replace(/config_server_job_edit/g, 'config_server_job_edit_' + jid); - job_elem.innerHTML = job_elem.innerHTML.replace(/justify-content-between align-items-center\">/, 'justify-content-between align-items-center\">' + server_jobs[i].print_cmd + ''); - - let text = job_elem.innerHTML; - //$(text).insertBefore(host_id); - $('#config_server_job_list').append(text); - - $('#config_server_job_delete_' + jid).click(function (event) { - event.preventDefault(); - - if (!shared) { - DeleteJobFromServer(server_id.value, jid, network_guid); - } - - }); - - $('#config_server_job_edit_' + jid).click(function (event) { - event.preventDefault(); - if (!shared) { - EditJobInServer(server_id.value, jid, network_guid); - } - }); - }); -} - -const UpdateServerForm = function(name) { - elem = document.getElementById(name).innerHTML; - server_job_list = document.getElementById('config_server_job_list'); - - if (!elem || !server_job_list) { - return; - } - - $('div[name="config_server_select_input"]').remove(); - $(elem).insertBefore(server_job_list); -} - -const ConfigServerJobOnChange = function (evnt) { - - let elem = null; - let server_job_list = null; - let n = null; - let server_id = null; - - switch (evnt.target.value) { - case '0': - $('div[name="config_server_select_input"]').remove(); - break; - - case '1': - UpdateServerForm('config_server_ping_c_1_script'); - break; - - case '200': - UpdateServerForm('config_server_start_udp_server_script'); - break; - - case '201': - UpdateServerForm('config_server_start_tcp_server_script'); - break; - - case '202': - UpdateServerForm('config_server_block_tcp_udp_port_script'); - break; - - case '203': - UpdateServerForm('config_server_add_dhcp_server_script'); - FillDeviceSelectIntf('#config_server_add_dhcp_interface_select_iface_field', '#server_id', "Выберите линк", false) - break; - - default: - console.log("Unknown target.value"); - } - -} - -const DisableFormInputs = function () { - let s = config_content_id + ' :input'; - $(s).prop("disabled", true); - $(config_content_save_tag + ' :input').prop("disabled", true); -} - -const DisableVLANInputs = function (n) { - var modalId = 'VlanModal_' + n.data.id; - - $(document).ready(function () { - $('#config_button_vlan').prop('disabled', false); - $('#' + modalId + ' :input').not('.btn-close').prop('disabled', true); - $('#' + modalId + ' .form-check-input, ' + modalId + ' .form-switch input').prop('disabled', true); - }); -}; - -const UpdateRouterForm = function(name) { - /** - * Replace old form with new one - */ - elem = document.getElementById(name).innerHTML; - router_job_list = document.getElementById('config_router_job_list'); - - if (!elem || !router_job_list){ - return; - } - - $('div[name="config_router_select_input"]').remove(); - $(elem).insertBefore(router_job_list); -} - -const FillDeviceSelectIntf = function(select_id, device, field_msg = 'Интерфейс начальной точки', return_ip = true) { - /** - * Fill select element with network hosts. - * @param {String} select_id ID(name) of the element to which you need to add data. - * @param {String} field_msg Message that will be displayed in the select list by default. - * @param {Boolean} return_ip True if replace user's input with ip and False if replace it with element's id. - */ - - // configured router id - device_id = $(device)[0].value; - - if (!device_id) { - console.log("Не нашел device_id"); - return - } - - device_node = nodes.find(n => n.data.id === device_id); - device_type = device.slice(1, -3 ) //example : #router_id -> router - - if (!device_node) { - console.log("Не нашел device_node"); - return; - } - - if (!device_node.interface.length) { - $(select_id).append(''); - return; - } else { - $(select_id).append(``); - } - $(select_id).on('change', function () { - let selectedOption = $(this).find('option:selected'); // Получаем выбранный элемент - let selectedLabel = selectedOption.text(); // Получаем текст выбранного элемента - document.getElementById(device_type + '_connection_host_label_hidden').value = selectedLabel; // Записываем его в скрытое поле - }); - - device_node.interface.forEach(function(iface) { - // iterating over the router interfaces - - let iface_id = iface.id; - let iface_ip = iface.ip; - - if (!iface_id || (return_ip && !iface_ip)) { - console.log("Не нашел ip/id у интерфейса"); - return; - } - - let connect_id = iface.connect; - if (!connect_id) { - console.log("Не нашел подключение у интерфейса"); - return; - } - - let edge = edges.find(e => e.data.id === connect_id); - - if (!edge) { - console.log("Не нашел ребро по подключению интерфейса"); - return; - } - - let edge_source = edge.data.source; - let edge_target = edge.data.target; - - if (!edge_source || !edge_target) { - console.log("Не получилось найти target и source у ребра"); - return; - } - - let device_connection = (device_node.data.id === edge_target) ? edge_source : edge_target; - - let device_connection_host_node = nodes.find(n => n.data.id === device_connection); - let device_connection_host_label = (device_connection_host_node) ? device_connection_host_node.data.label : "Unknown"; - - $(select_id).append(''); - - }); -} - - -const DisableVXLANInputs = function (n) { - var modalId = 'VxlanConfigModal' + n.data.id; - - - $(document).ready(function () { - $('#config_button_vxlan').prop('disabled', false); - $('#' + modalId + ' :input').not('.btn-close').prop('disabled', true); - $('#' + modalId + ' .form-check-input, #' + modalId + ' .form-switch input').prop('disabled', true); - $(' - `); - } - - let activeNodeId = null; - - const hideResizeFrame = () => { - $('#resize-frame').remove(); - activeNodeId = null; - }; - - $(document).off('mousedown.resizeHide').on('mousedown.resizeHide', function(e) { - if ($(e.target).closest('#network_scheme').length > 0) { - return; - } - - if ($(e.target).closest('.resize-frame').length > 0 || $(e.target).hasClass('resize-handle')) { - return; - } - - hideResizeFrame(); - }); - - const updateResizeFrame = () => { - if (!activeNodeId) return; - const node = cy.getElementById(activeNodeId); - if (node.empty() || node.removed()) { hideResizeFrame(); return; } - - const bb = node.renderedBoundingBox({ includeLabels: false }); - const containerOffset = $(cy.container()).offset(); - - let frame = $('#resize-frame'); - if (frame.length === 0) return; - - frame.css({ - top: containerOffset.top + bb.y1, - left: containerOffset.left + bb.x1, - width: bb.w, - height: bb.h - }); - }; - - const initResizeFrame = (node) => { - hideResizeFrame(); - activeNodeId = node.id(); - - let n = nodes.find(n => n.data.id === node.id()); - if (!n || !n.config) return; - - const frame = $('
'); - $('body').append(frame); - - const handles = [ - { d: 'nw', x: 0, y: 0, c: 'nw-resize' }, { d: 'n', x: 50, y: 0, c: 'n-resize' }, - { d: 'ne', x: 100, y: 0, c: 'ne-resize' }, { d: 'e', x: 100, y: 50, c: 'e-resize' }, - { d: 'se', x: 100, y: 100, c: 'se-resize' }, { d: 's', x: 50, y: 100, c: 's-resize' }, - { d: 'sw', x: 0, y: 100, c: 'sw-resize' }, { d: 'w', x: 0, y: 50, c: 'w-resize' } - ]; - - handles.forEach(h => { - const handle = $('
'); - handle.css({ left: h.x + '%', top: h.y + '%', cursor: h.c }); - - handle.on('mousedown', function(e) { - e.stopPropagation(); - e.preventDefault(); - - const startX = e.pageX; - const startY = e.pageY; - const startW = n.config.width || 100; - const startH = n.config.height || 50; - const startPos = {x: node.position().x, y: node.position().y}; - const zoom = cy.zoom(); - - node.ungrabify(); - - $(document).on('mousemove.resizing', function(ev) { - const dx = (ev.pageX - startX) / zoom; - const dy = (ev.pageY - startY) / zoom; - - let newW = startW; - let newH = startH; - let newX = startPos.x; - let newY = startPos.y; - - // Horizontal Resize - if (h.d.includes('e')) { - newW = Math.max(30, startW + dx); - newX += (newW - startW) / 2; // Shift center right - } - if (h.d.includes('w')) { - newW = Math.max(30, startW - dx); - newX -= (newW - startW) / 2; // Shift center left - } - - // Vertical Resize - if (h.d.includes('s')) { - newH = Math.max(30, startH + dy); - newY += (newH - startH) / 2; // Shift center down - } - if (h.d.includes('n')) { - newH = Math.max(30, startH - dy); - newY -= (newH - startH) / 2; // Shift center up - } - - n.config.width = newW; - n.config.height = newH; - - node.style('width', newW); - node.style('height', newH); - node.style('text-max-width', newW); - - node.position({x: newX, y: newY}); - - updateResizeFrame(); - }); - - $(document).on('mouseup.resizing', function() { - $(document).off('.resizing'); - node.grabify(); - TakeGraphPictureAndUpdate(); - MoveNodes(); - }); - }); - - frame.append(handle); - }); - - updateResizeFrame(); - }; - - cy.on('tap', '.textbox', (e) => initResizeFrame(e.target)); - cy.on('tap', (e) => { if (e.target === cy) hideResizeFrame(); }); - cy.on('zoom pan position', updateResizeFrame); - - let allowEdges = function(src, tgt) { - const sNode = nodes.find(n => n.data.id === src.id()); - const tNode = nodes.find(n => n.data.id === tgt.id()); - - if (sNode && sNode.config && sNode.config.type === 'textbox') return false; - if (tNode && tNode.config && tNode.config.type === 'textbox') return false; - - return !src.same(tgt); - } - - let customDefaults = { - handleNodes: '.host, .l2_switch, .l1_hub, .l3_router, .server', - canConnect: (src, tgt) => allowEdges(src, tgt), - - edgeParams: (src, tgt) => allowEdges(src, tgt) ? {} : null, - - hoverDelay: 150, - snap: false, - snapThreshold: 50, - snapFrequency: 15, - noEdgeEventsInDraw: true, - disableBrowserGestures: true, - }; - - global_eh = cy.edgehandles(customDefaults); - - cy.minZoom(0.5); - cy.maxZoom(2); - - cy.add(nodes); - cy.add(edges); - - // Mark edges that have a link-down job configured - MarkLinkDownEdges(cy); - - // Auto-snap existing network nodes on load - SnapNodesToGrid(cy); - - // Changing zoom - cy.on('zoom', function(evt){ - - if (NetworkUpdateTimeoutId >= 0){ - clearTimeout(NetworkUpdateTimeoutId); - NetworkUpdateTimeoutId = -1; - } - - NetworkUpdateTimeoutId = setTimeout(UpdateNetworkConfig, 2000); - - // Update grid zoom and redraw - if (gridCanvasLayer) { - currentGridZoom = cy.zoom(); - drawGrid(); - } - }); - - // Changing the pan - cy.on('pan', function(evt){ - - if (NetworkUpdateTimeoutId >= 0){ - clearTimeout(NetworkUpdateTimeoutId); - NetworkUpdateTimeoutId = -1; - } - - NetworkUpdateTimeoutId = setTimeout(UpdateNetworkConfig, 2000); - - // Update grid when panning to keep it aligned with nodes - if (gridCanvasLayer) { - drawGrid(); - } - }); - - // Looking for a position changing - cy.on('dragfree', 'node', function(evt){ - - //let node_id = evt.target.id(); - let n = nodes.find(n => n.data.id === this.id()); - - if (!n) { - return; - } - - // Get current position - let posX = this.position().x; - let posY = this.position().y; - - // Snap to grid (like draw.io) - const baseGridSize = 25; - - // Snap position to nearest grid intersection - posX = Math.round(posX / baseGridSize) * baseGridSize; - posY = Math.round(posY / baseGridSize) * baseGridSize; - - // Apply snapped position back to node - this.position({ - x: posX, - y: posY - }); - - n.position.x = posX; - n.position.y = posY; - - MoveNodes(); // --- RESIZE UI LOGIC END --- - - TakeGraphPictureAndUpdate(); - }); - - // Click on object - cy.on('click', function (evt) { - - let evtTarget = evt.target; - - // Is this cy ? - if (evtTarget === cy) { - ClearConfigForm(''); - selecteed_node_id = 0; - selected_edge_id = 0; - return; - } - - // Is this edge ? - if (evtTarget.group() === 'edges'){ - selected_edge_id = evtTarget.data().id; - ShowEdgeConfig(selected_edge_id); - selecteed_node_id = 0; - return; - } - - // Maybe host ? - var target_id = evt.target.id(); - let n = nodes.find(n => n.data.id === target_id); - - if (!n) { - return; - } - - selecteed_node_id = n.data.id; - selected_edge_id = 0; - - if (n.config.type === 'host'){ - ShowHostConfig(n); - } else if (n.config.type === 'l1_hub'){ - ShowHubConfig(n); - } else if (n.config.type === 'l2_switch'){ - ShowSwitchConfig(n); - } else if (n.config.type === 'router'){ - ShowRouterConfig(n); - } else if (n.config.type === 'server'){ - ShowServerConfig(n); - } else if (n.config.type === 'textbox'){ - ShowTextboxConfig(n); - } - }); - - // Add edge to the edges[] and then save it to the server. - cy.on('ehcomplete', (event, sourceNode, targetNode, addedEdge) => { - AddEdge(sourceNode._private.data.id, targetNode._private.data.id); - DrawGraph(); - PostNodesEdges(); - TakeGraphPictureAndUpdate(); - - SetNetworkPlayerState(-1); - }); - - $(document).on('keyup', function(e){ - - const evtTarget = e.target; - if (evtTarget && evtTarget.form) { - return; - } - - if (e.keyCode == 46 && selecteed_node_id) { - if (activeNodeId === selecteed_node_id) { - hideResizeFrame(); - } - - // Save the network state. - SaveNetworkObject(); - - DeleteNode(selecteed_node_id); - DeleteJob(selecteed_node_id); - - ClearConfigForm(''); - selecteed_node_id = 0; - selected_edge_id = 0; - - PostNodesEdges(); // Update network on server - cy.elements().remove(); - cy.add(nodes); - cy.add(edges); - - TakeGraphPictureAndUpdate(); - - // Reset network state - SetNetworkPlayerState(-1); - } - if (e.keyCode == 46 && selected_edge_id) { - - // Save the network state. - SaveNetworkObject(); - - // If the source or target is a switch, delete the jobs. - let ed = edges.find(ed => ed.data.id === selected_edge_id); - if (ed){ - if (ed.data.source.startsWith("l2sw")){ - DeleteJob(ed.data.source) - } - if (ed.data.target.startsWith("l2sw")){ - DeleteJob(ed.data.target) - } - } - DeleteEdge(selected_edge_id); - - ClearConfigForm(''); - selected_edge_id = 0; - - PostNodesEdges(); // Update network on server - cy.elements().remove(); - cy.add(nodes); - cy.add(edges); - - TakeGraphPictureAndUpdate(); - - // Reset network state - SetNetworkPlayerState(-1); - } - - if (e.keyCode == 90 && e.ctrlKey){ - - ClearConfigForm(''); - selecteed_node_id = 0; - selected_edge_id = 0; - - RestoreNetworkObject(); - - PostNodesEdges(); // Update network on server - cy.elements().remove(); - cy.add(nodes); - cy.add(edges); - - TakeGraphPictureAndUpdate(); - - // Reset network state - SetNetworkPlayerState(-1); - } - - }); - - - cy.on('tap', '.textbox', (e) => { - e.originalEvent.stopPropagation(); - initResizeFrame(e.target); - }); - - cy.on('tap', (e) => { - if (e.target === cy) { - hideResizeFrame(); - return; - } - - if (e.target.isNode && e.target.isNode()) { - let n = nodes.find(n => n.data.id === e.target.id()); - if (n && n.config && n.config.type !== 'textbox') { - hideResizeFrame(); - } - } - }); - - cy.on('zoom pan', updateResizeFrame); - - cy.on('dragstart', (e) => { - if (e.target.id() !== activeNodeId) { - hideResizeFrame(); - } - }); - - // Initialize grid - initGrid(cy); -} - -const DrawGraphStatic = function(nodes, edges, shared=0) { - - // Do we already have one? - let cy = undefined; - - let network_scheme_id = "network_scheme"; - - if (shared){ - network_scheme_id = "network_scheme_shared"; - } - - if (global_cy) - { - cy = global_cy; - cy.elements().remove(); - } else { - cy = cytoscape({ - container: document.getElementById(network_scheme_id), - boxSelectionEnabled: true, - autounselectify: false, - style: prepareStylesheet(), - elements: [], - layout: 'preset', - zoom: network_zoom, - pan: { x: network_pan_x, y: network_pan_y }, - fit: true, - }); - - global_cy = cy; - } - - // Turn off edges creation. - if (global_eh){ - global_eh.disable(); - } - - cy.autounselectify(false); - cy.add(nodes); - cy.add(edges); - MarkLinkDownEdges(cy); - cy.nodes().ungrabify(); - - // Initialize grid - initGrid(cy); - - return; -} - -const DrawSharedGraph = function(nodes, edges) { - - // Do we already have one? - let cy = undefined; - - if (global_cy) - { - cy = global_cy; - cy.elements().remove(); - } else { - cy = cytoscape({ - container: document.getElementById("network_scheme_shared"), - boxSelectionEnabled: true, - autounselectify: true, - style: prepareStylesheet(), - elements: [], - layout: 'preset', - zoom: network_zoom, - pan: { x: network_pan_x, y: network_pan_y }, - fit: true, - }); - - global_cy = cy; - } - - cy.autounselectify(true); - - cy.minZoom(0.5); - cy.maxZoom(2); - - cy.add(nodes); - cy.add(edges); - MarkLinkDownEdges(cy); - - // Click on object - cy.on('click', function (evt) { - - let evtTarget = evt.target; - if (evtTarget === cy) { - ClearConfigForm(''); - selecteed_node_id = 0; - selected_edge_id = 0; - return; - } - - // Is this edge ? - if (evtTarget.group() === 'edges'){ - selected_edge_id = evtTarget.data().id; - ShowEdgeConfig(selected_edge_id, 1); - selecteed_node_id = 0; - return; - } - - var target_id = evt.target.id(); - let n = nodes.find(n => n.data.id === target_id); - - if (!n) { - return; - } - - selecteed_node_id = n.data.id; - selected_edge_id = 0; - - if (n.config.type === 'host'){ - ShowHostConfig(n, 1); - } else if (n.config.type === 'l1_hub'){ - ShowHubConfig(n, 1); - } else if (n.config.type === 'l2_switch'){ - ShowSwitchConfig(n, 1); - } else if (n.config.type === 'router'){ - ShowRouterConfig(n, 1); - } else if (n.config.type === 'server'){ - ShowServerConfig(n, 1); - } - }); - - // Initialize grid - initGrid(cy); -} - -const DrawIndexGraphStatic = function(nodes, edges, container_id, graph_network_zoom, - graph_network_pan_x, graph_network_pan_y) -{ - - let index_cy = cytoscape({ - container: document.getElementById(container_id), - boxSelectionEnabled: true, - autounselectify: false, - style: prepareStylesheet(), - elements: [], - layout: 'preset', - zoom: graph_network_zoom, - pan: { x: graph_network_pan_x, y: graph_network_pan_y }, - fit: true, - }); - - index_cy.autounselectify(false); - - index_cy.add(nodes); - index_cy.add(edges); - index_cy.panningEnabled(false); - - index_cy.nodes().ungrabify(); - return index_cy; -} - -// Check whether simulation is over and we can run packets -const CheckSimulation = function (simulation_id) -{ - ajaxWithAuth({ - type: 'GET', - url: ExternalUrlFor('/check_simulation?simulation_id=' + simulation_id + '&network_guid=' + network_guid), - data: '', - success: function(data, textStatus, xhr) { - // If we got 210 (processing) wait 2 sec and call themself again - if (xhr.status === 210) - { - setTimeout(CheckSimulation, 2000, simulation_id); - } - - // Simulation is ended up and we can grab the packets - if (xhr.status === 200) - { - packets = JSON.parse(data.packets); - pcaps = data.pcaps; - - // Set filters - packetsNotFiltered = null; - SetPacketFilter(); - - const answerButton = document.querySelector('button[name="answerQuestion"]'); - if (answerButton) { - answerButton.disabled = false; - } - } - }, - error: function(xhr) { - console.log('Cannot check simulation id = ' + simulation_id); - if (lastSimulationId == simulation_id){ - SetNetworkPlayerState(-1); - } - }, - contentType: "application/json", - dataType: 'json' - }); -} - -// Update edge configuration -const UpdateEdgeConfiguration = (data) => { - SetNetworkPlayerState(-1); - - return ajaxWithAuth({ - type: 'POST', - url: ExternalUrlFor('/edge/save_config'), - data: data, - complete: function() { - DrawGraph(); - $('#config_edge_main_form_submit_button').html('Сохранить'); - }, - error: function(xhr) { - console.log('Не удалось обновить конфигурацию ребра'); - console.log(xhr); - }, - dataType: 'json' - }); -}; - - -const InsertWaitingTime = function () -{ - // Get last emulation task time - // and send request to get count of emulating networks before this time - ajaxWithAuth({ - type: 'GET', - url: ExternalUrlFor('/emulation_queue/time'), - data: '', - success: function(data) { - // Run helper function with time param - InsertWaitingTimeHelper(data.time) - }, - error: function(err) { - console.error("Failed to fetch queue time:", err); - }, - contentType: "application/json", - dataType: 'json' - }); -} - -const InsertWaitingTimeHelper = function(time_filter) { - // Insert field with queue size - ajaxWithAuth({ - type: 'GET', - url: ExternalUrlFor('/emulation_queue/size?time-filter=' + time_filter.toString()), - data: '', - success: function(data) { - const queue_size = parseInt(data.size); - if (!$('#NetworkPlayer button:first').prop('disabled')) { - console.log($('#NetworkPlayer button:first').prop('disabled')) - return; - } else if (queue_size <= 1) { - $('#NetworkPlayerLabel').text("Ожидание 10-15 сек."); - } else { - $('#NetworkPlayerLabel').text(`Место в очереди ${queue_size}`); - - // Update waiting time - setTimeout(() => InsertWaitingTimeHelper(time_filter), 500); - } - - }, - error: function(err) { - console.error("Failed to fetch queue size:", err); - }, - contentType: "application/json", - dataType: 'json' - }); -} - -// Update host configuration -const UpdateHostConfiguration = function (data, host_id) -{ - // Reset network player - SetNetworkPlayerState(-1); - - ajaxWithAuth({ - type: 'POST', - url: ExternalUrlFor('/host/save_config'), - data: data, - success: function(data, textStatus, xhr) { - - if (xhr.status === 200) - { - // Exit edit mode on successful save - if (editingJobId && editingDeviceType === 'host') { - ExitEditMode('host'); - } - if (!data.warning){ - // Update nodes - nodes = data.nodes; - // Update jobs - jobs = data.jobs; - - // Update graph - DrawGraph(); - } - - // Ok, let's try to update host config form - let n = nodes.find(n => n.data.id === host_id); - - if (!n) { - ClearConfigForm('Нет такого хоста'); - return; - } - - if (n.config.type === 'host'){ - ShowHostConfig(n); - } else { - ClearConfigForm('Узел есть, но это не хост'); - return; - } - - if (data.warning){ - HostWarningMsg(data.warning); - } - - // Update job counter after successful configuration - UpdateJobCounter('config_host_job_counter', host_id); - } - }, - error: function(xhr) { - console.log('Не удалось обновить конфигурацию хоста'); - console.log(xhr); - - // Show error message to user - let errorMsg = 'Ошибка при сохранении конфигурации'; - if (xhr.responseJSON && xhr.responseJSON.message) { - errorMsg = xhr.responseJSON.message; - } - HostErrorMsg(errorMsg); - - // Exit edit mode on error to allow retry - if (editingJobId && editingDeviceType === 'host') { - ExitEditMode('host'); - } - }, - dataType: 'json' - }); -} - -// Delete job from host -const DeleteJobFromHost = function (host_id, job_id, network_guid) -{ - // Reset network player - SetNetworkPlayerState(-1); - - let data = { - id: job_id, - guid: network_guid, - }; - - ajaxWithAuth({ - type: 'POST', - url: ExternalUrlFor('/host/delete_job'), - data: data, - encode: true, - success: function(data, textStatus, xhr) { - - if (xhr.status === 200) - { - // Update jobs - jobs = data.jobs; - - // Update graph - DrawGraph(); - - // Ok, let's try to update host config form - let n = nodes.find(n => n.data.id === host_id); - - if (!n) { - ClearConfigForm('Нет такого хоста'); - return; - } - - if (n.config.type === 'host'){ - ShowHostConfig(n); - } else { - ClearConfigForm('Узел есть, но это не хост'); - } - - // Update job counter after deletion - UpdateJobCounter('config_host_job_counter', host_id); - - } - }, - error: function(xhr) { - console.log('Не удалось удалить команду'); - console.log(xhr); - }, - dataType: 'json' - }); -} - -// Delete job from router -const DeleteJobFromRouter = function (router_id, job_id, network_guid) -{ - // Reset network player - SetNetworkPlayerState(-1); - - let data = { - id: job_id, - guid: network_guid, - }; - - $.ajax({ - type: 'POST', - url: '/host/delete_job', - data: data, - encode: true, - success: function(data, textStatus, xhr) { - - if (xhr.status === 200) - { - // Update jobs - jobs = data.jobs; - - // Update graph - DrawGraph(); - - // Ok, let's try to update host config form - let n = nodes.find(n => n.data.id === router_id); - - if (!n) { - ClearConfigForm('Нет такого хоста'); - return; - } - - if (n.config.type === 'router'){ - ShowRouterConfig(n); - } else { - ClearConfigForm('Узел есть, но это не раутер'); - } - - // Update job counter after deletion - UpdateJobCounter('config_router_job_counter', router_id); - } - }, - error: function(xhr) { - console.log('Не удалось удалить команду'); - console.log(xhr); - }, - dataType: 'json' - }); -} - -const DeleteJobFromSwitch = function (switch_id, job_id, network_guid) -{ - // Reset network player - SetNetworkPlayerState(-1); - - let data = { - id: job_id, - guid: network_guid, - }; - - $.ajax({ - type: 'POST', - url: '/host/delete_job', - data: data, - encode: true, - success: function(data, textStatus, xhr) { - - if (xhr.status === 200) - { - // Update jobs - jobs = data.jobs; - - // Update graph - DrawGraph(); - - // Ok, let's try to update host config form - let n = nodes.find(n => n.data.id === switch_id); - - if (!n) { - ClearConfigForm('Нет такого хоста'); - return; - } - - if (n.config.type === 'l2_switch'){ - ShowSwitchConfig(n); - } else { - ClearConfigForm('Узел есть, но это не свитч'); - } - UpdateJobCounter('config_switch_job_counter', switch_id); - } - }, - error: function(xhr) { - console.log('Не удалось удалить команду'); - console.log(xhr); - }, - dataType: 'json' - }); -} - -// Delete job from server -const DeleteJobFromServer = function (server_id, job_id, network_guid) -{ - // Reset network player - SetNetworkPlayerState(-1); - - let data = { - id: job_id, - guid: network_guid, - }; - - $.ajax({ - type: 'POST', - url: '/host/delete_job', - data: data, - encode: true, - success: function(data, textStatus, xhr) { - - if (xhr.status === 200) - { - // Update jobs - jobs = data.jobs; - - // Update graph - DrawGraph(); - - // Ok, let's try to update host config form - let n = nodes.find(n => n.data.id === server_id); - - if (!n) { - ClearConfigForm('Нет такого хоста'); - return; - } - - if (n.config.type === 'server'){ - ShowServerConfig(n); - } else { - ClearConfigForm('Узел есть, но это не сервер'); - } - - // Update job counter after deletion - UpdateJobCounter('config_server_job_counter', server_id); - } - }, - error: function(xhr) { - console.log('Не удалось удалить команду'); - console.log(xhr); - }, - dataType: 'json' - }); -} - -// Update router configuration -const UpdateRouterConfiguration = function (data, router_id) -{ - // Reset network player - SetNetworkPlayerState(-1); - - ajaxWithAuth({ - type: 'POST', - url: ExternalUrlFor('/host/router_save_config'), - data: data, - success: function(data, textStatus, xhr) { - - if (xhr.status === 200) - { - - // Exit edit mode on successful save - if (editingJobId && editingDeviceType === 'router') { - ExitEditMode('router'); - } - - // Update nodes - if (data.nodes) - { - nodes = data.nodes; - } - - // Update jobs - if (data.jobs) - { - jobs = data.jobs; - } - - // Update graph - DrawGraph(); - - // Ok, let's try to update router config form - let n = nodes.find(n => n.data.id === router_id); - - if (!n) { - ClearConfigForm('Нет такого раутера'); - return; - } - - if (n.config.type === 'router'){ - ShowRouterConfig(n); - } else { - ClearConfigForm('Узел есть, но это не раутер'); - return; - } - - if (data.warning) - { - HostWarningMsg(data.warning); - } - - // Update job counter after successful configuration - UpdateJobCounter('config_router_job_counter', router_id); - } - - }, - error: function(xhr) { - console.log('Не удалось обновить конфигурацию хоста'); - console.log(xhr); - - // Show error message to user - let errorMsg = 'Ошибка при сохранении конфигурации роутера'; - if (xhr.responseJSON && xhr.responseJSON.message) { - errorMsg = xhr.responseJSON.message; - } - HostErrorMsg(errorMsg); - - // Exit edit mode on error to allow retry - if (editingJobId && editingDeviceType === 'router') { - ExitEditMode('router'); - } - }, - dataType: 'json' - }); -} - -// Update server configuration -const UpdateServerConfiguration = function (data, router_id) -{ - // Reset network player - SetNetworkPlayerState(-1); - - ajaxWithAuth({ - type: 'POST', - url: ExternalUrlFor('/host/server_save_config'), - data: data, - success: function(data, textStatus, xhr) { - - if (xhr.status === 200) - { - - // Exit edit mode on successful save - if (editingJobId && editingDeviceType === 'server') { - ExitEditMode('server'); - } - - if (!data.warning){ - - if (data.nodes){ - nodes = data.nodes; - } - - if (data.jobs){ - jobs = data.jobs; - } - - // Update graph - DrawGraph(); - } - - // Ok, let's try to update router config form - let n = nodes.find(n => n.data.id === router_id); - - if (!n) { - ClearConfigForm('Нет такого сервера'); - return; - } - - if (n.config.type === 'server'){ - ShowServerConfig(n); - } else { - ClearConfigForm('Узел есть, но это не сервер'); - return; - } - - if (data.warning) - { - ServerWarningMsg(data.warning); - } - - // Update job counter after successful configuration - UpdateJobCounter('config_server_job_counter', router_id); - } - - }, - error: function(xhr) { - console.log('Не удалось обновить конфигурацию сервера'); - console.log(xhr); - - // Show error message to user - let errorMsg = 'Ошибка при сохранении конфигурации сервера'; - if (xhr.responseJSON && xhr.responseJSON.message) { - errorMsg = xhr.responseJSON.message; - } - HostErrorMsg(errorMsg); - - // Exit edit mode on error to allow retry - if (editingJobId && editingDeviceType === 'server') { - ExitEditMode('server'); - } - }, - dataType: 'json' - }); -} - -// Update hub configuration -const UpdateHubConfiguration = function (data, hub_id) -{ - ajaxWithAuth({ - type: 'POST', - url: ExternalUrlFor('/host/hub_save_config'), - data: data, - success: function(data, textStatus, xhr) { - - if (xhr.status === 200) - { - // Update nodes - nodes = data.nodes; - - // We don't clear packets and RunButtonState. - // Hub can change only names - - // Update graph - DrawGraph(); - - // Ok, let's try to update host config form - let n = nodes.find(n => n.data.id === hub_id); - - if (!n) { - ClearConfigForm('Нет такого узла'); - return; - } - - if (n.config.type === 'l1_hub'){ - ShowHubConfig(n); - } else { - ClearConfigForm('Нет такого хаба'); - } - } - }, - error: function(xhr) { - console.log('Cannot update host config'); - console.log(xhr); - }, - dataType: 'json' - }); -} - -const UpdateTextboxConfiguration = function (data, textbox_id) { - SetNetworkPlayerState(-1); - - $.ajax({ - type: "POST", - url: "/host/textbox_save_config", - data: data, - success: function (data, textStatus, xhr) { - if (xhr.status === 200) { - - nodes = data.nodes; - DrawGraph(); - - let n = nodes.find((n) => n.data.id === textbox_id); - - if (!n) { - ClearConfigForm("Нет такого текстового блока"); - return; - } - - if (n.config.type === "textbox") { - ShowTextboxConfig(n); - } else { - ClearConfigForm("Нет такого текстового блока"); - return; - } - - } - }, - error: function(xhr) { - console.log("Cannot update textbox config"); - console.log(xhr); - }, - dataType: 'json' - - }); -}; - -// Update Switch configuration -const UpdateSwitchConfiguration = function (data, switch_id) -{ - // Reset network player - SetNetworkPlayerState(-1); - - ajaxWithAuth({ - type: 'POST', - url: ExternalUrlFor('/host/switch_save_config'), - data: data, - success: function(data, textStatus, xhr) { - - if (xhr.status === 200) - { - if (editingJobId && editingDeviceType === 'switch') { - ExitEditMode('switch'); - } - if (!data.warning){ - - // Update nodes - nodes = data.nodes; - - // Update jobs - jobs = data.jobs; - - // Update graph - DrawGraph(); - } - - // We don't clear packets and RunButtonState. - // Hub can change only names - - // Ok, let's try to update host config form - let n = nodes.find(n => n.data.id === switch_id); - - if (!n) { - ClearConfigForm('Нет такого узла'); - return; - } - - if (n.config.type === 'l2_switch'){ - ShowSwitchConfig(n); - } else { - ClearConfigForm('Нет такого свитча'); - } - if (data.warning){ - SwitchWarningMsg(data.warning) - } - UpdateJobCounter('config_switch_job_counter', switch_id); - } - }, - error: function(xhr) { - console.log('Cannot update switch config'); - console.log(xhr); - // Show error message to user - let errorMsg = 'Ошибка при сохранении конфигурации свитча'; - if (xhr.responseJSON && xhr.responseJSON.message) { - errorMsg = xhr.responseJSON.message; - } - HostErrorMsg(errorMsg); - - // Exit edit mode on error to allow retry - if (editingJobId && editingDeviceType === 'switch') { - ExitEditMode('switch'); - } - }, - dataType: 'json' - }); -} - -const RunSimulation = function (network_guid) -{ - ajaxWithAuth({ - type: 'POST', - url: ExternalUrlFor('/run_simulation?guid=' + network_guid), - data: '', - success: function(data, textStatus, xhr) { - if (xhr.status === 201) - { - lastSimulationId = data.simulation_id - console.log("Simulation is running!"); - // Ok, run CheckSimulation - if (data.simulation_id) - { - CheckSimulation(data.simulation_id); - } - } - }, - error: function(err) { - console.log('Cannot run simulation guid = ' + network_guid); - SetNetworkPlayerState(-1); - }, - contentType: "application/json", - dataType: 'json' - }); -} - -const FilterPackets = function () { - const tcpRegex = /TCP \((ACK|SYN|FIN)/; - packets = packets - .map((step) => - step.filter( - (pkt) => - !( - (packetFilterState.hideARP && - pkt.data.label.startsWith("ARP")) || - (packetFilterState.hideSTP && - (pkt.data.label.startsWith("STP") || - pkt.data.label.startsWith("RSTP"))) || - (packetFilterState.hideSYN && - tcpRegex.test(pkt.data.label)) - ) - ) - ) - .filter((step) => step.length > 0); -}; - -const UpdateFilterStates = function (settings) { - if (!settings) { - return; - } - - Object.assign(packetFilterState, settings); - $("#ARPFilterCheckbox").prop("checked", packetFilterState.hideARP); - $("#STPFilterCheckbox").prop("checked", packetFilterState.hideSTP); - $("#SYNFilterCheckbox").prop("checked", packetFilterState.hideSYN); -}; - -const SaveAnimationFilters = function () { - if (!window.isAuthenticated) { - return; - } - - const payload = { - hideARP: Boolean(packetFilterState.hideARP), - hideSTP: Boolean(packetFilterState.hideSTP), - hideSYN: Boolean(packetFilterState.hideSYN), - }; - - $.ajax({ - type: "POST", - url: "/user/animation_filters", - data: JSON.stringify(payload), - contentType: "application/json; charset=utf-8", - dataType: "json", - success: function (data) { - if (!data) { - return; - } - - const saved = { - hideARP: Boolean(data.hideARP), - hideSTP: Boolean(data.hideSTP), - hideSYN: Boolean(data.hideSYN), - }; - - UpdateFilterStates(saved); - }, - error: function (xhr) { - console.log("Cannot save animation filters"); - console.log(xhr); - }, - }); -}; - -const SetPacketFilter = function (shared = 0) { - // If network player UI is absent (e.g., not on network page), skip. - if (!document.getElementById("NetworkPlayer") || !document.getElementById("PacketSliderInput")) { - return; - } - - console.log("Packet filter call"); - // SetPacketFilter first call on emulated network - if (packets && !packetsNotFiltered) { - packetsNotFiltered = JSON.parse(JSON.stringify(packets)); // Array deep copy - } - // Numerous filter call, we grab our packets copy to filter it - else if (packetsNotFiltered) { - packets = JSON.parse(JSON.stringify(packetsNotFiltered)); - } - - packetFilterState.hideARP = $("#ARPFilterCheckbox").is(":checked"); - packetFilterState.hideSTP = $("#STPFilterCheckbox").is(":checked"); - packetFilterState.hideSYN = $("#SYNFilterCheckbox").is(":checked"); - - if (packets) { - FilterPackets(); - if (shared) { - SetSharedNetworkPlayerState(); - } else { - SetNetworkPlayerState(0); - } - } -}; - -// 2 states: -// Do we need emulation -// We have a packets and ready to play packets -const SetNetworkPlayerState = function (simulation_id) { - - // Reset? - if (simulation_id === -1) { - packetsNotFiltered = null; - packets = null; - pcaps = []; - SetNetworkPlayerState(0); - return; - } - - // If we have packets, then we're ready to run - if (packets) - { - $('#NetworkPlayer').empty(); - $('#NetworkPlayer').append(''); - $('#NetworkPlayer').append(''); - - // Init player - PacketPlayer.getInstance().InitPlayer(packets); - - // Configure the slider - if (!$('#PacketSliderInput')[0] || !$('#PacketSliderInput')[0].noUiSlider) { - return; - } - - $('#PacketSliderInput')[0].noUiSlider.updateOptions({ - start: [1], - range: { - 'min': 1, - 'max': packets.length, - }, - format: { - to: function (val){return '' + val}, - from: function (val){return '' + val}, - }, - tooltips: false, - }); - - // Show Slider on - $('#PacketSliderInput').show(); - - const pkt_count = packets.reduce((currentCount, row) => currentCount + row.length, 0); - $('#NetworkPlayerLabel').text(packets.length + ' ' + NumWord(packets.length, ['шаг', 'шага', 'шагов']) + ' / ' + pkt_count + ' ' + NumWord(pkt_count, ['пакет', 'пакета', 'пакетов'])); - - $('#PacketSliderInput')[0].noUiSlider.on('slide', function (e) { - if (!e) return; - let x = Math.round(e[0]); - PacketPlayer.getInstance().setAnimationTrafficStep(x-1); - }); - - $('#PacketSliderInput')[0].noUiSlider.on('update', function (e) { - if (!e) return; - let x = Math.round(e[0]); - if (packets.length === 0){ - $('#NetworkPlayerLabel').text('0 пакетов'); - return; - } - $('#NetworkPlayerLabel').text('Шаг: ' + x + '/' + packets.length + ' (' + packets[x-1].length + ' ' + NumWord(packets[x-1].length, ['пакет', 'пакета', 'пакетов']) + ')'); - }); - - // Set click handlers - $('#NetworkPlayPauseButton').click(function() { - - // If btn-success then start to play - if ($(this).hasClass("btn-success")){ - $(this).removeClass('btn-success'); - $(this).addClass('btn-warning'); - - $(this).empty(); - $(this).append(''); - - // If not in pause. Draw a new layout and go. - if (!PacketPlayer.getInstance().getPlayerPause()) - { - DrawGraphStatic(nodes, edges); - } - - PacketPlayer.getInstance().setAnimationTrafficStepCallback(function() { - $('#PacketSliderInput')[0].noUiSlider.set(PacketPlayer.getInstance().getAnimationTrafficStep()); - }); - - PacketPlayer.getInstance().StartPlayer(global_cy); - return; - } else { - - $(this).removeClass('btn-warning'); - $(this).addClass('btn-success'); - $(this).empty(); - $(this).append(''); - - PacketPlayer.getInstance().PausePlayer(); - return; - } - }); - - $('#NetworkStopButton').click(function() { - - PacketPlayer.getInstance().resetAnimationTrafficStepCallback(); - PacketPlayer.getInstance().StopPlayer(); - - // Reset slider. - $('#PacketSliderInput')[0].noUiSlider.set(0); - - DrawGraph(nodes, edges); - - $('#NetworkPlayPauseButton').removeClass('btn-success'); - $('#NetworkPlayPauseButton').removeClass('btn-warning'); - $('#NetworkPlayPauseButton').empty(); - $('#NetworkPlayPauseButton').addClass('btn-success'); - $('#NetworkPlayPauseButton').append(''); - return; - }); - - return; - } - - // No packets. - // The network is simulating? - if (simulation_id) { - $('#NetworkPlayer').empty(); - $('#PacketSliderInput').hide(); - $('#NetworkPlayer').append(''); - InsertWaitingTime() - CheckSimulation(simulation_id); - return; - } - - // No packets and no simulation. - // Add emulation button. - $('#NetworkPlayer').empty(); - $('#PacketSliderInput').hide(); - $('#NetworkPlayer').append(''); - $('#NetworkPlayerLabel').empty(); - - $('#NetworkEmulateButton').click(function() { - - // Check for job. If no job - show modal and exit. - if (!jobs.length) - { - $('#noJobsModal').modal('toggle'); - return; - } - - if (nodes.length > 80) - { - $('#tooManyHostModal').modal('toggle'); - return; - } - - if (typeof window.ym != 'undefined') - { - ym(92293993,'reachGoal','NetworkEmulate'); - } - - RunSimulation(network_guid); - - $('#NetworkPlayer').empty(); - $('#NetworkPlayer').append(''); - InsertWaitingTime(); - return; - }); - - return; - -} - -// 2 states: -// No packets - disable button. -// We have a packets and ready to play packets -const SetSharedNetworkPlayerState = function() -{ - - // If we have packets, then we're ready to run - if (packets) - { - $('#NetworkPlayer').empty(); - $('#NetworkPlayer').append(''); - $('#NetworkPlayer').append(''); - - // Init player - PacketPlayer.getInstance().InitPlayer(packets); - - // Configure the slider - $('#PacketSliderInput')[0].noUiSlider.updateOptions({ - start: [1], - range: { - 'min': 1, - 'max': packets.length, - }, - format: { - to: function (val){return '' + val}, - from: function (val){return '' + val}, - }, - tooltips: false, - }); - - // Show Slider on - $('#PacketSliderInput').show(); - - const pkt_count = packets.reduce((currentCount, row) => currentCount + row.length, 0); - $('#NetworkPlayerLabel').text(packets.length + ' ' + NumWord(packets.length, ['шаг', 'шага', 'шагов']) + ' / ' + pkt_count + ' ' + NumWord(pkt_count, ['пакет', 'пакета', 'пакетов'])); - - $('#PacketSliderInput')[0].noUiSlider.on('slide', function (e) { - if (!e) return; - let x = Math.round(e[0]); - PacketPlayer.getInstance().setAnimationTrafficStep(x-1); - }); - - $('#PacketSliderInput')[0].noUiSlider.on('update', function (e) { - if (!e) return; - let x = Math.round(e[0]); - if (packets.length === 0){ - $('#NetworkPlayerLabel').text('0 пакетов'); - return; - } - $('#NetworkPlayerLabel').text('Шаг: ' + x + '/' + packets.length + ' (' + packets[x-1].length + ' ' + NumWord(packets[x-1].length, ['пакет', 'пакета', 'пакетов']) + ')'); - }); - - // Set click handlers - $('#NetworkPlayPauseButton').click(function() { - - // If btn-success then start to play - if ($(this).hasClass("btn-success")){ - $(this).removeClass('btn-success'); - $(this).addClass('btn-warning'); - - $(this).empty(); - $(this).append(''); - - // If not in pause. Draw a new layout and go. - if (!PacketPlayer.getInstance().getPlayerPause()) - { - DrawGraphStatic(nodes, edges); - } - - PacketPlayer.getInstance().setAnimationTrafficStepCallback(function() { - $('#PacketSliderInput')[0].noUiSlider.set(PacketPlayer.getInstance().getAnimationTrafficStep()); - }); - - PacketPlayer.getInstance().StartPlayer(global_cy); - } else { - $(this).removeClass('btn-warning'); - $(this).addClass('btn-success'); - $(this).empty(); - $(this).append(''); - - PacketPlayer.getInstance().PausePlayer(); - return; - } - }); - - $('#NetworkStopButton').click(function() { - - PacketPlayer.getInstance().resetAnimationTrafficStepCallback(); - PacketPlayer.getInstance().StopPlayer(); - - // Reset slider. - $('#PacketSliderInput')[0].noUiSlider.set(0); - - DrawSharedGraph(nodes, edges); - - $('#NetworkPlayPauseButton').removeClass('btn-success'); - $('#NetworkPlayPauseButton').removeClass('btn-warning'); - $('#NetworkPlayPauseButton').empty(); - $('#NetworkPlayPauseButton').addClass('btn-success'); - $('#NetworkPlayPauseButton').append(''); - return; - }); - - return; - } - - // No packets - // Add info button - $('#NetworkPlayer').empty(); - $('#PacketSliderInput').hide(); - $('#NetworkPlayerLabel').empty(); - $('#NetworkPlayer').append(''); - return; -} - -// Take a picture and update it. -const TakeGraphPictureAndUpdate = function() -{ - if (!global_cy) - { - return; - } - - let png_blob = global_cy.png({output: 'blob', maxWidth: 512, maxHeight: 512}); - - ajaxWithAuth({ - type: 'POST', - url: ExternalUrlFor('/network/upload_network_picture?guid=' + network_guid), - data: png_blob, - processData: false, - error: function(xhr) { - - if (xhr.status != 200){ - console.log('Cannot upload graph picture'); - } - - }, - dataType: 'image/png' - }); -} - -// Calculate drop offsets -const CalculateDropOffset = function(elem_x, elem_y) -{ - const network_scheme = document.getElementById("network_scheme"); - let offset_left = 0; - let offset_top = 0; - let ret = {'x' : 0, 'y' : 0}; - - console.log(elem_x + ", " + elem_y); - - if (network_scheme){ - ret.x += network_scheme.offsetLeft - 25; - ret.y += network_scheme.offsetTop - 15; - } - - if (global_cy) - { - ret.x = ret.x + global_cy.pan().x; - ret.y = ret.y + global_cy.pan().y; - - ret.x = (elem_x - ret.x) / global_cy.zoom(); - ret.y = (elem_y - ret.y) / global_cy.zoom(); - - // Apply snap-to-grid - const baseGridSize = 25; - ret.x = Math.round(ret.x / baseGridSize) * baseGridSize; - ret.y = Math.round(ret.y / baseGridSize) * baseGridSize; - } - - return ret; -} - -const UpdateNetworkConfig = function() -{ - if (!global_cy){ - return; - } - - let data = {'network_title' : network_title, 'network_description' : network_description, - 'zoom' : global_cy.zoom(),'pan_x' : global_cy.pan().x, 'pan_y' : global_cy.pan().y}; - - ajaxWithAuth({ - type: 'POST', - url: ExternalUrlFor('/network/update_network_config?guid=' + network_guid), - data: JSON.stringify(data), - contentType: "application/json; charset=utf-8", - success: function(data, textStatus, xhr) { - }, - error: function(xhr) { - console.log('Cannot update network config'); - console.log(xhr); - }, - dataType: 'json' - }); - -} - -const CopyNetwork = function () -{ - ajaxWithAuth({ - type: 'POST', - url: ExternalUrlFor('/network/copy_network?guid=' + network_guid), - data: '', - success: function(data, textStatus, xhr) { - if (xhr.status === 200) - { - console.log("Copy network is made."); - $('#ModalCopy').modal('show'); - $('.modal-option').click(function() { - var selectedOption = $(this).attr('data-option'); - if (selectedOption === 'edit') { - var newUrl = data.new_url; - window.location.href = newUrl; - console.log('Go to editing'); - } else if (selectedOption === 'continue') { - console.log('Continue here'); - } - $('#ModalCopy').modal('hide'); - }); - } - }, - error: function(err) { - console.log('Copy has not been made.'); - }, - contentType: "application/json", - dataType: 'json' - }); -} - - -const NumWord = function (value, words){ - value = Math.abs(value) % 100; - var num = value % 10; - if(value > 10 && value < 20) return words[2]; - if(num > 1 && num < 5) return words[1]; - if(num == 1) return words[0]; - return words[2]; -} - -const SaveNetworkObject = function (){ - let n = JSON.parse(JSON.stringify(nodes)); - let e = JSON.parse(JSON.stringify(edges)); - - NetworkCache.push({ - nodes: n, - edges: e, - }); - - return 0; -} - -const RestoreNetworkObject = function (){ - let x = NetworkCache.pop(); - - if (!x){ - return; - } - - nodes=x.nodes; - edges=x.edges; - - return 0; -} - -// ========== COMMAND EDITING UTILITIES ========== -// Global variables to track editing state -let editingJobId = null; -let editingDeviceType = null; - -// Function to enter edit mode -const EnterEditMode = function(deviceType, jobId, jobTypeId) { - editingJobId = jobId; - editingDeviceType = deviceType; - - // Change submit button text - const submitButton = document.getElementById(`config_${deviceType}_main_form_submit_button`); - if (submitButton) { - submitButton.textContent = 'Сохранить изменения'; - } - - // Change label text from "Выполнить команду" to "Редактировать команду" - const selectLabel = $(`label[for="config_${deviceType}_job_select_field"]`); - if (selectLabel.length) { - selectLabel.text('Редактировать команду'); - } - - // Hide the select dropdown and show command name - const selectField = document.getElementById(`config_${deviceType}_job_select_field`); - if (selectField) { - selectField.style.display = 'none'; - - // Remove old command display if exists - const existingDisplay = document.getElementById(`config_${deviceType}_edit_command_display`); - if (existingDisplay) { - existingDisplay.remove(); - } - - // Get command name from the selected option in HTML - const selectedOption = selectField.querySelector(`option[value="${jobTypeId}"]`); - const commandName = selectedOption ? selectedOption.textContent : 'Команда'; - - // Create and insert command name display - const commandDisplay = document.createElement('input'); - commandDisplay.type = 'text'; - commandDisplay.id = `config_${deviceType}_edit_command_display`; - commandDisplay.className = 'form-control form-control-sm'; - commandDisplay.value = commandName; - commandDisplay.disabled = true; - selectField.parentNode.insertBefore(commandDisplay, selectField.nextSibling); - } - - // Highlight the editing command - $(`#config_${deviceType}_job_list li`).removeClass('editing-command'); - const listItem = $(`#config_${deviceType}_job_delete_${jobId}`).closest('li'); - listItem.addClass('editing-command'); - - // Highlight only the input fields area after it's inserted into DOM - setTimeout(() => { - const jobList = document.getElementById(`config_${deviceType}_job_list`); - if (jobList) { - const inputDiv = $(jobList).prev(`div[name="config_${deviceType}_select_input"]`); - if (inputDiv.length) { - inputDiv.addClass('editing-form-area'); - } - } - - // Scroll to the "Редактировать команду" label (select field) - // This helps when user clicks edit on a command at the bottom of the list - const selectLabel = $(`label[for="config_${deviceType}_job_select_field"]`); - if (selectLabel.length) { - selectLabel[0].scrollIntoView({ - behavior: 'smooth', - block: 'start', - inline: 'nearest' - }); - } - }, 50); -}; - -// Function to exit edit mode -const ExitEditMode = function(deviceType) { - editingJobId = null; - editingDeviceType = null; - - // Reset submit button text - const submitButton = document.getElementById(`config_${deviceType}_main_form_submit_button`); - if (submitButton) { - submitButton.textContent = 'Сохранить'; - } - - // Reset label text back to "Выполнить команду" - const selectLabel = $(`label[for="config_${deviceType}_job_select_field"]`); - if (selectLabel.length) { - selectLabel.text('Выполнить команду'); - } - - // Remove command text display - const commandDisplay = document.getElementById(`config_${deviceType}_edit_command_display`); - if (commandDisplay) { - commandDisplay.remove(); - } - - // Show the select dropdown again - const selectField = document.getElementById(`config_${deviceType}_job_select_field`); - if (selectField) { - selectField.style.display = 'block'; - selectField.value = '0'; - } - - // Remove highlight from command and input areas - $(`#config_${deviceType}_job_list li`).removeClass('editing-command'); - $(`div[name="config_${deviceType}_select_input"]`).removeClass('editing-form-area'); - - // Clear form inputs - $('div[name="config_' + deviceType + '_select_input"]').remove(); -}; - -// Function to delete old job and save new configuration -const DeleteAndSaveJob = function(deviceType, updateFunction, formData, deviceId) { - if (!editingJobId || editingDeviceType !== deviceType) { - // Not in edit mode, just save - updateFunction(formData, deviceId); - return; - } - - // In edit mode: pass editing_job_id to server - // Server will validate first, then delete old and add new atomically - formData += '&editing_job_id=' + encodeURIComponent(editingJobId); - - updateFunction(formData, deviceId); -}; - -// Grid drawing functions -const initGrid = function(cy) { - if (!cy) return; - - // Clean up previous listener - if (typeof gridCanvasLayer !== 'undefined' && gridCanvasLayer && gridCanvasLayer.resizeAndDrawCanvas) { - window.removeEventListener('resize', gridCanvasLayer.resizeAndDrawCanvas); - } - - // Remove old grid canvas if exists - const oldCanvas = document.getElementById('grid-canvas-static'); - if (oldCanvas) { - oldCanvas.remove(); - } - - // Create canvas with absolute positioning to overlay on top of cytoscape container - const canvas = document.createElement('canvas'); - canvas.id = 'grid-canvas-static'; - canvas.style.position = 'absolute'; - canvas.style.top = '0'; - canvas.style.left = '0'; - canvas.style.width = '100%'; - canvas.style.height = '100%'; - canvas.style.pointerEvents = 'none'; - - const container = cy.container(); - container.insertBefore(canvas, container.firstChild); - - const ctx = canvas.getContext('2d'); - - const resizeAndDrawCanvas = function() { - const pixelRatio = window.devicePixelRatio || 1; - - // Use container dimensions instead of window dimensions to prevent distortion - // when container is not full screen - canvas.width = container.clientWidth * pixelRatio; - canvas.height = container.clientHeight * pixelRatio; - - // Always redraw when resizing - if (gridCanvasLayer) { - drawGrid(); - } - }; - - gridCanvasLayer = { - canvas: canvas, - ctx: ctx, - resizeAndDrawCanvas: resizeAndDrawCanvas - }; - - resizeAndDrawCanvas(); - - // Add event listener for resize - window.addEventListener('resize', resizeAndDrawCanvas); - - // Add cy resize listener to handle container resizing specifically - if (cy) { - cy.on('resize', resizeAndDrawCanvas); - cy.on('viewport', function() { - currentGridZoom = cy.zoom(); - drawGrid(); - }); - } - - // Initialize current zoom from cytoscape - if (cy && cy.zoom) { - currentGridZoom = cy.zoom(); - } - - // Draw grid - drawGrid(); -}; - -const drawGrid = function() { - if (!gridCanvasLayer) { - return; - } - - const canvas = gridCanvasLayer.canvas; - const ctx = gridCanvasLayer.ctx; - - if (!canvas || !ctx) { - return; - } - - // Scale grid with zoom: at max zoom (2.0) = 50px like before, at min zoom (0.5) = small cells - const gridSize = 25 * currentGridZoom; // 25 * 2.0 = 50px (max zoom), 25 * 0.5 = 12.5px (min zoom) - const pixelRatio = window.devicePixelRatio || 1; - - ctx.clearRect(0, 0, canvas.width, canvas.height); - - const screenWidth = canvas.width / pixelRatio; - const screenHeight = canvas.height / pixelRatio; - - // Get pan offset to align grid with cytoscape coordinate system - let panX = 0; - let panY = 0; - if (global_cy && global_cy.pan) { - const pan = global_cy.pan(); - panX = pan.x; - panY = pan.y; - } - - ctx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0); - - // Draw grid lines across entire viewport - ctx.strokeStyle = 'rgba(200, 200, 200, 0.4)'; - ctx.lineWidth = 1; - - ctx.beginPath(); - - // Calculate grid origin with pan offset - // Grid should be offset by pan to stay aligned with nodes - const gridOriginX = panX % gridSize; - const gridOriginY = panY % gridSize; - - // Vertical lines across entire viewport - let verticalCount = 0; - const startX = Math.floor(-gridOriginX / gridSize) * gridSize + gridOriginX; - for (let x = startX; x <= screenWidth; x += gridSize) { - ctx.moveTo(x, 0); - ctx.lineTo(x, screenHeight); - verticalCount++; - } - - // Horizontal lines across entire viewport - let horizontalCount = 0; - const startY = Math.floor(-gridOriginY / gridSize) * gridSize + gridOriginY; - for (let y = startY; y <= screenHeight; y += gridSize) { - ctx.moveTo(0, y); - ctx.lineTo(screenWidth, y); - horizontalCount++; - } - - ctx.stroke(); -}; - - -// Update grid when config panel opens/closes -const updateGridForConfigPanel = function() { - if (gridCanvasLayer && gridCanvasLayer.resizeAndDrawCanvas) { - // Small delay to let DOM update - setTimeout(function() { - gridCanvasLayer.resizeAndDrawCanvas(); - }, 50); - } -} diff --git a/front/src/templates/base.html b/front/src/templates/base.html index ed785be5..919e1d4c 100644 --- a/front/src/templates/base.html +++ b/front/src/templates/base.html @@ -323,7 +323,8 @@
Наше компьюнити
- + + - diff --git a/front/src/templates/quiz/networkBase.html b/front/src/templates/quiz/networkBase.html index 6e3c2c68..cfe6d07a 100644 --- a/front/src/templates/quiz/networkBase.html +++ b/front/src/templates/quiz/networkBase.html @@ -163,7 +163,8 @@ - + + - diff --git a/front/uv.lock b/front/uv.lock new file mode 100644 index 00000000..9b50e3d7 --- /dev/null +++ b/front/uv.lock @@ -0,0 +1,1261 @@ +version = 1 +revision = 3 +requires-python = ">=3.11, <3.13" + +[[package]] +name = "alembic" +version = "1.16.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mako" }, + { name = "sqlalchemy" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9a/ca/4dc52902cf3491892d464f5265a81e9dff094692c8a049a3ed6a05fe7ee8/alembic-1.16.5.tar.gz", hash = "sha256:a88bb7f6e513bd4301ecf4c7f2206fe93f9913f9b48dac3b78babde2d6fe765e", size = 1969868, upload-time = "2025-08-27T18:02:05.668Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/4a/4c61d4c84cfd9befb6fa08a702535b27b21fff08c946bc2f6139decbf7f7/alembic-1.16.5-py3-none-any.whl", hash = "sha256:e845dfe090c5ffa7b92593ae6687c5cb1a101e91fa53868497dbd79847f9dbe3", size = 247355, upload-time = "2025-08-27T18:02:07.37Z" }, +] + +[[package]] +name = "amqp" +version = "5.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "vine" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/79/fc/ec94a357dfc6683d8c86f8b4cfa5416a4c36b28052ec8260c77aca96a443/amqp-5.3.1.tar.gz", hash = "sha256:cddc00c725449522023bad949f70fff7b48f0b1ade74d170a6f10ab044739432", size = 129013, upload-time = "2024-11-12T19:55:44.051Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/99/fc813cd978842c26c82534010ea849eee9ab3a13ea2b74e95cb9c99e747b/amqp-5.3.1-py3-none-any.whl", hash = "sha256:43b3319e1b4e7d1251833a93d672b4af1e40f3d632d479b98661a95f117880a2", size = 50944, upload-time = "2024-11-12T19:55:41.782Z" }, +] + +[[package]] +name = "attrs" +version = "26.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", size = 952055, upload-time = "2026-03-19T14:22:25.026Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, +] + +[[package]] +name = "beautifulsoup4" +version = "4.13.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "soupsieve" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/85/2e/3e5079847e653b1f6dc647aa24549d68c6addb4c595cc0d902d1b19308ad/beautifulsoup4-4.13.5.tar.gz", hash = "sha256:5e70131382930e7c3de33450a2f54a63d5e4b19386eab43a5b34d594268f3695", size = 622954, upload-time = "2025-08-24T14:06:13.168Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/eb/f4151e0c7377a6e08a38108609ba5cede57986802757848688aeedd1b9e8/beautifulsoup4-4.13.5-py3-none-any.whl", hash = "sha256:642085eaa22233aceadff9c69651bc51e8bf3f874fb6d7104ece2beb24b47c4a", size = 105113, upload-time = "2025-08-24T14:06:14.884Z" }, +] + +[[package]] +name = "billiard" +version = "4.2.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/23/b12ac0bcdfb7360d664f40a00b1bda139cbbbced012c34e375506dbd0143/billiard-4.2.4.tar.gz", hash = "sha256:55f542c371209e03cd5862299b74e52e4fbcba8250ba611ad94276b369b6a85f", size = 156537, upload-time = "2025-11-30T13:28:48.52Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/87/8bab77b323f16d67be364031220069f79159117dd5e43eeb4be2fef1ac9b/billiard-4.2.4-py3-none-any.whl", hash = "sha256:525b42bdec68d2b983347ac312f892db930858495db601b5836ac24e6477cde5", size = 87070, upload-time = "2025-11-30T13:28:47.016Z" }, +] + +[[package]] +name = "blinker" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/28/9b3f50ce0e048515135495f198351908d99540d69bfdc8c1d15b73dc55ce/blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", size = 22460, upload-time = "2024-11-08T17:25:47.436Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc", size = 8458, upload-time = "2024-11-08T17:25:46.184Z" }, +] + +[[package]] +name = "cachecontrol" +version = "0.14.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "msgpack" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2d/f6/c972b32d80760fb79d6b9eeb0b3010a46b89c0b23cf6329417ff7886cd22/cachecontrol-0.14.4.tar.gz", hash = "sha256:e6220afafa4c22a47dd0badb319f84475d79108100d04e26e8542ef7d3ab05a1", size = 16150, upload-time = "2025-11-14T04:32:13.138Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/79/c45f2d53efe6ada1110cf6f9fca095e4ff47a0454444aefdde6ac4789179/cachecontrol-0.14.4-py3-none-any.whl", hash = "sha256:b7ac014ff72ee199b5f8af1de29d60239954f223e948196fa3d84adaffc71d2b", size = 22247, upload-time = "2025-11-14T04:32:11.733Z" }, +] + +[[package]] +name = "cachetools" +version = "5.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/81/3747dad6b14fa2cf53fcf10548cf5aea6913e96fab41a3c198676f8948a5/cachetools-5.5.2.tar.gz", hash = "sha256:1a661caa9175d26759571b2e19580f9d6393969e5dfca11fdb1f947a23e640d4", size = 28380, upload-time = "2025-02-20T21:01:19.524Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/72/76/20fa66124dbe6be5cafeb312ece67de6b61dd91a0247d1ea13db4ebb33c2/cachetools-5.5.2-py3-none-any.whl", hash = "sha256:d26a22bcc62eb95c3beabd9f1ee5e820d3d2704fe2967cbe350e20c8ffcd3f0a", size = 10080, upload-time = "2025-02-20T21:01:16.647Z" }, +] + +[[package]] +name = "celery" +version = "5.5.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "billiard" }, + { name = "click" }, + { name = "click-didyoumean" }, + { name = "click-plugins" }, + { name = "click-repl" }, + { name = "kombu" }, + { name = "python-dateutil" }, + { name = "vine" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bb/7d/6c289f407d219ba36d8b384b42489ebdd0c84ce9c413875a8aae0c85f35b/celery-5.5.3.tar.gz", hash = "sha256:6c972ae7968c2b5281227f01c3a3f984037d21c5129d07bf3550cc2afc6b10a5", size = 1667144, upload-time = "2025-06-01T11:08:12.563Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c9/af/0dcccc7fdcdf170f9a1585e5e96b6fb0ba1749ef6be8c89a6202284759bd/celery-5.5.3-py3-none-any.whl", hash = "sha256:0b5761a07057acee94694464ca482416b959568904c9dfa41ce8413a7d65d525", size = 438775, upload-time = "2025-06-01T11:08:09.94Z" }, +] + +[[package]] +name = "certifi" +version = "2025.10.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/5b/b6ce21586237c77ce67d01dc5507039d444b630dd76611bbca2d8e5dcd91/certifi-2025.10.5.tar.gz", hash = "sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43", size = 164519, upload-time = "2025-10-05T04:12:15.808Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-py3-none-any.whl", hash = "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de", size = 163286, upload-time = "2025-10-05T04:12:14.03Z" }, +] + +[[package]] +name = "cffi" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076, upload-time = "2025-09-08T23:22:40.95Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820, upload-time = "2025-09-08T23:22:42.463Z" }, + { url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635, upload-time = "2025-09-08T23:22:43.623Z" }, + { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, + { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/83/2d/5fd176ceb9b2fc619e63405525573493ca23441330fcdaee6bef9460e924/charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14", size = 122371, upload-time = "2025-08-09T07:57:28.46Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/b5/991245018615474a60965a7c9cd2b4efbaabd16d582a5547c47ee1c7730b/charset_normalizer-3.4.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b256ee2e749283ef3ddcff51a675ff43798d92d746d1a6e4631bf8c707d22d0b", size = 204483, upload-time = "2025-08-09T07:55:53.12Z" }, + { url = "https://files.pythonhosted.org/packages/c7/2a/ae245c41c06299ec18262825c1569c5d3298fc920e4ddf56ab011b417efd/charset_normalizer-3.4.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13faeacfe61784e2559e690fc53fa4c5ae97c6fcedb8eb6fb8d0a15b475d2c64", size = 145520, upload-time = "2025-08-09T07:55:54.712Z" }, + { url = "https://files.pythonhosted.org/packages/3a/a4/b3b6c76e7a635748c4421d2b92c7b8f90a432f98bda5082049af37ffc8e3/charset_normalizer-3.4.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:00237675befef519d9af72169d8604a067d92755e84fe76492fef5441db05b91", size = 158876, upload-time = "2025-08-09T07:55:56.024Z" }, + { url = "https://files.pythonhosted.org/packages/e2/e6/63bb0e10f90a8243c5def74b5b105b3bbbfb3e7bb753915fe333fb0c11ea/charset_normalizer-3.4.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:585f3b2a80fbd26b048a0be90c5aae8f06605d3c92615911c3a2b03a8a3b796f", size = 156083, upload-time = "2025-08-09T07:55:57.582Z" }, + { url = "https://files.pythonhosted.org/packages/87/df/b7737ff046c974b183ea9aa111b74185ac8c3a326c6262d413bd5a1b8c69/charset_normalizer-3.4.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e78314bdc32fa80696f72fa16dc61168fda4d6a0c014e0380f9d02f0e5d8a07", size = 150295, upload-time = "2025-08-09T07:55:59.147Z" }, + { url = "https://files.pythonhosted.org/packages/61/f1/190d9977e0084d3f1dc169acd060d479bbbc71b90bf3e7bf7b9927dec3eb/charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:96b2b3d1a83ad55310de8c7b4a2d04d9277d5591f40761274856635acc5fcb30", size = 148379, upload-time = "2025-08-09T07:56:00.364Z" }, + { url = "https://files.pythonhosted.org/packages/4c/92/27dbe365d34c68cfe0ca76f1edd70e8705d82b378cb54ebbaeabc2e3029d/charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:939578d9d8fd4299220161fdd76e86c6a251987476f5243e8864a7844476ba14", size = 160018, upload-time = "2025-08-09T07:56:01.678Z" }, + { url = "https://files.pythonhosted.org/packages/99/04/baae2a1ea1893a01635d475b9261c889a18fd48393634b6270827869fa34/charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:fd10de089bcdcd1be95a2f73dbe6254798ec1bda9f450d5828c96f93e2536b9c", size = 157430, upload-time = "2025-08-09T07:56:02.87Z" }, + { url = "https://files.pythonhosted.org/packages/2f/36/77da9c6a328c54d17b960c89eccacfab8271fdaaa228305330915b88afa9/charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1e8ac75d72fa3775e0b7cb7e4629cec13b7514d928d15ef8ea06bca03ef01cae", size = 151600, upload-time = "2025-08-09T07:56:04.089Z" }, + { url = "https://files.pythonhosted.org/packages/64/d4/9eb4ff2c167edbbf08cdd28e19078bf195762e9bd63371689cab5ecd3d0d/charset_normalizer-3.4.3-cp311-cp311-win32.whl", hash = "sha256:6cf8fd4c04756b6b60146d98cd8a77d0cdae0e1ca20329da2ac85eed779b6849", size = 99616, upload-time = "2025-08-09T07:56:05.658Z" }, + { url = "https://files.pythonhosted.org/packages/f4/9c/996a4a028222e7761a96634d1820de8a744ff4327a00ada9c8942033089b/charset_normalizer-3.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:31a9a6f775f9bcd865d88ee350f0ffb0e25936a7f930ca98995c05abf1faf21c", size = 107108, upload-time = "2025-08-09T07:56:07.176Z" }, + { url = "https://files.pythonhosted.org/packages/e9/5e/14c94999e418d9b87682734589404a25854d5f5d0408df68bc15b6ff54bb/charset_normalizer-3.4.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1", size = 205655, upload-time = "2025-08-09T07:56:08.475Z" }, + { url = "https://files.pythonhosted.org/packages/7d/a8/c6ec5d389672521f644505a257f50544c074cf5fc292d5390331cd6fc9c3/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884", size = 146223, upload-time = "2025-08-09T07:56:09.708Z" }, + { url = "https://files.pythonhosted.org/packages/fc/eb/a2ffb08547f4e1e5415fb69eb7db25932c52a52bed371429648db4d84fb1/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018", size = 159366, upload-time = "2025-08-09T07:56:11.326Z" }, + { url = "https://files.pythonhosted.org/packages/82/10/0fd19f20c624b278dddaf83b8464dcddc2456cb4b02bb902a6da126b87a1/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392", size = 157104, upload-time = "2025-08-09T07:56:13.014Z" }, + { url = "https://files.pythonhosted.org/packages/16/ab/0233c3231af734f5dfcf0844aa9582d5a1466c985bbed6cedab85af9bfe3/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f", size = 151830, upload-time = "2025-08-09T07:56:14.428Z" }, + { url = "https://files.pythonhosted.org/packages/ae/02/e29e22b4e02839a0e4a06557b1999d0a47db3567e82989b5bb21f3fbbd9f/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154", size = 148854, upload-time = "2025-08-09T07:56:16.051Z" }, + { url = "https://files.pythonhosted.org/packages/05/6b/e2539a0a4be302b481e8cafb5af8792da8093b486885a1ae4d15d452bcec/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491", size = 160670, upload-time = "2025-08-09T07:56:17.314Z" }, + { url = "https://files.pythonhosted.org/packages/31/e7/883ee5676a2ef217a40ce0bffcc3d0dfbf9e64cbcfbdf822c52981c3304b/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93", size = 158501, upload-time = "2025-08-09T07:56:18.641Z" }, + { url = "https://files.pythonhosted.org/packages/c1/35/6525b21aa0db614cf8b5792d232021dca3df7f90a1944db934efa5d20bb1/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f", size = 153173, upload-time = "2025-08-09T07:56:20.289Z" }, + { url = "https://files.pythonhosted.org/packages/50/ee/f4704bad8201de513fdc8aac1cabc87e38c5818c93857140e06e772b5892/charset_normalizer-3.4.3-cp312-cp312-win32.whl", hash = "sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37", size = 99822, upload-time = "2025-08-09T07:56:21.551Z" }, + { url = "https://files.pythonhosted.org/packages/39/f5/3b3836ca6064d0992c58c7561c6b6eee1b3892e9665d650c803bd5614522/charset_normalizer-3.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc", size = 107543, upload-time = "2025-08-09T07:56:23.115Z" }, + { url = "https://files.pythonhosted.org/packages/8a/1f/f041989e93b001bc4e44bb1669ccdcf54d3f00e628229a85b08d330615c5/charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a", size = 53175, upload-time = "2025-08-09T07:57:26.864Z" }, +] + +[[package]] +name = "click" +version = "8.1.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593, upload-time = "2024-12-21T18:38:44.339Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188, upload-time = "2024-12-21T18:38:41.666Z" }, +] + +[[package]] +name = "click-didyoumean" +version = "0.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/30/ce/217289b77c590ea1e7c24242d9ddd6e249e52c795ff10fac2c50062c48cb/click_didyoumean-0.3.1.tar.gz", hash = "sha256:4f82fdff0dbe64ef8ab2279bd6aa3f6a99c3b28c05aa09cbfc07c9d7fbb5a463", size = 3089, upload-time = "2024-03-24T08:22:07.499Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1b/5b/974430b5ffdb7a4f1941d13d83c64a0395114503cc357c6b9ae4ce5047ed/click_didyoumean-0.3.1-py3-none-any.whl", hash = "sha256:5c4bb6007cfea5f2fd6583a2fb6701a22a41eb98957e63d0fac41c10e7c3117c", size = 3631, upload-time = "2024-03-24T08:22:06.356Z" }, +] + +[[package]] +name = "click-plugins" +version = "1.1.1.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c3/a4/34847b59150da33690a36da3681d6bbc2ec14ee9a846bc30a6746e5984e4/click_plugins-1.1.1.2.tar.gz", hash = "sha256:d7af3984a99d243c131aa1a828331e7630f4a88a9741fd05c927b204bcf92261", size = 8343, upload-time = "2025-06-25T00:47:37.555Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/9a/2abecb28ae875e39c8cad711eb1186d8d14eab564705325e77e4e6ab9ae5/click_plugins-1.1.1.2-py2.py3-none-any.whl", hash = "sha256:008d65743833ffc1f5417bf0e78e8d2c23aab04d9745ba817bd3e71b0feb6aa6", size = 11051, upload-time = "2025-06-25T00:47:36.731Z" }, +] + +[[package]] +name = "click-repl" +version = "0.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "prompt-toolkit" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/a2/57f4ac79838cfae6912f997b4d1a64a858fb0c86d7fcaae6f7b58d267fca/click-repl-0.3.0.tar.gz", hash = "sha256:17849c23dba3d667247dc4defe1757fff98694e90fe37474f3feebb69ced26a9", size = 10449, upload-time = "2023-06-15T12:43:51.141Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/40/9d857001228658f0d59e97ebd4c346fe73e138c6de1bce61dc568a57c7f8/click_repl-0.3.0-py3-none-any.whl", hash = "sha256:fb7e06deb8da8de86180a33a9da97ac316751c094c6899382da7feeeeb51b812", size = 10289, upload-time = "2023-06-15T12:43:48.626Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "dpkt" +version = "1.9.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/7d/52f17a794db52a66e46ebb0c7549bf2f035ed61d5a920ba4aaa127dd038e/dpkt-1.9.8.tar.gz", hash = "sha256:43f8686e455da5052835fd1eda2689d51de3670aac9799b1b00cfd203927ee45", size = 180073, upload-time = "2022-08-18T05:54:13.582Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/79/479e2194c9096b92aecdf33634ae948d2be306c6011673e98ee1917f32c2/dpkt-1.9.8-py3-none-any.whl", hash = "sha256:4da4d111d7bf67575b571f5c678c71bddd2d8a01a3d57d489faf0a92c748fbfd", size = 194973, upload-time = "2022-08-18T05:54:10.793Z" }, +] + +[[package]] +name = "flask" +version = "3.1.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "blinker" }, + { name = "click" }, + { name = "itsdangerous" }, + { name = "jinja2" }, + { name = "markupsafe" }, + { name = "werkzeug" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dc/6d/cfe3c0fcc5e477df242b98bfe186a4c34357b4847e87ecaef04507332dab/flask-3.1.2.tar.gz", hash = "sha256:bf656c15c80190ed628ad08cdfd3aaa35beb087855e2f494910aa3774cc4fd87", size = 720160, upload-time = "2025-08-19T21:03:21.205Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/f9/7f9263c5695f4bd0023734af91bedb2ff8209e8de6ead162f35d8dc762fd/flask-3.1.2-py3-none-any.whl", hash = "sha256:ca1d8112ec8a6158cc29ea4858963350011b5c846a414cdb7a954aa9e967d03c", size = 103308, upload-time = "2025-08-19T21:03:19.499Z" }, +] + +[[package]] +name = "flask-admin" +version = "2.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "flask" }, + { name = "jinja2" }, + { name = "markupsafe" }, + { name = "werkzeug" }, + { name = "wtforms" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/5f/318ac7beac7e8511e0aa1dd1781dc112e44061c3dfcfeebfacdef2862508/flask_admin-2.0.1.tar.gz", hash = "sha256:ab83a435942c2a6af83e80f306b18786e459887564a18cb8fb45c31af8a4929b", size = 5528560, upload-time = "2025-11-02T13:00:24.307Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/fc/69007b74724738f8a39c5acebafef0e97b22260e0e5621b65e2883270e8a/flask_admin-2.0.1-py3-none-any.whl", hash = "sha256:3bebd383322d680f46793bc59325e991d99f5b5e0ddb6db05e759c824f372f1c", size = 6458579, upload-time = "2025-11-02T13:00:21.83Z" }, +] + +[[package]] +name = "flask-cors" +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "flask" }, + { name = "werkzeug" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/70/74/0fc0fa68d62f21daef41017dafab19ef4b36551521260987eb3a5394c7ba/flask_cors-6.0.2.tar.gz", hash = "sha256:6e118f3698249ae33e429760db98ce032a8bf9913638d085ca0f4c5534ad2423", size = 13472, upload-time = "2025-12-12T20:31:42.861Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4f/af/72ad54402e599152de6d067324c46fe6a4f531c7c65baf7e96c63db55eaf/flask_cors-6.0.2-py3-none-any.whl", hash = "sha256:e57544d415dfd7da89a9564e1e3a9e515042df76e12130641ca6f3f2f03b699a", size = 13257, upload-time = "2025-12-12T20:31:41.3Z" }, +] + +[[package]] +name = "flask-jwt-extended" +version = "4.7.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "flask" }, + { name = "pyjwt" }, + { name = "werkzeug" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/51/16/96b101f18cba17ecce3225ab07bc4c8f23e6befd8552dbbed87482e7c7fb/flask_jwt_extended-4.7.1.tar.gz", hash = "sha256:8085d6757505b6f3291a2638c84d207e8f0ad0de662d1f46aa2f77e658a0c976", size = 34411, upload-time = "2024-11-20T23:44:41.044Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/67/34/9a91da47b1565811ab4aa5fb134632c8d1757960bfa7d457f486947c4d75/Flask_JWT_Extended-4.7.1-py2.py3-none-any.whl", hash = "sha256:52f35bf0985354d7fb7b876e2eb0e0b141aaff865a22ff6cc33d9a18aa987978", size = 22588, upload-time = "2024-11-20T23:44:39.435Z" }, +] + +[[package]] +name = "flask-login" +version = "0.6.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "flask" }, + { name = "werkzeug" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c3/6e/2f4e13e373bb49e68c02c51ceadd22d172715a06716f9299d9df01b6ddb2/Flask-Login-0.6.3.tar.gz", hash = "sha256:5e23d14a607ef12806c699590b89d0f0e0d67baeec599d75947bf9c147330333", size = 48834, upload-time = "2023-10-30T14:53:21.151Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/59/f5/67e9cc5c2036f58115f9fe0f00d203cf6780c3ff8ae0e705e7a9d9e8ff9e/Flask_Login-0.6.3-py3-none-any.whl", hash = "sha256:849b25b82a436bf830a054e74214074af59097171562ab10bfa999e6b78aae5d", size = 17303, upload-time = "2023-10-30T14:53:19.636Z" }, +] + +[[package]] +name = "flask-migrate" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "alembic" }, + { name = "flask" }, + { name = "flask-sqlalchemy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5a/8e/47c7b3c93855ceffc2eabfa271782332942443321a07de193e4198f920cf/flask_migrate-4.1.0.tar.gz", hash = "sha256:1a336b06eb2c3ace005f5f2ded8641d534c18798d64061f6ff11f79e1434126d", size = 21965, upload-time = "2025-01-10T18:51:11.848Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/c4/3f329b23d769fe7628a5fc57ad36956f1fb7132cf8837be6da762b197327/Flask_Migrate-4.1.0-py3-none-any.whl", hash = "sha256:24d8051af161782e0743af1b04a152d007bad9772b2bca67b7ec1e8ceeb3910d", size = 21237, upload-time = "2025-01-10T18:51:09.527Z" }, +] + +[[package]] +name = "flask-sqlalchemy" +version = "3.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "flask" }, + { name = "sqlalchemy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/91/53/b0a9fcc1b1297f51e68b69ed3b7c3c40d8c45be1391d77ae198712914392/flask_sqlalchemy-3.1.1.tar.gz", hash = "sha256:e4b68bb881802dda1a7d878b2fc84c06d1ee57fb40b874d3dc97dabfa36b8312", size = 81899, upload-time = "2023-09-11T21:42:36.147Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/6a/89963a5c6ecf166e8be29e0d1bf6806051ee8fe6c82e232842e3aeac9204/flask_sqlalchemy-3.1.1-py3-none-any.whl", hash = "sha256:4ba4be7f419dc72f4efd8802d69974803c37259dd42f3913b0dcf75c9447e0a0", size = 25125, upload-time = "2023-09-11T21:42:34.514Z" }, +] + +[[package]] +name = "google" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "beautifulsoup4" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/89/97/b49c69893cddea912c7a660a4b6102c6b02cd268f8c7162dd70b7c16f753/google-3.0.0.tar.gz", hash = "sha256:143530122ee5130509ad5e989f0512f7cb218b2d4eddbafbad40fd10e8d8ccbe", size = 44978, upload-time = "2020-07-11T14:50:45.678Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ac/35/17c9141c4ae21e9a29a43acdfd848e3e468a810517f862cad07977bf8fe9/google-3.0.0-py2.py3-none-any.whl", hash = "sha256:889cf695f84e4ae2c55fbc0cfdaf4c1e729417fa52ab1db0485202ba173e4935", size = 45258, upload-time = "2020-07-11T14:49:58.287Z" }, +] + +[[package]] +name = "google-auth" +version = "2.40.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cachetools" }, + { name = "pyasn1-modules" }, + { name = "rsa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9e/9b/e92ef23b84fa10a64ce4831390b7a4c2e53c0132568d99d4ae61d04c8855/google_auth-2.40.3.tar.gz", hash = "sha256:500c3a29adedeb36ea9cf24b8d10858e152f2412e3ca37829b3fa18e33d63b77", size = 281029, upload-time = "2025-06-04T18:04:57.577Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/63/b19553b658a1692443c62bd07e5868adaa0ad746a0751ba62c59568cd45b/google_auth-2.40.3-py2.py3-none-any.whl", hash = "sha256:1370d4593e86213563547f97a92752fc658456fe4514c809544f330fed45a7ca", size = 216137, upload-time = "2025-06-04T18:04:55.573Z" }, +] + +[[package]] +name = "google-auth-oauthlib" +version = "1.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-auth" }, + { name = "requests-oauthlib" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fb/87/e10bf24f7bcffc1421b84d6f9c3377c30ec305d082cd737ddaa6d8f77f7c/google_auth_oauthlib-1.2.2.tar.gz", hash = "sha256:11046fb8d3348b296302dd939ace8af0a724042e8029c1b872d87fabc9f41684", size = 20955, upload-time = "2025-04-22T16:40:29.172Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ac/84/40ee070be95771acd2f4418981edb834979424565c3eec3cd88b6aa09d24/google_auth_oauthlib-1.2.2-py3-none-any.whl", hash = "sha256:fd619506f4b3908b5df17b65f39ca8d66ea56986e5472eb5978fd8f3786f00a2", size = 19072, upload-time = "2025-04-22T16:40:28.174Z" }, +] + +[[package]] +name = "greenlet" +version = "3.2.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/03/b8/704d753a5a45507a7aab61f18db9509302ed3d0a27ac7e0359ec2905b1a6/greenlet-3.2.4.tar.gz", hash = "sha256:0dca0d95ff849f9a364385f36ab49f50065d76964944638be9691e1832e9f86d", size = 188260, upload-time = "2025-08-07T13:24:33.51Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/de/f28ced0a67749cac23fecb02b694f6473f47686dff6afaa211d186e2ef9c/greenlet-3.2.4-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:96378df1de302bc38e99c3a9aa311967b7dc80ced1dcc6f171e99842987882a2", size = 272305, upload-time = "2025-08-07T13:15:41.288Z" }, + { url = "https://files.pythonhosted.org/packages/09/16/2c3792cba130000bf2a31c5272999113f4764fd9d874fb257ff588ac779a/greenlet-3.2.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1ee8fae0519a337f2329cb78bd7a8e128ec0f881073d43f023c7b8d4831d5246", size = 632472, upload-time = "2025-08-07T13:42:55.044Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8f/95d48d7e3d433e6dae5b1682e4292242a53f22df82e6d3dda81b1701a960/greenlet-3.2.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:94abf90142c2a18151632371140b3dba4dee031633fe614cb592dbb6c9e17bc3", size = 644646, upload-time = "2025-08-07T13:45:26.523Z" }, + { url = "https://files.pythonhosted.org/packages/d5/5e/405965351aef8c76b8ef7ad370e5da58d57ef6068df197548b015464001a/greenlet-3.2.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:4d1378601b85e2e5171b99be8d2dc85f594c79967599328f95c1dc1a40f1c633", size = 640519, upload-time = "2025-08-07T13:53:13.928Z" }, + { url = "https://files.pythonhosted.org/packages/25/5d/382753b52006ce0218297ec1b628e048c4e64b155379331f25a7316eb749/greenlet-3.2.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0db5594dce18db94f7d1650d7489909b57afde4c580806b8d9203b6e79cdc079", size = 639707, upload-time = "2025-08-07T13:18:27.146Z" }, + { url = "https://files.pythonhosted.org/packages/1f/8e/abdd3f14d735b2929290a018ecf133c901be4874b858dd1c604b9319f064/greenlet-3.2.4-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2523e5246274f54fdadbce8494458a2ebdcdbc7b802318466ac5606d3cded1f8", size = 587684, upload-time = "2025-08-07T13:18:25.164Z" }, + { url = "https://files.pythonhosted.org/packages/5d/65/deb2a69c3e5996439b0176f6651e0052542bb6c8f8ec2e3fba97c9768805/greenlet-3.2.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1987de92fec508535687fb807a5cea1560f6196285a4cde35c100b8cd632cc52", size = 1116647, upload-time = "2025-08-07T13:42:38.655Z" }, + { url = "https://files.pythonhosted.org/packages/3f/cc/b07000438a29ac5cfb2194bfc128151d52f333cee74dd7dfe3fb733fc16c/greenlet-3.2.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:55e9c5affaa6775e2c6b67659f3a71684de4c549b3dd9afca3bc773533d284fa", size = 1142073, upload-time = "2025-08-07T13:18:21.737Z" }, + { url = "https://files.pythonhosted.org/packages/67/24/28a5b2fa42d12b3d7e5614145f0bd89714c34c08be6aabe39c14dd52db34/greenlet-3.2.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c9c6de1940a7d828635fbd254d69db79e54619f165ee7ce32fda763a9cb6a58c", size = 1548385, upload-time = "2025-11-04T12:42:11.067Z" }, + { url = "https://files.pythonhosted.org/packages/6a/05/03f2f0bdd0b0ff9a4f7b99333d57b53a7709c27723ec8123056b084e69cd/greenlet-3.2.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:03c5136e7be905045160b1b9fdca93dd6727b180feeafda6818e6496434ed8c5", size = 1613329, upload-time = "2025-11-04T12:42:12.928Z" }, + { url = "https://files.pythonhosted.org/packages/d8/0f/30aef242fcab550b0b3520b8e3561156857c94288f0332a79928c31a52cf/greenlet-3.2.4-cp311-cp311-win_amd64.whl", hash = "sha256:9c40adce87eaa9ddb593ccb0fa6a07caf34015a29bf8d344811665b573138db9", size = 299100, upload-time = "2025-08-07T13:44:12.287Z" }, + { url = "https://files.pythonhosted.org/packages/44/69/9b804adb5fd0671f367781560eb5eb586c4d495277c93bde4307b9e28068/greenlet-3.2.4-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3b67ca49f54cede0186854a008109d6ee71f66bd57bb36abd6d0a0267b540cdd", size = 274079, upload-time = "2025-08-07T13:15:45.033Z" }, + { url = "https://files.pythonhosted.org/packages/46/e9/d2a80c99f19a153eff70bc451ab78615583b8dac0754cfb942223d2c1a0d/greenlet-3.2.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ddf9164e7a5b08e9d22511526865780a576f19ddd00d62f8a665949327fde8bb", size = 640997, upload-time = "2025-08-07T13:42:56.234Z" }, + { url = "https://files.pythonhosted.org/packages/3b/16/035dcfcc48715ccd345f3a93183267167cdd162ad123cd93067d86f27ce4/greenlet-3.2.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f28588772bb5fb869a8eb331374ec06f24a83a9c25bfa1f38b6993afe9c1e968", size = 655185, upload-time = "2025-08-07T13:45:27.624Z" }, + { url = "https://files.pythonhosted.org/packages/31/da/0386695eef69ffae1ad726881571dfe28b41970173947e7c558d9998de0f/greenlet-3.2.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:5c9320971821a7cb77cfab8d956fa8e39cd07ca44b6070db358ceb7f8797c8c9", size = 649926, upload-time = "2025-08-07T13:53:15.251Z" }, + { url = "https://files.pythonhosted.org/packages/68/88/69bf19fd4dc19981928ceacbc5fd4bb6bc2215d53199e367832e98d1d8fe/greenlet-3.2.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c60a6d84229b271d44b70fb6e5fa23781abb5d742af7b808ae3f6efd7c9c60f6", size = 651839, upload-time = "2025-08-07T13:18:30.281Z" }, + { url = "https://files.pythonhosted.org/packages/19/0d/6660d55f7373b2ff8152401a83e02084956da23ae58cddbfb0b330978fe9/greenlet-3.2.4-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b3812d8d0c9579967815af437d96623f45c0f2ae5f04e366de62a12d83a8fb0", size = 607586, upload-time = "2025-08-07T13:18:28.544Z" }, + { url = "https://files.pythonhosted.org/packages/8e/1a/c953fdedd22d81ee4629afbb38d2f9d71e37d23caace44775a3a969147d4/greenlet-3.2.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:abbf57b5a870d30c4675928c37278493044d7c14378350b3aa5d484fa65575f0", size = 1123281, upload-time = "2025-08-07T13:42:39.858Z" }, + { url = "https://files.pythonhosted.org/packages/3f/c7/12381b18e21aef2c6bd3a636da1088b888b97b7a0362fac2e4de92405f97/greenlet-3.2.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:20fb936b4652b6e307b8f347665e2c615540d4b42b3b4c8a321d8286da7e520f", size = 1151142, upload-time = "2025-08-07T13:18:22.981Z" }, + { url = "https://files.pythonhosted.org/packages/27/45/80935968b53cfd3f33cf99ea5f08227f2646e044568c9b1555b58ffd61c2/greenlet-3.2.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ee7a6ec486883397d70eec05059353b8e83eca9168b9f3f9a361971e77e0bcd0", size = 1564846, upload-time = "2025-11-04T12:42:15.191Z" }, + { url = "https://files.pythonhosted.org/packages/69/02/b7c30e5e04752cb4db6202a3858b149c0710e5453b71a3b2aec5d78a1aab/greenlet-3.2.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:326d234cbf337c9c3def0676412eb7040a35a768efc92504b947b3e9cfc7543d", size = 1633814, upload-time = "2025-11-04T12:42:17.175Z" }, + { url = "https://files.pythonhosted.org/packages/e9/08/b0814846b79399e585f974bbeebf5580fbe59e258ea7be64d9dfb253c84f/greenlet-3.2.4-cp312-cp312-win_amd64.whl", hash = "sha256:a7d4e128405eea3814a12cc2605e0e6aedb4035bf32697f72deca74de4105e02", size = 299899, upload-time = "2025-08-07T13:38:53.448Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, +] + +[[package]] +name = "importlib-metadata" +version = "8.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "zipp" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/76/66/650a33bd90f786193e4de4b3ad86ea60b53c89b669a5c7be931fac31cdb0/importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000", size = 56641, upload-time = "2025-04-27T15:29:01.736Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/b0/36bd937216ec521246249be3bf9855081de4c5e06a0c9b4219dbeda50373/importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd", size = 27656, upload-time = "2025-04-27T15:29:00.214Z" }, +] + +[[package]] +name = "importlib-resources" +version = "6.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cf/8c/f834fbf984f691b4f7ff60f50b514cc3de5cc08abfc3295564dd89c5e2e7/importlib_resources-6.5.2.tar.gz", hash = "sha256:185f87adef5bcc288449d98fb4fba07cea78bc036455dd44c5fc4a2fe78fed2c", size = 44693, upload-time = "2025-01-03T18:51:56.698Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/ed/1f1afb2e9e7f38a545d628f864d562a5ae64fe6f7a10e28ffb9b185b4e89/importlib_resources-6.5.2-py3-none-any.whl", hash = "sha256:789cfdc3ed28c78b67a06acb8126751ced69a3d5f79c095a98298cd8a760ccec", size = 37461, upload-time = "2025-01-03T18:51:54.306Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "itsdangerous" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410, upload-time = "2024-04-16T21:28:15.614Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234, upload-time = "2024-04-16T21:28:14.499Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "jsonschema" +version = "4.25.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "jsonschema-specifications" }, + { name = "referencing" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/74/69/f7185de793a29082a9f3c7728268ffb31cb5095131a9c139a74078e27336/jsonschema-4.25.1.tar.gz", hash = "sha256:e4a9655ce0da0c0b67a085847e00a3a51449e1157f4f75e9fb5aa545e122eb85", size = 357342, upload-time = "2025-08-18T17:03:50.038Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl", hash = "sha256:3fba0169e345c7175110351d456342c364814cfcf3b964ba4587f22915230a63", size = 90040, upload-time = "2025-08-18T17:03:48.373Z" }, +] + +[[package]] +name = "jsonschema-specifications" +version = "2025.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, +] + +[[package]] +name = "kombu" +version = "5.5.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "amqp" }, + { name = "packaging" }, + { name = "tzdata" }, + { name = "vine" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0f/d3/5ff936d8319ac86b9c409f1501b07c426e6ad41966fedace9ef1b966e23f/kombu-5.5.4.tar.gz", hash = "sha256:886600168275ebeada93b888e831352fe578168342f0d1d5833d88ba0d847363", size = 461992, upload-time = "2025-06-01T10:19:22.281Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/70/a07dcf4f62598c8ad579df241af55ced65bed76e42e45d3c368a6d82dbc1/kombu-5.5.4-py3-none-any.whl", hash = "sha256:a12ed0557c238897d8e518f1d1fdf84bd1516c5e305af2dacd85c2015115feb8", size = 210034, upload-time = "2025-06-01T10:19:20.436Z" }, +] + +[[package]] +name = "mako" +version = "1.3.10" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9e/38/bd5b78a920a64d708fe6bc8e0a2c075e1389d53bef8413725c63ba041535/mako-1.3.10.tar.gz", hash = "sha256:99579a6f39583fa7e5630a28c3c1f440e4e97a414b80372649c0ce338da2ea28", size = 392474, upload-time = "2025-04-10T12:44:31.16Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/fb/99f81ac72ae23375f22b7afdb7642aba97c00a713c217124420147681a2f/mako-1.3.10-py3-none-any.whl", hash = "sha256:baef24a52fc4fc514a0887ac600f9f1cff3d82c61d4d700a1fa84d597b88db59", size = 78509, upload-time = "2025-04-10T12:50:53.297Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353, upload-time = "2024-10-18T15:21:02.187Z" }, + { url = "https://files.pythonhosted.org/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392, upload-time = "2024-10-18T15:21:02.941Z" }, + { url = "https://files.pythonhosted.org/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984, upload-time = "2024-10-18T15:21:03.953Z" }, + { url = "https://files.pythonhosted.org/packages/f1/a4/aefb044a2cd8d7334c8a47d3fb2c9f328ac48cb349468cc31c20b539305f/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", size = 23120, upload-time = "2024-10-18T15:21:06.495Z" }, + { url = "https://files.pythonhosted.org/packages/8d/21/5e4851379f88f3fad1de30361db501300d4f07bcad047d3cb0449fc51f8c/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", size = 23032, upload-time = "2024-10-18T15:21:07.295Z" }, + { url = "https://files.pythonhosted.org/packages/00/7b/e92c64e079b2d0d7ddf69899c98842f3f9a60a1ae72657c89ce2655c999d/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", size = 24057, upload-time = "2024-10-18T15:21:08.073Z" }, + { url = "https://files.pythonhosted.org/packages/f9/ac/46f960ca323037caa0a10662ef97d0a4728e890334fc156b9f9e52bcc4ca/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", size = 23359, upload-time = "2024-10-18T15:21:09.318Z" }, + { url = "https://files.pythonhosted.org/packages/69/84/83439e16197337b8b14b6a5b9c2105fff81d42c2a7c5b58ac7b62ee2c3b1/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", size = 23306, upload-time = "2024-10-18T15:21:10.185Z" }, + { url = "https://files.pythonhosted.org/packages/9a/34/a15aa69f01e2181ed8d2b685c0d2f6655d5cca2c4db0ddea775e631918cd/MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", size = 15094, upload-time = "2024-10-18T15:21:11.005Z" }, + { url = "https://files.pythonhosted.org/packages/da/b8/3a3bd761922d416f3dc5d00bfbed11f66b1ab89a0c2b6e887240a30b0f6b/MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", size = 15521, upload-time = "2024-10-18T15:21:12.911Z" }, + { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274, upload-time = "2024-10-18T15:21:13.777Z" }, + { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348, upload-time = "2024-10-18T15:21:14.822Z" }, + { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149, upload-time = "2024-10-18T15:21:15.642Z" }, + { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118, upload-time = "2024-10-18T15:21:17.133Z" }, + { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993, upload-time = "2024-10-18T15:21:18.064Z" }, + { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178, upload-time = "2024-10-18T15:21:18.859Z" }, + { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319, upload-time = "2024-10-18T15:21:19.671Z" }, + { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352, upload-time = "2024-10-18T15:21:20.971Z" }, + { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097, upload-time = "2024-10-18T15:21:22.646Z" }, + { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601, upload-time = "2024-10-18T15:21:23.499Z" }, +] + +[[package]] +name = "miminet-front" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "alembic" }, + { name = "beautifulsoup4" }, + { name = "cachecontrol" }, + { name = "celery" }, + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "click" }, + { name = "colorama" }, + { name = "dpkt" }, + { name = "flask" }, + { name = "flask-admin" }, + { name = "flask-cors" }, + { name = "flask-jwt-extended" }, + { name = "flask-login" }, + { name = "flask-migrate" }, + { name = "flask-sqlalchemy" }, + { name = "google" }, + { name = "google-auth" }, + { name = "google-auth-oauthlib" }, + { name = "greenlet" }, + { name = "h11" }, + { name = "idna" }, + { name = "importlib-metadata" }, + { name = "importlib-resources" }, + { name = "itsdangerous" }, + { name = "jinja2" }, + { name = "jsonschema" }, + { name = "kombu" }, + { name = "mako" }, + { name = "markupsafe" }, + { name = "oauthlib" }, + { name = "pillow" }, + { name = "psycopg2-binary" }, + { name = "pyasn1" }, + { name = "pyasn1-modules" }, + { name = "pysocks" }, + { name = "python-dateutil" }, + { name = "python-dotenv" }, + { name = "requests" }, + { name = "requests-oauthlib" }, + { name = "rsa" }, + { name = "six" }, + { name = "soupsieve" }, + { name = "sqlalchemy" }, + { name = "urllib3" }, + { name = "uwsgi" }, + { name = "vine" }, + { name = "wcwidth" }, + { name = "websocket-client" }, + { name = "werkzeug" }, + { name = "wsproto" }, + { name = "wtforms" }, + { name = "zipp" }, +] + +[package.optional-dependencies] +test = [ + { name = "pytest" }, + { name = "pytest-mock" }, + { name = "selenium" }, +] + +[package.metadata] +requires-dist = [ + { name = "alembic", specifier = "==1.16.5" }, + { name = "beautifulsoup4", specifier = "==4.13.5" }, + { name = "cachecontrol", specifier = ">=0.14" }, + { name = "celery", specifier = "==5.5.3" }, + { name = "certifi", specifier = "==2025.10.5" }, + { name = "charset-normalizer", specifier = "==3.4.3" }, + { name = "click", specifier = "==8.1.8" }, + { name = "colorama", specifier = "==0.4.6" }, + { name = "dpkt", specifier = "==1.9.8" }, + { name = "flask", specifier = "==3.1.2" }, + { name = "flask-admin", specifier = "==2.0.1" }, + { name = "flask-cors", specifier = "==6.0.2" }, + { name = "flask-jwt-extended", specifier = "==4.7.1" }, + { name = "flask-login", specifier = "==0.6.3" }, + { name = "flask-migrate", specifier = "==4.1.0" }, + { name = "flask-sqlalchemy", specifier = "==3.1.1" }, + { name = "google", specifier = "==3.0.0" }, + { name = "google-auth", specifier = "==2.40.3" }, + { name = "google-auth-oauthlib", specifier = "==1.2.2" }, + { name = "greenlet", specifier = "==3.2.4" }, + { name = "h11", specifier = "==0.16.0" }, + { name = "idna", specifier = "==3.10" }, + { name = "importlib-metadata", specifier = "==8.7.0" }, + { name = "importlib-resources", specifier = "==6.5.2" }, + { name = "itsdangerous", specifier = "==2.2.0" }, + { name = "jinja2", specifier = "==3.1.6" }, + { name = "jsonschema", specifier = "==4.25.1" }, + { name = "kombu", specifier = "==5.5.4" }, + { name = "mako", specifier = "==1.3.10" }, + { name = "markupsafe", specifier = "==3.0.2" }, + { name = "oauthlib", specifier = "==3.3.1" }, + { name = "pillow", specifier = "==11.3.0" }, + { name = "psycopg2-binary", specifier = "==2.9.10" }, + { name = "pyasn1", specifier = "==0.6.1" }, + { name = "pyasn1-modules", specifier = "==0.4.2" }, + { name = "pysocks", specifier = "==1.7.1" }, + { name = "pytest", marker = "extra == 'test'", specifier = "==8.4.2" }, + { name = "pytest-mock", marker = "extra == 'test'", specifier = "==3.15.1" }, + { name = "python-dateutil", specifier = "==2.9.0.post0" }, + { name = "python-dotenv", specifier = "==1.1.1" }, + { name = "requests", specifier = "==2.32.5" }, + { name = "requests-oauthlib", specifier = "==2.0.0" }, + { name = "rsa", specifier = "==4.9.1" }, + { name = "selenium", marker = "extra == 'test'" }, + { name = "six", specifier = "==1.17.0" }, + { name = "soupsieve", specifier = "==2.8" }, + { name = "sqlalchemy", specifier = "==2.0.43" }, + { name = "urllib3", specifier = "==2.5.0" }, + { name = "uwsgi", specifier = ">=2.0" }, + { name = "vine", specifier = "==5.1.0" }, + { name = "wcwidth", specifier = "==0.2.13" }, + { name = "websocket-client", specifier = "==1.8.0" }, + { name = "werkzeug", specifier = "==3.1.3" }, + { name = "wsproto", specifier = "==1.2.0" }, + { name = "wtforms", specifier = "==3.2.1" }, + { name = "zipp", specifier = "==3.23.0" }, +] +provides-extras = ["test"] + +[[package]] +name = "msgpack" +version = "1.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4d/f2/bfb55a6236ed8725a96b0aa3acbd0ec17588e6a2c3b62a93eb513ed8783f/msgpack-1.1.2.tar.gz", hash = "sha256:3b60763c1373dd60f398488069bcdc703cd08a711477b5d480eecc9f9626f47e", size = 173581, upload-time = "2025-10-08T09:15:56.596Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/97/560d11202bcd537abca693fd85d81cebe2107ba17301de42b01ac1677b69/msgpack-1.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2e86a607e558d22985d856948c12a3fa7b42efad264dca8a3ebbcfa2735d786c", size = 82271, upload-time = "2025-10-08T09:14:49.967Z" }, + { url = "https://files.pythonhosted.org/packages/83/04/28a41024ccbd67467380b6fb440ae916c1e4f25e2cd4c63abe6835ac566e/msgpack-1.1.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:283ae72fc89da59aa004ba147e8fc2f766647b1251500182fac0350d8af299c0", size = 84914, upload-time = "2025-10-08T09:14:50.958Z" }, + { url = "https://files.pythonhosted.org/packages/71/46/b817349db6886d79e57a966346cf0902a426375aadc1e8e7a86a75e22f19/msgpack-1.1.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:61c8aa3bd513d87c72ed0b37b53dd5c5a0f58f2ff9f26e1555d3bd7948fb7296", size = 416962, upload-time = "2025-10-08T09:14:51.997Z" }, + { url = "https://files.pythonhosted.org/packages/da/e0/6cc2e852837cd6086fe7d8406af4294e66827a60a4cf60b86575a4a65ca8/msgpack-1.1.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:454e29e186285d2ebe65be34629fa0e8605202c60fbc7c4c650ccd41870896ef", size = 426183, upload-time = "2025-10-08T09:14:53.477Z" }, + { url = "https://files.pythonhosted.org/packages/25/98/6a19f030b3d2ea906696cedd1eb251708e50a5891d0978b012cb6107234c/msgpack-1.1.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7bc8813f88417599564fafa59fd6f95be417179f76b40325b500b3c98409757c", size = 411454, upload-time = "2025-10-08T09:14:54.648Z" }, + { url = "https://files.pythonhosted.org/packages/b7/cd/9098fcb6adb32187a70b7ecaabf6339da50553351558f37600e53a4a2a23/msgpack-1.1.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bafca952dc13907bdfdedfc6a5f579bf4f292bdd506fadb38389afa3ac5b208e", size = 422341, upload-time = "2025-10-08T09:14:56.328Z" }, + { url = "https://files.pythonhosted.org/packages/e6/ae/270cecbcf36c1dc85ec086b33a51a4d7d08fc4f404bdbc15b582255d05ff/msgpack-1.1.2-cp311-cp311-win32.whl", hash = "sha256:602b6740e95ffc55bfb078172d279de3773d7b7db1f703b2f1323566b878b90e", size = 64747, upload-time = "2025-10-08T09:14:57.882Z" }, + { url = "https://files.pythonhosted.org/packages/2a/79/309d0e637f6f37e83c711f547308b91af02b72d2326ddd860b966080ef29/msgpack-1.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:d198d275222dc54244bf3327eb8cbe00307d220241d9cec4d306d49a44e85f68", size = 71633, upload-time = "2025-10-08T09:14:59.177Z" }, + { url = "https://files.pythonhosted.org/packages/73/4d/7c4e2b3d9b1106cd0aa6cb56cc57c6267f59fa8bfab7d91df5adc802c847/msgpack-1.1.2-cp311-cp311-win_arm64.whl", hash = "sha256:86f8136dfa5c116365a8a651a7d7484b65b13339731dd6faebb9a0242151c406", size = 64755, upload-time = "2025-10-08T09:15:00.48Z" }, + { url = "https://files.pythonhosted.org/packages/ad/bd/8b0d01c756203fbab65d265859749860682ccd2a59594609aeec3a144efa/msgpack-1.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:70a0dff9d1f8da25179ffcf880e10cf1aad55fdb63cd59c9a49a1b82290062aa", size = 81939, upload-time = "2025-10-08T09:15:01.472Z" }, + { url = "https://files.pythonhosted.org/packages/34/68/ba4f155f793a74c1483d4bdef136e1023f7bcba557f0db4ef3db3c665cf1/msgpack-1.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:446abdd8b94b55c800ac34b102dffd2f6aa0ce643c55dfc017ad89347db3dbdb", size = 85064, upload-time = "2025-10-08T09:15:03.764Z" }, + { url = "https://files.pythonhosted.org/packages/f2/60/a064b0345fc36c4c3d2c743c82d9100c40388d77f0b48b2f04d6041dbec1/msgpack-1.1.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c63eea553c69ab05b6747901b97d620bb2a690633c77f23feb0c6a947a8a7b8f", size = 417131, upload-time = "2025-10-08T09:15:05.136Z" }, + { url = "https://files.pythonhosted.org/packages/65/92/a5100f7185a800a5d29f8d14041f61475b9de465ffcc0f3b9fba606e4505/msgpack-1.1.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:372839311ccf6bdaf39b00b61288e0557916c3729529b301c52c2d88842add42", size = 427556, upload-time = "2025-10-08T09:15:06.837Z" }, + { url = "https://files.pythonhosted.org/packages/f5/87/ffe21d1bf7d9991354ad93949286f643b2bb6ddbeab66373922b44c3b8cc/msgpack-1.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2929af52106ca73fcb28576218476ffbb531a036c2adbcf54a3664de124303e9", size = 404920, upload-time = "2025-10-08T09:15:08.179Z" }, + { url = "https://files.pythonhosted.org/packages/ff/41/8543ed2b8604f7c0d89ce066f42007faac1eaa7d79a81555f206a5cdb889/msgpack-1.1.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:be52a8fc79e45b0364210eef5234a7cf8d330836d0a64dfbb878efa903d84620", size = 415013, upload-time = "2025-10-08T09:15:09.83Z" }, + { url = "https://files.pythonhosted.org/packages/41/0d/2ddfaa8b7e1cee6c490d46cb0a39742b19e2481600a7a0e96537e9c22f43/msgpack-1.1.2-cp312-cp312-win32.whl", hash = "sha256:1fff3d825d7859ac888b0fbda39a42d59193543920eda9d9bea44d958a878029", size = 65096, upload-time = "2025-10-08T09:15:11.11Z" }, + { url = "https://files.pythonhosted.org/packages/8c/ec/d431eb7941fb55a31dd6ca3404d41fbb52d99172df2e7707754488390910/msgpack-1.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:1de460f0403172cff81169a30b9a92b260cb809c4cb7e2fc79ae8d0510c78b6b", size = 72708, upload-time = "2025-10-08T09:15:12.554Z" }, + { url = "https://files.pythonhosted.org/packages/c5/31/5b1a1f70eb0e87d1678e9624908f86317787b536060641d6798e3cf70ace/msgpack-1.1.2-cp312-cp312-win_arm64.whl", hash = "sha256:be5980f3ee0e6bd44f3a9e9dea01054f175b50c3e6cdb692bc9424c0bbb8bf69", size = 64119, upload-time = "2025-10-08T09:15:13.589Z" }, +] + +[[package]] +name = "oauthlib" +version = "3.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/5f/19930f824ffeb0ad4372da4812c50edbd1434f678c90c2733e1188edfc63/oauthlib-3.3.1.tar.gz", hash = "sha256:0f0f8aa759826a193cf66c12ea1af1637f87b9b4622d46e866952bb022e538c9", size = 185918, upload-time = "2025-06-19T22:48:08.269Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/9c/92789c596b8df838baa98fa71844d84283302f7604ed565dafe5a6b5041a/oauthlib-3.3.1-py3-none-any.whl", hash = "sha256:88119c938d2b8fb88561af5f6ee0eec8cc8d552b7bb1f712743136eb7523b7a1", size = 160065, upload-time = "2025-06-19T22:48:06.508Z" }, +] + +[[package]] +name = "outcome" +version = "1.3.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/df/77698abfac98571e65ffeb0c1fba8ffd692ab8458d617a0eed7d9a8d38f2/outcome-1.3.0.post0.tar.gz", hash = "sha256:9dcf02e65f2971b80047b377468e72a268e15c0af3cf1238e6ff14f7f91143b8", size = 21060, upload-time = "2023-10-26T04:26:04.361Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/55/8b/5ab7257531a5d830fc8000c476e63c935488d74609b50f9384a643ec0a62/outcome-1.3.0.post0-py2.py3-none-any.whl", hash = "sha256:e771c5ce06d1415e356078d3bdd68523f284b4ce5419828922b6871e65eda82b", size = 10692, upload-time = "2023-10-26T04:26:02.532Z" }, +] + +[[package]] +name = "packaging" +version = "26.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, +] + +[[package]] +name = "pillow" +version = "11.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/d0d6dea55cd152ce3d6767bb38a8fc10e33796ba4ba210cbab9354b6d238/pillow-11.3.0.tar.gz", hash = "sha256:3828ee7586cd0b2091b6209e5ad53e20d0649bbe87164a459d0676e035e8f523", size = 47113069, upload-time = "2025-07-01T09:16:30.666Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/26/77f8ed17ca4ffd60e1dcd220a6ec6d71210ba398cfa33a13a1cd614c5613/pillow-11.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1cd110edf822773368b396281a2293aeb91c90a2db00d78ea43e7e861631b722", size = 5316531, upload-time = "2025-07-01T09:13:59.203Z" }, + { url = "https://files.pythonhosted.org/packages/cb/39/ee475903197ce709322a17a866892efb560f57900d9af2e55f86db51b0a5/pillow-11.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9c412fddd1b77a75aa904615ebaa6001f169b26fd467b4be93aded278266b288", size = 4686560, upload-time = "2025-07-01T09:14:01.101Z" }, + { url = "https://files.pythonhosted.org/packages/d5/90/442068a160fd179938ba55ec8c97050a612426fae5ec0a764e345839f76d/pillow-11.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d1aa4de119a0ecac0a34a9c8bde33f34022e2e8f99104e47a3ca392fd60e37d", size = 5870978, upload-time = "2025-07-03T13:09:55.638Z" }, + { url = "https://files.pythonhosted.org/packages/13/92/dcdd147ab02daf405387f0218dcf792dc6dd5b14d2573d40b4caeef01059/pillow-11.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:91da1d88226663594e3f6b4b8c3c8d85bd504117d043740a8e0ec449087cc494", size = 7641168, upload-time = "2025-07-03T13:10:00.37Z" }, + { url = "https://files.pythonhosted.org/packages/6e/db/839d6ba7fd38b51af641aa904e2960e7a5644d60ec754c046b7d2aee00e5/pillow-11.3.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:643f189248837533073c405ec2f0bb250ba54598cf80e8c1e043381a60632f58", size = 5973053, upload-time = "2025-07-01T09:14:04.491Z" }, + { url = "https://files.pythonhosted.org/packages/f2/2f/d7675ecae6c43e9f12aa8d58b6012683b20b6edfbdac7abcb4e6af7a3784/pillow-11.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:106064daa23a745510dabce1d84f29137a37224831d88eb4ce94bb187b1d7e5f", size = 6640273, upload-time = "2025-07-01T09:14:06.235Z" }, + { url = "https://files.pythonhosted.org/packages/45/ad/931694675ede172e15b2ff03c8144a0ddaea1d87adb72bb07655eaffb654/pillow-11.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cd8ff254faf15591e724dc7c4ddb6bf4793efcbe13802a4ae3e863cd300b493e", size = 6082043, upload-time = "2025-07-01T09:14:07.978Z" }, + { url = "https://files.pythonhosted.org/packages/3a/04/ba8f2b11fc80d2dd462d7abec16351b45ec99cbbaea4387648a44190351a/pillow-11.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:932c754c2d51ad2b2271fd01c3d121daaa35e27efae2a616f77bf164bc0b3e94", size = 6715516, upload-time = "2025-07-01T09:14:10.233Z" }, + { url = "https://files.pythonhosted.org/packages/48/59/8cd06d7f3944cc7d892e8533c56b0acb68399f640786313275faec1e3b6f/pillow-11.3.0-cp311-cp311-win32.whl", hash = "sha256:b4b8f3efc8d530a1544e5962bd6b403d5f7fe8b9e08227c6b255f98ad82b4ba0", size = 6274768, upload-time = "2025-07-01T09:14:11.921Z" }, + { url = "https://files.pythonhosted.org/packages/f1/cc/29c0f5d64ab8eae20f3232da8f8571660aa0ab4b8f1331da5c2f5f9a938e/pillow-11.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:1a992e86b0dd7aeb1f053cd506508c0999d710a8f07b4c791c63843fc6a807ac", size = 6986055, upload-time = "2025-07-01T09:14:13.623Z" }, + { url = "https://files.pythonhosted.org/packages/c6/df/90bd886fabd544c25addd63e5ca6932c86f2b701d5da6c7839387a076b4a/pillow-11.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:30807c931ff7c095620fe04448e2c2fc673fcbb1ffe2a7da3fb39613489b1ddd", size = 2423079, upload-time = "2025-07-01T09:14:15.268Z" }, + { url = "https://files.pythonhosted.org/packages/40/fe/1bc9b3ee13f68487a99ac9529968035cca2f0a51ec36892060edcc51d06a/pillow-11.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdae223722da47b024b867c1ea0be64e0df702c5e0a60e27daad39bf960dd1e4", size = 5278800, upload-time = "2025-07-01T09:14:17.648Z" }, + { url = "https://files.pythonhosted.org/packages/2c/32/7e2ac19b5713657384cec55f89065fb306b06af008cfd87e572035b27119/pillow-11.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:921bd305b10e82b4d1f5e802b6850677f965d8394203d182f078873851dada69", size = 4686296, upload-time = "2025-07-01T09:14:19.828Z" }, + { url = "https://files.pythonhosted.org/packages/8e/1e/b9e12bbe6e4c2220effebc09ea0923a07a6da1e1f1bfbc8d7d29a01ce32b/pillow-11.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eb76541cba2f958032d79d143b98a3a6b3ea87f0959bbe256c0b5e416599fd5d", size = 5871726, upload-time = "2025-07-03T13:10:04.448Z" }, + { url = "https://files.pythonhosted.org/packages/8d/33/e9200d2bd7ba00dc3ddb78df1198a6e80d7669cce6c2bdbeb2530a74ec58/pillow-11.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:67172f2944ebba3d4a7b54f2e95c786a3a50c21b88456329314caaa28cda70f6", size = 7644652, upload-time = "2025-07-03T13:10:10.391Z" }, + { url = "https://files.pythonhosted.org/packages/41/f1/6f2427a26fc683e00d985bc391bdd76d8dd4e92fac33d841127eb8fb2313/pillow-11.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f07ed9f56a3b9b5f49d3661dc9607484e85c67e27f3e8be2c7d28ca032fec7", size = 5977787, upload-time = "2025-07-01T09:14:21.63Z" }, + { url = "https://files.pythonhosted.org/packages/e4/c9/06dd4a38974e24f932ff5f98ea3c546ce3f8c995d3f0985f8e5ba48bba19/pillow-11.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:676b2815362456b5b3216b4fd5bd89d362100dc6f4945154ff172e206a22c024", size = 6645236, upload-time = "2025-07-01T09:14:23.321Z" }, + { url = "https://files.pythonhosted.org/packages/40/e7/848f69fb79843b3d91241bad658e9c14f39a32f71a301bcd1d139416d1be/pillow-11.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3e184b2f26ff146363dd07bde8b711833d7b0202e27d13540bfe2e35a323a809", size = 6086950, upload-time = "2025-07-01T09:14:25.237Z" }, + { url = "https://files.pythonhosted.org/packages/0b/1a/7cff92e695a2a29ac1958c2a0fe4c0b2393b60aac13b04a4fe2735cad52d/pillow-11.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6be31e3fc9a621e071bc17bb7de63b85cbe0bfae91bb0363c893cbe67247780d", size = 6723358, upload-time = "2025-07-01T09:14:27.053Z" }, + { url = "https://files.pythonhosted.org/packages/26/7d/73699ad77895f69edff76b0f332acc3d497f22f5d75e5360f78cbcaff248/pillow-11.3.0-cp312-cp312-win32.whl", hash = "sha256:7b161756381f0918e05e7cb8a371fff367e807770f8fe92ecb20d905d0e1c149", size = 6275079, upload-time = "2025-07-01T09:14:30.104Z" }, + { url = "https://files.pythonhosted.org/packages/8c/ce/e7dfc873bdd9828f3b6e5c2bbb74e47a98ec23cc5c74fc4e54462f0d9204/pillow-11.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a6444696fce635783440b7f7a9fc24b3ad10a9ea3f0ab66c5905be1c19ccf17d", size = 6986324, upload-time = "2025-07-01T09:14:31.899Z" }, + { url = "https://files.pythonhosted.org/packages/16/8f/b13447d1bf0b1f7467ce7d86f6e6edf66c0ad7cf44cf5c87a37f9bed9936/pillow-11.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:2aceea54f957dd4448264f9bf40875da0415c83eb85f55069d89c0ed436e3542", size = 2423067, upload-time = "2025-07-01T09:14:33.709Z" }, + { url = "https://files.pythonhosted.org/packages/9e/e3/6fa84033758276fb31da12e5fb66ad747ae83b93c67af17f8c6ff4cc8f34/pillow-11.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7c8ec7a017ad1bd562f93dbd8505763e688d388cde6e4a010ae1486916e713e6", size = 5270566, upload-time = "2025-07-01T09:16:19.801Z" }, + { url = "https://files.pythonhosted.org/packages/5b/ee/e8d2e1ab4892970b561e1ba96cbd59c0d28cf66737fc44abb2aec3795a4e/pillow-11.3.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9ab6ae226de48019caa8074894544af5b53a117ccb9d3b3dcb2871464c829438", size = 4654618, upload-time = "2025-07-01T09:16:21.818Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6d/17f80f4e1f0761f02160fc433abd4109fa1548dcfdca46cfdadaf9efa565/pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fe27fb049cdcca11f11a7bfda64043c37b30e6b91f10cb5bab275806c32f6ab3", size = 4874248, upload-time = "2025-07-03T13:11:20.738Z" }, + { url = "https://files.pythonhosted.org/packages/de/5f/c22340acd61cef960130585bbe2120e2fd8434c214802f07e8c03596b17e/pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:465b9e8844e3c3519a983d58b80be3f668e2a7a5db97f2784e7079fbc9f9822c", size = 6583963, upload-time = "2025-07-03T13:11:26.283Z" }, + { url = "https://files.pythonhosted.org/packages/31/5e/03966aedfbfcbb4d5f8aa042452d3361f325b963ebbadddac05b122e47dd/pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5418b53c0d59b3824d05e029669efa023bbef0f3e92e75ec8428f3799487f361", size = 4957170, upload-time = "2025-07-01T09:16:23.762Z" }, + { url = "https://files.pythonhosted.org/packages/cc/2d/e082982aacc927fc2cab48e1e731bdb1643a1406acace8bed0900a61464e/pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:504b6f59505f08ae014f724b6207ff6222662aab5cc9542577fb084ed0676ac7", size = 5581505, upload-time = "2025-07-01T09:16:25.593Z" }, + { url = "https://files.pythonhosted.org/packages/34/e7/ae39f538fd6844e982063c3a5e4598b8ced43b9633baa3a85ef33af8c05c/pillow-11.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c84d689db21a1c397d001aa08241044aa2069e7587b398c8cc63020390b1c1b8", size = 6984598, upload-time = "2025-07-01T09:16:27.732Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "prompt-toolkit" +version = "3.0.52" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198, upload-time = "2025-08-27T15:24:02.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" }, +] + +[[package]] +name = "psycopg2-binary" +version = "2.9.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cb/0e/bdc8274dc0585090b4e3432267d7be4dfbfd8971c0fa59167c711105a6bf/psycopg2-binary-2.9.10.tar.gz", hash = "sha256:4b3df0e6990aa98acda57d983942eff13d824135fe2250e6522edaa782a06de2", size = 385764, upload-time = "2024-10-16T11:24:58.126Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9c/8f/9feb01291d0d7a0a4c6a6bab24094135c2b59c6a81943752f632c75896d6/psycopg2_binary-2.9.10-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:04392983d0bb89a8717772a193cfaac58871321e3ec69514e1c4e0d4957b5aff", size = 3043397, upload-time = "2024-10-16T11:19:40.033Z" }, + { url = "https://files.pythonhosted.org/packages/15/30/346e4683532011561cd9c8dfeac6a8153dd96452fee0b12666058ab7893c/psycopg2_binary-2.9.10-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:1a6784f0ce3fec4edc64e985865c17778514325074adf5ad8f80636cd029ef7c", size = 3274806, upload-time = "2024-10-16T11:19:43.5Z" }, + { url = "https://files.pythonhosted.org/packages/66/6e/4efebe76f76aee7ec99166b6c023ff8abdc4e183f7b70913d7c047701b79/psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5f86c56eeb91dc3135b3fd8a95dc7ae14c538a2f3ad77a19645cf55bab1799c", size = 2851370, upload-time = "2024-10-16T11:19:46.986Z" }, + { url = "https://files.pythonhosted.org/packages/7f/fd/ff83313f86b50f7ca089b161b8e0a22bb3c319974096093cd50680433fdb/psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b3d2491d4d78b6b14f76881905c7a8a8abcf974aad4a8a0b065273a0ed7a2cb", size = 3080780, upload-time = "2024-10-16T11:19:50.242Z" }, + { url = "https://files.pythonhosted.org/packages/e6/c4/bfadd202dcda8333a7ccafdc51c541dbdfce7c2c7cda89fa2374455d795f/psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2286791ececda3a723d1910441c793be44625d86d1a4e79942751197f4d30341", size = 3264583, upload-time = "2024-10-16T11:19:54.424Z" }, + { url = "https://files.pythonhosted.org/packages/5d/f1/09f45ac25e704ac954862581f9f9ae21303cc5ded3d0b775532b407f0e90/psycopg2_binary-2.9.10-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:512d29bb12608891e349af6a0cccedce51677725a921c07dba6342beaf576f9a", size = 3019831, upload-time = "2024-10-16T11:19:57.762Z" }, + { url = "https://files.pythonhosted.org/packages/9e/2e/9beaea078095cc558f215e38f647c7114987d9febfc25cb2beed7c3582a5/psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5a507320c58903967ef7384355a4da7ff3f28132d679aeb23572753cbf2ec10b", size = 2871822, upload-time = "2024-10-16T11:20:04.693Z" }, + { url = "https://files.pythonhosted.org/packages/01/9e/ef93c5d93f3dc9fc92786ffab39e323b9aed066ba59fdc34cf85e2722271/psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6d4fa1079cab9018f4d0bd2db307beaa612b0d13ba73b5c6304b9fe2fb441ff7", size = 2820975, upload-time = "2024-10-16T11:20:11.401Z" }, + { url = "https://files.pythonhosted.org/packages/a5/f0/049e9631e3268fe4c5a387f6fc27e267ebe199acf1bc1bc9cbde4bd6916c/psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:851485a42dbb0bdc1edcdabdb8557c09c9655dfa2ca0460ff210522e073e319e", size = 2919320, upload-time = "2024-10-16T11:20:17.959Z" }, + { url = "https://files.pythonhosted.org/packages/dc/9a/bcb8773b88e45fb5a5ea8339e2104d82c863a3b8558fbb2aadfe66df86b3/psycopg2_binary-2.9.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:35958ec9e46432d9076286dda67942ed6d968b9c3a6a2fd62b48939d1d78bf68", size = 2957617, upload-time = "2024-10-16T11:20:24.711Z" }, + { url = "https://files.pythonhosted.org/packages/e2/6b/144336a9bf08a67d217b3af3246abb1d027095dab726f0687f01f43e8c03/psycopg2_binary-2.9.10-cp311-cp311-win32.whl", hash = "sha256:ecced182e935529727401b24d76634a357c71c9275b356efafd8a2a91ec07392", size = 1024618, upload-time = "2024-10-16T11:20:27.718Z" }, + { url = "https://files.pythonhosted.org/packages/61/69/3b3d7bd583c6d3cbe5100802efa5beacaacc86e37b653fc708bf3d6853b8/psycopg2_binary-2.9.10-cp311-cp311-win_amd64.whl", hash = "sha256:ee0e8c683a7ff25d23b55b11161c2663d4b099770f6085ff0a20d4505778d6b4", size = 1163816, upload-time = "2024-10-16T11:20:30.777Z" }, + { url = "https://files.pythonhosted.org/packages/49/7d/465cc9795cf76f6d329efdafca74693714556ea3891813701ac1fee87545/psycopg2_binary-2.9.10-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:880845dfe1f85d9d5f7c412efea7a08946a46894537e4e5d091732eb1d34d9a0", size = 3044771, upload-time = "2024-10-16T11:20:35.234Z" }, + { url = "https://files.pythonhosted.org/packages/8b/31/6d225b7b641a1a2148e3ed65e1aa74fc86ba3fee850545e27be9e1de893d/psycopg2_binary-2.9.10-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9440fa522a79356aaa482aa4ba500b65f28e5d0e63b801abf6aa152a29bd842a", size = 3275336, upload-time = "2024-10-16T11:20:38.742Z" }, + { url = "https://files.pythonhosted.org/packages/30/b7/a68c2b4bff1cbb1728e3ec864b2d92327c77ad52edcd27922535a8366f68/psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3923c1d9870c49a2d44f795df0c889a22380d36ef92440ff618ec315757e539", size = 2851637, upload-time = "2024-10-16T11:20:42.145Z" }, + { url = "https://files.pythonhosted.org/packages/0b/b1/cfedc0e0e6f9ad61f8657fd173b2f831ce261c02a08c0b09c652b127d813/psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b2c956c028ea5de47ff3a8d6b3cc3330ab45cf0b7c3da35a2d6ff8420896526", size = 3082097, upload-time = "2024-10-16T11:20:46.185Z" }, + { url = "https://files.pythonhosted.org/packages/18/ed/0a8e4153c9b769f59c02fb5e7914f20f0b2483a19dae7bf2db54b743d0d0/psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f758ed67cab30b9a8d2833609513ce4d3bd027641673d4ebc9c067e4d208eec1", size = 3264776, upload-time = "2024-10-16T11:20:50.879Z" }, + { url = "https://files.pythonhosted.org/packages/10/db/d09da68c6a0cdab41566b74e0a6068a425f077169bed0946559b7348ebe9/psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cd9b4f2cfab88ed4a9106192de509464b75a906462fb846b936eabe45c2063e", size = 3020968, upload-time = "2024-10-16T11:20:56.819Z" }, + { url = "https://files.pythonhosted.org/packages/94/28/4d6f8c255f0dfffb410db2b3f9ac5218d959a66c715c34cac31081e19b95/psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dc08420625b5a20b53551c50deae6e231e6371194fa0651dbe0fb206452ae1f", size = 2872334, upload-time = "2024-10-16T11:21:02.411Z" }, + { url = "https://files.pythonhosted.org/packages/05/f7/20d7bf796593c4fea95e12119d6cc384ff1f6141a24fbb7df5a668d29d29/psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d7cd730dfa7c36dbe8724426bf5612798734bff2d3c3857f36f2733f5bfc7c00", size = 2822722, upload-time = "2024-10-16T11:21:09.01Z" }, + { url = "https://files.pythonhosted.org/packages/4d/e4/0c407ae919ef626dbdb32835a03b6737013c3cc7240169843965cada2bdf/psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:155e69561d54d02b3c3209545fb08938e27889ff5a10c19de8d23eb5a41be8a5", size = 2920132, upload-time = "2024-10-16T11:21:16.339Z" }, + { url = "https://files.pythonhosted.org/packages/2d/70/aa69c9f69cf09a01da224909ff6ce8b68faeef476f00f7ec377e8f03be70/psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3cc28a6fd5a4a26224007712e79b81dbaee2ffb90ff406256158ec4d7b52b47", size = 2959312, upload-time = "2024-10-16T11:21:25.584Z" }, + { url = "https://files.pythonhosted.org/packages/d3/bd/213e59854fafe87ba47814bf413ace0dcee33a89c8c8c814faca6bc7cf3c/psycopg2_binary-2.9.10-cp312-cp312-win32.whl", hash = "sha256:ec8a77f521a17506a24a5f626cb2aee7850f9b69a0afe704586f63a464f3cd64", size = 1025191, upload-time = "2024-10-16T11:21:29.912Z" }, + { url = "https://files.pythonhosted.org/packages/92/29/06261ea000e2dc1e22907dbbc483a1093665509ea586b29b8986a0e56733/psycopg2_binary-2.9.10-cp312-cp312-win_amd64.whl", hash = "sha256:18c5ee682b9c6dd3696dad6e54cc7ff3a1a9020df6a5c0f861ef8bfd338c3ca0", size = 1164031, upload-time = "2024-10-16T11:21:34.211Z" }, +] + +[[package]] +name = "pyasn1" +version = "0.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/e9/01f1a64245b89f039897cb0130016d79f77d52669aae6ee7b159a6c4c018/pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034", size = 145322, upload-time = "2024-09-10T22:41:42.55Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/f1/d6a797abb14f6283c0ddff96bbdd46937f64122b8c925cab503dd37f8214/pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629", size = 83135, upload-time = "2024-09-11T16:00:36.122Z" }, +] + +[[package]] +name = "pyasn1-modules" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyasn1" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/e6/78ebbb10a8c8e4b61a59249394a4a594c1a7af95593dc933a349c8d00964/pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6", size = 307892, upload-time = "2025-03-28T02:41:22.17Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a", size = 181259, upload-time = "2025-03-28T02:41:19.028Z" }, +] + +[[package]] +name = "pycparser" +version = "3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, +] + +[[package]] +name = "pygments" +version = "2.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, +] + +[[package]] +name = "pyjwt" +version = "2.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c2/27/a3b6e5bf6ff856d2509292e95c8f57f0df7017cf5394921fc4e4ef40308a/pyjwt-2.12.1.tar.gz", hash = "sha256:c74a7a2adf861c04d002db713dd85f84beb242228e671280bf709d765b03672b", size = 102564, upload-time = "2026-03-13T19:27:37.25Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/7a/8dd906bd22e79e47397a61742927f6747fe93242ef86645ee9092e610244/pyjwt-2.12.1-py3-none-any.whl", hash = "sha256:28ca37c070cad8ba8cd9790cd940535d40274d22f80ab87f3ac6a713e6e8454c", size = 29726, upload-time = "2026-03-13T19:27:35.677Z" }, +] + +[[package]] +name = "pysocks" +version = "1.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/11/293dd436aea955d45fc4e8a35b6ae7270f5b8e00b53cf6c024c83b657a11/PySocks-1.7.1.tar.gz", hash = "sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0", size = 284429, upload-time = "2019-09-20T02:07:35.714Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/59/b4572118e098ac8e46e399a1dd0f2d85403ce8bbaad9ec79373ed6badaf9/PySocks-1.7.1-py3-none-any.whl", hash = "sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5", size = 16725, upload-time = "2019-09-20T02:06:22.938Z" }, +] + +[[package]] +name = "pytest" +version = "8.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, +] + +[[package]] +name = "pytest-mock" +version = "3.15.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/68/14/eb014d26be205d38ad5ad20d9a80f7d201472e08167f0bb4361e251084a9/pytest_mock-3.15.1.tar.gz", hash = "sha256:1849a238f6f396da19762269de72cb1814ab44416fa73a8686deac10b0d87a0f", size = 34036, upload-time = "2025-09-16T16:37:27.081Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/cc/06253936f4a7fa2e0f48dfe6d851d9c56df896a9ab09ac019d70b760619c/pytest_mock-3.15.1-py3-none-any.whl", hash = "sha256:0a25e2eb88fe5168d535041d09a4529a188176ae608a6d249ee65abc0949630d", size = 10095, upload-time = "2025-09-16T16:37:25.734Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "python-dotenv" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/b0/4bc07ccd3572a2f9df7e6782f52b0c6c90dcbb803ac4a167702d7d0dfe1e/python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab", size = 41978, upload-time = "2025-06-24T04:21:07.341Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" }, +] + +[[package]] +name = "referencing" +version = "0.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "rpds-py" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" }, +] + +[[package]] +name = "requests" +version = "2.32.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, +] + +[[package]] +name = "requests-oauthlib" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "oauthlib" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/f2/05f29bc3913aea15eb670be136045bf5c5bbf4b99ecb839da9b422bb2c85/requests-oauthlib-2.0.0.tar.gz", hash = "sha256:b3dffaebd884d8cd778494369603a9e7b58d29111bf6b41bdc2dcd87203af4e9", size = 55650, upload-time = "2024-03-22T20:32:29.939Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/5d/63d4ae3b9daea098d5d6f5da83984853c1bbacd5dc826764b249fe119d24/requests_oauthlib-2.0.0-py2.py3-none-any.whl", hash = "sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36", size = 24179, upload-time = "2024-03-22T20:32:28.055Z" }, +] + +[[package]] +name = "rpds-py" +version = "0.30.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/af/3f2f423103f1113b36230496629986e0ef7e199d2aa8392452b484b38ced/rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84", size = 69469, upload-time = "2025-11-30T20:24:38.837Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/6e/f964e88b3d2abee2a82c1ac8366da848fce1c6d834dc2132c3fda3970290/rpds_py-0.30.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a2bffea6a4ca9f01b3f8e548302470306689684e61602aa3d141e34da06cf425", size = 370157, upload-time = "2025-11-30T20:21:53.789Z" }, + { url = "https://files.pythonhosted.org/packages/94/ba/24e5ebb7c1c82e74c4e4f33b2112a5573ddc703915b13a073737b59b86e0/rpds_py-0.30.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dc4f992dfe1e2bc3ebc7444f6c7051b4bc13cd8e33e43511e8ffd13bf407010d", size = 359676, upload-time = "2025-11-30T20:21:55.475Z" }, + { url = "https://files.pythonhosted.org/packages/84/86/04dbba1b087227747d64d80c3b74df946b986c57af0a9f0c98726d4d7a3b/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:422c3cb9856d80b09d30d2eb255d0754b23e090034e1deb4083f8004bd0761e4", size = 389938, upload-time = "2025-11-30T20:21:57.079Z" }, + { url = "https://files.pythonhosted.org/packages/42/bb/1463f0b1722b7f45431bdd468301991d1328b16cffe0b1c2918eba2c4eee/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:07ae8a593e1c3c6b82ca3292efbe73c30b61332fd612e05abee07c79359f292f", size = 402932, upload-time = "2025-11-30T20:21:58.47Z" }, + { url = "https://files.pythonhosted.org/packages/99/ee/2520700a5c1f2d76631f948b0736cdf9b0acb25abd0ca8e889b5c62ac2e3/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12f90dd7557b6bd57f40abe7747e81e0c0b119bef015ea7726e69fe550e394a4", size = 525830, upload-time = "2025-11-30T20:21:59.699Z" }, + { url = "https://files.pythonhosted.org/packages/e0/ad/bd0331f740f5705cc555a5e17fdf334671262160270962e69a2bdef3bf76/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99b47d6ad9a6da00bec6aabe5a6279ecd3c06a329d4aa4771034a21e335c3a97", size = 412033, upload-time = "2025-11-30T20:22:00.991Z" }, + { url = "https://files.pythonhosted.org/packages/f8/1e/372195d326549bb51f0ba0f2ecb9874579906b97e08880e7a65c3bef1a99/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33f559f3104504506a44bb666b93a33f5d33133765b0c216a5bf2f1e1503af89", size = 390828, upload-time = "2025-11-30T20:22:02.723Z" }, + { url = "https://files.pythonhosted.org/packages/ab/2b/d88bb33294e3e0c76bc8f351a3721212713629ffca1700fa94979cb3eae8/rpds_py-0.30.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:946fe926af6e44f3697abbc305ea168c2c31d3e3ef1058cf68f379bf0335a78d", size = 404683, upload-time = "2025-11-30T20:22:04.367Z" }, + { url = "https://files.pythonhosted.org/packages/50/32/c759a8d42bcb5289c1fac697cd92f6fe01a018dd937e62ae77e0e7f15702/rpds_py-0.30.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:495aeca4b93d465efde585977365187149e75383ad2684f81519f504f5c13038", size = 421583, upload-time = "2025-11-30T20:22:05.814Z" }, + { url = "https://files.pythonhosted.org/packages/2b/81/e729761dbd55ddf5d84ec4ff1f47857f4374b0f19bdabfcf929164da3e24/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9a0ca5da0386dee0655b4ccdf46119df60e0f10da268d04fe7cc87886872ba7", size = 572496, upload-time = "2025-11-30T20:22:07.713Z" }, + { url = "https://files.pythonhosted.org/packages/14/f6/69066a924c3557c9c30baa6ec3a0aa07526305684c6f86c696b08860726c/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8d6d1cc13664ec13c1b84241204ff3b12f9bb82464b8ad6e7a5d3486975c2eed", size = 598669, upload-time = "2025-11-30T20:22:09.312Z" }, + { url = "https://files.pythonhosted.org/packages/5f/48/905896b1eb8a05630d20333d1d8ffd162394127b74ce0b0784ae04498d32/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3896fa1be39912cf0757753826bc8bdc8ca331a28a7c4ae46b7a21280b06bb85", size = 561011, upload-time = "2025-11-30T20:22:11.309Z" }, + { url = "https://files.pythonhosted.org/packages/22/16/cd3027c7e279d22e5eb431dd3c0fbc677bed58797fe7581e148f3f68818b/rpds_py-0.30.0-cp311-cp311-win32.whl", hash = "sha256:55f66022632205940f1827effeff17c4fa7ae1953d2b74a8581baaefb7d16f8c", size = 221406, upload-time = "2025-11-30T20:22:13.101Z" }, + { url = "https://files.pythonhosted.org/packages/fa/5b/e7b7aa136f28462b344e652ee010d4de26ee9fd16f1bfd5811f5153ccf89/rpds_py-0.30.0-cp311-cp311-win_amd64.whl", hash = "sha256:a51033ff701fca756439d641c0ad09a41d9242fa69121c7d8769604a0a629825", size = 236024, upload-time = "2025-11-30T20:22:14.853Z" }, + { url = "https://files.pythonhosted.org/packages/14/a6/364bba985e4c13658edb156640608f2c9e1d3ea3c81b27aa9d889fff0e31/rpds_py-0.30.0-cp311-cp311-win_arm64.whl", hash = "sha256:47b0ef6231c58f506ef0b74d44e330405caa8428e770fec25329ed2cb971a229", size = 229069, upload-time = "2025-11-30T20:22:16.577Z" }, + { url = "https://files.pythonhosted.org/packages/03/e7/98a2f4ac921d82f33e03f3835f5bf3a4a40aa1bfdc57975e74a97b2b4bdd/rpds_py-0.30.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a161f20d9a43006833cd7068375a94d035714d73a172b681d8881820600abfad", size = 375086, upload-time = "2025-11-30T20:22:17.93Z" }, + { url = "https://files.pythonhosted.org/packages/4d/a1/bca7fd3d452b272e13335db8d6b0b3ecde0f90ad6f16f3328c6fb150c889/rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6abc8880d9d036ecaafe709079969f56e876fcf107f7a8e9920ba6d5a3878d05", size = 359053, upload-time = "2025-11-30T20:22:19.297Z" }, + { url = "https://files.pythonhosted.org/packages/65/1c/ae157e83a6357eceff62ba7e52113e3ec4834a84cfe07fa4b0757a7d105f/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca28829ae5f5d569bb62a79512c842a03a12576375d5ece7d2cadf8abe96ec28", size = 390763, upload-time = "2025-11-30T20:22:21.661Z" }, + { url = "https://files.pythonhosted.org/packages/d4/36/eb2eb8515e2ad24c0bd43c3ee9cd74c33f7ca6430755ccdb240fd3144c44/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1010ed9524c73b94d15919ca4d41d8780980e1765babf85f9a2f90d247153dd", size = 408951, upload-time = "2025-11-30T20:22:23.408Z" }, + { url = "https://files.pythonhosted.org/packages/d6/65/ad8dc1784a331fabbd740ef6f71ce2198c7ed0890dab595adb9ea2d775a1/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8d1736cfb49381ba528cd5baa46f82fdc65c06e843dab24dd70b63d09121b3f", size = 514622, upload-time = "2025-11-30T20:22:25.16Z" }, + { url = "https://files.pythonhosted.org/packages/63/8e/0cfa7ae158e15e143fe03993b5bcd743a59f541f5952e1546b1ac1b5fd45/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d948b135c4693daff7bc2dcfc4ec57237a29bd37e60c2fabf5aff2bbacf3e2f1", size = 414492, upload-time = "2025-11-30T20:22:26.505Z" }, + { url = "https://files.pythonhosted.org/packages/60/1b/6f8f29f3f995c7ffdde46a626ddccd7c63aefc0efae881dc13b6e5d5bb16/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47f236970bccb2233267d89173d3ad2703cd36a0e2a6e92d0560d333871a3d23", size = 394080, upload-time = "2025-11-30T20:22:27.934Z" }, + { url = "https://files.pythonhosted.org/packages/6d/d5/a266341051a7a3ca2f4b750a3aa4abc986378431fc2da508c5034d081b70/rpds_py-0.30.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:2e6ecb5a5bcacf59c3f912155044479af1d0b6681280048b338b28e364aca1f6", size = 408680, upload-time = "2025-11-30T20:22:29.341Z" }, + { url = "https://files.pythonhosted.org/packages/10/3b/71b725851df9ab7a7a4e33cf36d241933da66040d195a84781f49c50490c/rpds_py-0.30.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a8fa71a2e078c527c3e9dc9fc5a98c9db40bcc8a92b4e8858e36d329f8684b51", size = 423589, upload-time = "2025-11-30T20:22:31.469Z" }, + { url = "https://files.pythonhosted.org/packages/00/2b/e59e58c544dc9bd8bd8384ecdb8ea91f6727f0e37a7131baeff8d6f51661/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73c67f2db7bc334e518d097c6d1e6fed021bbc9b7d678d6cc433478365d1d5f5", size = 573289, upload-time = "2025-11-30T20:22:32.997Z" }, + { url = "https://files.pythonhosted.org/packages/da/3e/a18e6f5b460893172a7d6a680e86d3b6bc87a54c1f0b03446a3c8c7b588f/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5ba103fb455be00f3b1c2076c9d4264bfcb037c976167a6047ed82f23153f02e", size = 599737, upload-time = "2025-11-30T20:22:34.419Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e2/714694e4b87b85a18e2c243614974413c60aa107fd815b8cbc42b873d1d7/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7cee9c752c0364588353e627da8a7e808a66873672bcb5f52890c33fd965b394", size = 563120, upload-time = "2025-11-30T20:22:35.903Z" }, + { url = "https://files.pythonhosted.org/packages/6f/ab/d5d5e3bcedb0a77f4f613706b750e50a5a3ba1c15ccd3665ecc636c968fd/rpds_py-0.30.0-cp312-cp312-win32.whl", hash = "sha256:1ab5b83dbcf55acc8b08fc62b796ef672c457b17dbd7820a11d6c52c06839bdf", size = 223782, upload-time = "2025-11-30T20:22:37.271Z" }, + { url = "https://files.pythonhosted.org/packages/39/3b/f786af9957306fdc38a74cef405b7b93180f481fb48453a114bb6465744a/rpds_py-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:a090322ca841abd453d43456ac34db46e8b05fd9b3b4ac0c78bcde8b089f959b", size = 240463, upload-time = "2025-11-30T20:22:39.021Z" }, + { url = "https://files.pythonhosted.org/packages/f3/d2/b91dc748126c1559042cfe41990deb92c4ee3e2b415f6b5234969ffaf0cc/rpds_py-0.30.0-cp312-cp312-win_arm64.whl", hash = "sha256:669b1805bd639dd2989b281be2cfd951c6121b65e729d9b843e9639ef1fd555e", size = 230868, upload-time = "2025-11-30T20:22:40.493Z" }, + { url = "https://files.pythonhosted.org/packages/69/71/3f34339ee70521864411f8b6992e7ab13ac30d8e4e3309e07c7361767d91/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c2262bdba0ad4fc6fb5545660673925c2d2a5d9e2e0fb603aad545427be0fc58", size = 372292, upload-time = "2025-11-30T20:24:16.537Z" }, + { url = "https://files.pythonhosted.org/packages/57/09/f183df9b8f2d66720d2ef71075c59f7e1b336bec7ee4c48f0a2b06857653/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ee6af14263f25eedc3bb918a3c04245106a42dfd4f5c2285ea6f997b1fc3f89a", size = 362128, upload-time = "2025-11-30T20:24:18.086Z" }, + { url = "https://files.pythonhosted.org/packages/7a/68/5c2594e937253457342e078f0cc1ded3dd7b2ad59afdbf2d354869110a02/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3adbb8179ce342d235c31ab8ec511e66c73faa27a47e076ccc92421add53e2bb", size = 391542, upload-time = "2025-11-30T20:24:20.092Z" }, + { url = "https://files.pythonhosted.org/packages/49/5c/31ef1afd70b4b4fbdb2800249f34c57c64beb687495b10aec0365f53dfc4/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:250fa00e9543ac9b97ac258bd37367ff5256666122c2d0f2bc97577c60a1818c", size = 404004, upload-time = "2025-11-30T20:24:22.231Z" }, + { url = "https://files.pythonhosted.org/packages/e3/63/0cfbea38d05756f3440ce6534d51a491d26176ac045e2707adc99bb6e60a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9854cf4f488b3d57b9aaeb105f06d78e5529d3145b1e4a41750167e8c213c6d3", size = 527063, upload-time = "2025-11-30T20:24:24.302Z" }, + { url = "https://files.pythonhosted.org/packages/42/e6/01e1f72a2456678b0f618fc9a1a13f882061690893c192fcad9f2926553a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:993914b8e560023bc0a8bf742c5f303551992dcb85e247b1e5c7f4a7d145bda5", size = 413099, upload-time = "2025-11-30T20:24:25.916Z" }, + { url = "https://files.pythonhosted.org/packages/b8/25/8df56677f209003dcbb180765520c544525e3ef21ea72279c98b9aa7c7fb/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58edca431fb9b29950807e301826586e5bbf24163677732429770a697ffe6738", size = 392177, upload-time = "2025-11-30T20:24:27.834Z" }, + { url = "https://files.pythonhosted.org/packages/4a/b4/0a771378c5f16f8115f796d1f437950158679bcd2a7c68cf251cfb00ed5b/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:dea5b552272a944763b34394d04577cf0f9bd013207bc32323b5a89a53cf9c2f", size = 406015, upload-time = "2025-11-30T20:24:29.457Z" }, + { url = "https://files.pythonhosted.org/packages/36/d8/456dbba0af75049dc6f63ff295a2f92766b9d521fa00de67a2bd6427d57a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ba3af48635eb83d03f6c9735dfb21785303e73d22ad03d489e88adae6eab8877", size = 423736, upload-time = "2025-11-30T20:24:31.22Z" }, + { url = "https://files.pythonhosted.org/packages/13/64/b4d76f227d5c45a7e0b796c674fd81b0a6c4fbd48dc29271857d8219571c/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:dff13836529b921e22f15cb099751209a60009731a68519630a24d61f0b1b30a", size = 573981, upload-time = "2025-11-30T20:24:32.934Z" }, + { url = "https://files.pythonhosted.org/packages/20/91/092bacadeda3edf92bf743cc96a7be133e13a39cdbfd7b5082e7ab638406/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:1b151685b23929ab7beec71080a8889d4d6d9fa9a983d213f07121205d48e2c4", size = 599782, upload-time = "2025-11-30T20:24:35.169Z" }, + { url = "https://files.pythonhosted.org/packages/d1/b7/b95708304cd49b7b6f82fdd039f1748b66ec2b21d6a45180910802f1abf1/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:ac37f9f516c51e5753f27dfdef11a88330f04de2d564be3991384b2f3535d02e", size = 562191, upload-time = "2025-11-30T20:24:36.853Z" }, +] + +[[package]] +name = "rsa" +version = "4.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyasn1" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/da/8a/22b7beea3ee0d44b1916c0c1cb0ee3af23b700b6da9f04991899d0c555d4/rsa-4.9.1.tar.gz", hash = "sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75", size = 29034, upload-time = "2025-04-16T09:51:18.218Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/8d/0133e4eb4beed9e425d9a98ed6e081a55d195481b7632472be1af08d2f6b/rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762", size = 34696, upload-time = "2025-04-16T09:51:17.142Z" }, +] + +[[package]] +name = "selenium" +version = "4.39.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "trio" }, + { name = "trio-websocket" }, + { name = "typing-extensions" }, + { name = "urllib3", extra = ["socks"] }, + { name = "websocket-client" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/af/19/27c1bf9eb1f7025632d35a956b50746efb4b10aa87f961b263fa7081f4c5/selenium-4.39.0.tar.gz", hash = "sha256:12f3325f02d43b6c24030fc9602b34a3c6865abbb1db9406641d13d108aa1889", size = 928575, upload-time = "2025-12-06T23:12:34.896Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/d0/55a6b7c6f35aad4c8a54be0eb7a52c1ff29a59542fc3e655f0ecbb14456d/selenium-4.39.0-py3-none-any.whl", hash = "sha256:c85f65d5610642ca0f47dae9d5cc117cd9e831f74038bc09fe1af126288200f9", size = 9655249, upload-time = "2025-12-06T23:12:33.085Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, +] + +[[package]] +name = "sortedcontainers" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/c4/ba2f8066cceb6f23394729afe52f3bf7adec04bf9ed2c820b39e19299111/sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", size = 30594, upload-time = "2021-05-16T22:03:42.897Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0", size = 29575, upload-time = "2021-05-16T22:03:41.177Z" }, +] + +[[package]] +name = "soupsieve" +version = "2.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/e6/21ccce3262dd4889aa3332e5a119a3491a95e8f60939870a3a035aabac0d/soupsieve-2.8.tar.gz", hash = "sha256:e2dd4a40a628cb5f28f6d4b0db8800b8f581b65bb380b97de22ba5ca8d72572f", size = 103472, upload-time = "2025-08-27T15:39:51.78Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/a0/bb38d3b76b8cae341dad93a2dd83ab7462e6dbcdd84d43f54ee60a8dc167/soupsieve-2.8-py3-none-any.whl", hash = "sha256:0cc76456a30e20f5d7f2e14a98a4ae2ee4e5abdc7c5ea0aafe795f344bc7984c", size = 36679, upload-time = "2025-08-27T15:39:50.179Z" }, +] + +[[package]] +name = "sqlalchemy" +version = "2.0.43" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d7/bc/d59b5d97d27229b0e009bd9098cd81af71c2fa5549c580a0a67b9bed0496/sqlalchemy-2.0.43.tar.gz", hash = "sha256:788bfcef6787a7764169cfe9859fe425bf44559619e1d9f56f5bddf2ebf6f417", size = 9762949, upload-time = "2025-08-11T14:24:58.438Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/77/fa7189fe44114658002566c6fe443d3ed0ec1fa782feb72af6ef7fbe98e7/sqlalchemy-2.0.43-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:52d9b73b8fb3e9da34c2b31e6d99d60f5f99fd8c1225c9dad24aeb74a91e1d29", size = 2136472, upload-time = "2025-08-11T15:52:21.789Z" }, + { url = "https://files.pythonhosted.org/packages/99/ea/92ac27f2fbc2e6c1766bb807084ca455265707e041ba027c09c17d697867/sqlalchemy-2.0.43-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f42f23e152e4545157fa367b2435a1ace7571cab016ca26038867eb7df2c3631", size = 2126535, upload-time = "2025-08-11T15:52:23.109Z" }, + { url = "https://files.pythonhosted.org/packages/94/12/536ede80163e295dc57fff69724caf68f91bb40578b6ac6583a293534849/sqlalchemy-2.0.43-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fb1a8c5438e0c5ea51afe9c6564f951525795cf432bed0c028c1cb081276685", size = 3297521, upload-time = "2025-08-11T15:50:33.536Z" }, + { url = "https://files.pythonhosted.org/packages/03/b5/cacf432e6f1fc9d156eca0560ac61d4355d2181e751ba8c0cd9cb232c8c1/sqlalchemy-2.0.43-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db691fa174e8f7036afefe3061bc40ac2b770718be2862bfb03aabae09051aca", size = 3297343, upload-time = "2025-08-11T15:57:51.186Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ba/d4c9b526f18457667de4c024ffbc3a0920c34237b9e9dd298e44c7c00ee5/sqlalchemy-2.0.43-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fe2b3b4927d0bc03d02ad883f402d5de201dbc8894ac87d2e981e7d87430e60d", size = 3232113, upload-time = "2025-08-11T15:50:34.949Z" }, + { url = "https://files.pythonhosted.org/packages/aa/79/c0121b12b1b114e2c8a10ea297a8a6d5367bc59081b2be896815154b1163/sqlalchemy-2.0.43-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4d3d9b904ad4a6b175a2de0738248822f5ac410f52c2fd389ada0b5262d6a1e3", size = 3258240, upload-time = "2025-08-11T15:57:52.983Z" }, + { url = "https://files.pythonhosted.org/packages/79/99/a2f9be96fb382f3ba027ad42f00dbe30fdb6ba28cda5f11412eee346bec5/sqlalchemy-2.0.43-cp311-cp311-win32.whl", hash = "sha256:5cda6b51faff2639296e276591808c1726c4a77929cfaa0f514f30a5f6156921", size = 2101248, upload-time = "2025-08-11T15:55:01.855Z" }, + { url = "https://files.pythonhosted.org/packages/ee/13/744a32ebe3b4a7a9c7ea4e57babae7aa22070d47acf330d8e5a1359607f1/sqlalchemy-2.0.43-cp311-cp311-win_amd64.whl", hash = "sha256:c5d1730b25d9a07727d20ad74bc1039bbbb0a6ca24e6769861c1aa5bf2c4c4a8", size = 2126109, upload-time = "2025-08-11T15:55:04.092Z" }, + { url = "https://files.pythonhosted.org/packages/61/db/20c78f1081446095450bdc6ee6cc10045fce67a8e003a5876b6eaafc5cc4/sqlalchemy-2.0.43-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:20d81fc2736509d7a2bd33292e489b056cbae543661bb7de7ce9f1c0cd6e7f24", size = 2134891, upload-time = "2025-08-11T15:51:13.019Z" }, + { url = "https://files.pythonhosted.org/packages/45/0a/3d89034ae62b200b4396f0f95319f7d86e9945ee64d2343dcad857150fa2/sqlalchemy-2.0.43-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:25b9fc27650ff5a2c9d490c13c14906b918b0de1f8fcbb4c992712d8caf40e83", size = 2123061, upload-time = "2025-08-11T15:51:14.319Z" }, + { url = "https://files.pythonhosted.org/packages/cb/10/2711f7ff1805919221ad5bee205971254845c069ee2e7036847103ca1e4c/sqlalchemy-2.0.43-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6772e3ca8a43a65a37c88e2f3e2adfd511b0b1da37ef11ed78dea16aeae85bd9", size = 3320384, upload-time = "2025-08-11T15:52:35.088Z" }, + { url = "https://files.pythonhosted.org/packages/6e/0e/3d155e264d2ed2778484006ef04647bc63f55b3e2d12e6a4f787747b5900/sqlalchemy-2.0.43-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a113da919c25f7f641ffbd07fbc9077abd4b3b75097c888ab818f962707eb48", size = 3329648, upload-time = "2025-08-11T15:56:34.153Z" }, + { url = "https://files.pythonhosted.org/packages/5b/81/635100fb19725c931622c673900da5efb1595c96ff5b441e07e3dd61f2be/sqlalchemy-2.0.43-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4286a1139f14b7d70141c67a8ae1582fc2b69105f1b09d9573494eb4bb4b2687", size = 3258030, upload-time = "2025-08-11T15:52:36.933Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ed/a99302716d62b4965fded12520c1cbb189f99b17a6d8cf77611d21442e47/sqlalchemy-2.0.43-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:529064085be2f4d8a6e5fab12d36ad44f1909a18848fcfbdb59cc6d4bbe48efe", size = 3294469, upload-time = "2025-08-11T15:56:35.553Z" }, + { url = "https://files.pythonhosted.org/packages/5d/a2/3a11b06715149bf3310b55a98b5c1e84a42cfb949a7b800bc75cb4e33abc/sqlalchemy-2.0.43-cp312-cp312-win32.whl", hash = "sha256:b535d35dea8bbb8195e7e2b40059e2253acb2b7579b73c1b432a35363694641d", size = 2098906, upload-time = "2025-08-11T15:55:00.645Z" }, + { url = "https://files.pythonhosted.org/packages/bc/09/405c915a974814b90aa591280623adc6ad6b322f61fd5cff80aeaef216c9/sqlalchemy-2.0.43-cp312-cp312-win_amd64.whl", hash = "sha256:1c6d85327ca688dbae7e2b06d7d84cfe4f3fffa5b5f9e21bb6ce9d0e1a0e0e0a", size = 2126260, upload-time = "2025-08-11T15:55:02.965Z" }, + { url = "https://files.pythonhosted.org/packages/b8/d9/13bdde6521f322861fab67473cec4b1cc8999f3871953531cf61945fad92/sqlalchemy-2.0.43-py3-none-any.whl", hash = "sha256:1681c21dd2ccee222c2fe0bef671d1aef7c504087c9c4e800371cfcc8ac966fc", size = 1924759, upload-time = "2025-08-11T15:39:53.024Z" }, +] + +[[package]] +name = "trio" +version = "0.33.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "cffi", marker = "implementation_name != 'pypy' and os_name == 'nt'" }, + { name = "idna" }, + { name = "outcome" }, + { name = "sniffio" }, + { name = "sortedcontainers" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/52/b6/c744031c6f89b18b3f5f4f7338603ab381d740a7f45938c4607b2302481f/trio-0.33.0.tar.gz", hash = "sha256:a29b92b73f09d4b48ed249acd91073281a7f1063f09caba5dc70465b5c7aa970", size = 605109, upload-time = "2026-02-14T18:40:55.386Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1c/93/dab25dc87ac48da0fe0f6419e07d0bfd98799bed4e05e7b9e0f85a1a4b4b/trio-0.33.0-py3-none-any.whl", hash = "sha256:3bd5d87f781d9b0192d592aef28691f8951d6c2e41b7e1da4c25cde6c180ae9b", size = 510294, upload-time = "2026-02-14T18:40:53.313Z" }, +] + +[[package]] +name = "trio-websocket" +version = "0.12.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "outcome" }, + { name = "trio" }, + { name = "wsproto" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/3c/8b4358e81f2f2cfe71b66a267f023a91db20a817b9425dd964873796980a/trio_websocket-0.12.2.tar.gz", hash = "sha256:22c72c436f3d1e264d0910a3951934798dcc5b00ae56fc4ee079d46c7cf20fae", size = 33549, upload-time = "2025-02-25T05:16:58.947Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/19/eb640a397bba49ba49ef9dbe2e7e5c04202ba045b6ce2ec36e9cadc51e04/trio_websocket-0.12.2-py3-none-any.whl", hash = "sha256:df605665f1db533f4a386c94525870851096a223adcb97f72a07e8b4beba45b6", size = 21221, upload-time = "2025-02-25T05:16:57.545Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "tzdata" +version = "2026.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/19/1b9b0e29f30c6d35cb345486df41110984ea67ae69dddbc0e8a100999493/tzdata-2026.2.tar.gz", hash = "sha256:9173fde7d80d9018e02a662e168e5a2d04f87c41ea174b139fbef642eda62d10", size = 198254, upload-time = "2026-04-24T15:22:08.651Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/e4/dccd7f47c4b64213ac01ef921a1337ee6e30e8c6466046018326977efd95/tzdata-2026.2-py2.py3-none-any.whl", hash = "sha256:bbe9af844f658da81a5f95019480da3a89415801f6cc966806612cc7169bffe7", size = 349321, upload-time = "2026-04-24T15:22:05.876Z" }, +] + +[[package]] +name = "urllib3" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, +] + +[package.optional-dependencies] +socks = [ + { name = "pysocks" }, +] + +[[package]] +name = "uwsgi" +version = "2.0.31" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9f/49/2f57640e889ba509fd1fae10cccec1b58972a07c2724486efba94c5ea448/uwsgi-2.0.31.tar.gz", hash = "sha256:e8f8b350ccc106ff93a65247b9136f529c14bf96b936ac5b264c6ff9d0c76257", size = 822796, upload-time = "2025-10-11T19:17:28.794Z" } + +[[package]] +name = "vine" +version = "5.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/e4/d07b5f29d283596b9727dd5275ccbceb63c44a1a82aa9e4bfd20426762ac/vine-5.1.0.tar.gz", hash = "sha256:8b62e981d35c41049211cf62a0a1242d8c1ee9bd15bb196ce38aefd6799e61e0", size = 48980, upload-time = "2023-11-05T08:46:53.857Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/ff/7c0c86c43b3cbb927e0ccc0255cb4057ceba4799cd44ae95174ce8e8b5b2/vine-5.1.0-py3-none-any.whl", hash = "sha256:40fdf3c48b2cfe1c38a49e9ae2da6fda88e4794c810050a728bd7413811fb1dc", size = 9636, upload-time = "2023-11-05T08:46:51.205Z" }, +] + +[[package]] +name = "wcwidth" +version = "0.2.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/63/53559446a878410fc5a5974feb13d31d78d752eb18aeba59c7fef1af7598/wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5", size = 101301, upload-time = "2024-01-06T02:10:57.829Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166, upload-time = "2024-01-06T02:10:55.763Z" }, +] + +[[package]] +name = "websocket-client" +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e6/30/fba0d96b4b5fbf5948ed3f4681f7da2f9f64512e1d303f94b4cc174c24a5/websocket_client-1.8.0.tar.gz", hash = "sha256:3239df9f44da632f96012472805d40a23281a991027ce11d2f45a6f24ac4c3da", size = 54648, upload-time = "2024-04-23T22:16:16.976Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/84/44687a29792a70e111c5c477230a72c4b957d88d16141199bf9acb7537a3/websocket_client-1.8.0-py3-none-any.whl", hash = "sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526", size = 58826, upload-time = "2024-04-23T22:16:14.422Z" }, +] + +[[package]] +name = "werkzeug" +version = "3.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9f/69/83029f1f6300c5fb2471d621ab06f6ec6b3324685a2ce0f9777fd4a8b71e/werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746", size = 806925, upload-time = "2024-11-08T15:52:18.093Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/24/ab44c871b0f07f491e5d2ad12c9bd7358e527510618cb1b803a88e986db1/werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e", size = 224498, upload-time = "2024-11-08T15:52:16.132Z" }, +] + +[[package]] +name = "wsproto" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/4a/44d3c295350d776427904d73c189e10aeae66d7f555bb2feee16d1e4ba5a/wsproto-1.2.0.tar.gz", hash = "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065", size = 53425, upload-time = "2022-08-23T19:58:21.447Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/58/e860788190eba3bcce367f74d29c4675466ce8dddfba85f7827588416f01/wsproto-1.2.0-py3-none-any.whl", hash = "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736", size = 24226, upload-time = "2022-08-23T19:58:19.96Z" }, +] + +[[package]] +name = "wtforms" +version = "3.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/01/e4/633d080897e769ed5712dcfad626e55dbd6cf45db0ff4d9884315c6a82da/wtforms-3.2.1.tar.gz", hash = "sha256:df3e6b70f3192e92623128123ec8dca3067df9cfadd43d59681e210cfb8d4682", size = 137801, upload-time = "2024-10-21T11:34:00.108Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/c9/2088fb5645cd289c99ebe0d4cdcc723922a1d8e1beaefb0f6f76dff9b21c/wtforms-3.2.1-py3-none-any.whl", hash = "sha256:583bad77ba1dd7286463f21e11aa3043ca4869d03575921d1a1698d0715e0fd4", size = 152454, upload-time = "2024-10-21T11:33:58.44Z" }, +] + +[[package]] +name = "zipp" +version = "3.23.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, +] diff --git a/front/web/package-lock.json b/front/web/package-lock.json new file mode 100644 index 00000000..9b1f22ba --- /dev/null +++ b/front/web/package-lock.json @@ -0,0 +1,1026 @@ +{ + "name": "miminet-front-web", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "miminet-front-web", + "version": "0.1.0", + "devDependencies": { + "vite": "^5.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.4.tgz", + "integrity": "sha512-F5QXMSiFebS9hKZj02XhWLLnRpJ3B3AROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mS/IMQloYgYxCuJC0fxEQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.4.tgz", + "integrity": "sha512-GxxTKApUpzRhof7poWvCJHRF51C67u1R7D6DiluBE8wKU1u5GWE8t+v81JvJYtbawoBFX1hLv5Ei4eVjkWokaw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.4.tgz", + "integrity": "sha512-tua0TaJxMOB1R0V0RS1jFZ/RpURFDJIOR2A6jWwQeawuFyS4gBW+rntLRaQd0EQ4bd6Vp44Z2rXW+YYDBsj6IA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.4.tgz", + "integrity": "sha512-CSKq7MsP+5PFIcydhAiR1K0UhEI1A2jWXVKHPCBZ151yOutENwvnPocgVHkivu2kviURtCEB6zUQw0vs8RrhMg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.4.tgz", + "integrity": "sha512-+O8OkVdyvXMtJEciu2wS/pzm1IxntEEQx3z5TAVy4l32G0etZn+RsA48ARRrFm6Ri8fvqPQfgrvNxSjKAbnd3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.4.tgz", + "integrity": "sha512-Iw3oMskH3AfNuhU0MSN7vNbdi4me/NiYo2azqPz/Le16zHSa+3RRmliCMWWQmh4lcndccU40xcJuTYJZxNo/lw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.4.tgz", + "integrity": "sha512-EIPRXTVQpHyF8WOo219AD2yEltPehLTcTMz2fn6JsatLYSzQf00hj3rulF+yauOlF9/FtM2WpkT/hJh/KJFGhA==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.4.tgz", + "integrity": "sha512-J3Yh9PzzF1Ovah2At+lHiGQdsYgArxBbXv/zHfSyaiFQEqvNv7DcW98pCrmdjCZBrqBiKrKKe2V+aaSGWuBe/w==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.4.tgz", + "integrity": "sha512-BFDEZMYfUvLn37ONE1yMBojPxnMlTFsdyNoqncT0qFq1mAfllL+ATMMJd8TeuVMiX84s1KbcxcZbXInmcO2mRg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.4.tgz", + "integrity": "sha512-pc9EYOSlOgdQ2uPl1o9PF6/kLSgaUosia7gOuS8mB69IxJvlclko1MECXysjs5ryez1/5zjYqx3+xYU0TU6R1A==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.4.tgz", + "integrity": "sha512-NxnomyxYerDh5n4iLrNa+sH+Z+U4BMEE46V2PgQ/hoB909i8gV1M5wPojWg9fk1jWpO3IQnOs20K4wyZuFLEFQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.4.tgz", + "integrity": "sha512-nbJnQ8a3z1mtmrwImCYhc6BGpThAyYVRQxw9uKSKG4wR6aAYno9sVjJ0zaZcW9BPJX1GbrDPf+SvdWjgTuDmnw==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.4.tgz", + "integrity": "sha512-2EU6acNrQLd8tYvo/LXW535wupT3m6fo7HKo6lr7ktQoItxTyOL1ZCR/GfGCuXl2vR+zmfI6eRXkSemafv+iVg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.4.tgz", + "integrity": "sha512-WeBtoMuaMxiiIrO2IYP3xs6GMWkJP2C0EoT8beTLkUPmzV1i/UcOSVw1d5r9KBODtHKilG5yFxsGRnBbK3wJ4A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.4.tgz", + "integrity": "sha512-FJHFfqpKUI3A10WrWKiFbBZ7yVbGT4q4B5o1qKFFojqpaYoh9LrQgqWCmmcxQzVSXYtyB5bzkXrYzlHTs21MYA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.4.tgz", + "integrity": "sha512-mcEl6CUT5IAUmQf1m9FYSmVqCJlpQ8r8eyftFUHG8i9OhY7BkBXSUdnLH5DOf0wCOjcP9v/QO93zpmF1SptCCw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.4.tgz", + "integrity": "sha512-ynt3JxVd2w2buzoKDWIyiV1pJW93xlQic1THVLXilz429oijRpSHivZAgp65KBu+cMcgf1eVVjdnTLvPxgCuoQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.4.tgz", + "integrity": "sha512-Boiz5+MsaROEWDf+GGEwF8VMHGhlUoQMtIPjOgA5fv4osupqTVnJteQNKJwUcnUog2G55jYXH7KZFFiJe0TEzQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.4.tgz", + "integrity": "sha512-+qfSY27qIrFfI/Hom04KYFw3GKZSGU4lXus51wsb5EuySfFlWRwjkKWoE9emgRw/ukoT4Udsj4W/+xxG8VbPKg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.4.tgz", + "integrity": "sha512-VpTfOPHgVXEBeeR8hZ2O0F3aSso+JDWqTWmTmzcQKted54IAdUVbxE+j/MVxUsKa8L20HJhv3vUezVPoquqWjA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.4.tgz", + "integrity": "sha512-IPOsh5aRYuLv/nkU51X10Bf75Bsf6+gZdx1X+QP5QM6lIJFHHqbHLG0uJn/hWthzo13UAc2umiUorqZy3axoZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.4.tgz", + "integrity": "sha512-4QzE9E81OohJ/HKzHhsqU+zcYYojVOXlFMs1DdyMT6qXl/niOH7AVElmmEdUNHHS/oRkc++d5k6Vy85zFs0DEw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.4.tgz", + "integrity": "sha512-zTPgT1YuHHcd+Tmx7h8aml0FWFVelV5N54oHow9SLj+GfoDy/huQ+UV396N/C7KpMDMiPspRktzM1/0r1usYEA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.4.tgz", + "integrity": "sha512-DRS4G7mi9lJxqEDezIkKCaUIKCrLUUDCUaCsTPCi/rtqaC6D/jjwslMQyiDU50Ka0JKpeXeRBFBAXwArY52vBw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.4.tgz", + "integrity": "sha512-QVTUovf40zgTqlFVrKA1uXMVvU2QWEFWfAH8Wdc48IxLvrJMQVMBRjuQyUpzZCDkakImib9eVazbWlC6ksWtJw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.5.14", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz", + "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rollup": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.4.tgz", + "integrity": "sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.4", + "@rollup/rollup-android-arm64": "4.60.4", + "@rollup/rollup-darwin-arm64": "4.60.4", + "@rollup/rollup-darwin-x64": "4.60.4", + "@rollup/rollup-freebsd-arm64": "4.60.4", + "@rollup/rollup-freebsd-x64": "4.60.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.4", + "@rollup/rollup-linux-arm-musleabihf": "4.60.4", + "@rollup/rollup-linux-arm64-gnu": "4.60.4", + "@rollup/rollup-linux-arm64-musl": "4.60.4", + "@rollup/rollup-linux-loong64-gnu": "4.60.4", + "@rollup/rollup-linux-loong64-musl": "4.60.4", + "@rollup/rollup-linux-ppc64-gnu": "4.60.4", + "@rollup/rollup-linux-ppc64-musl": "4.60.4", + "@rollup/rollup-linux-riscv64-gnu": "4.60.4", + "@rollup/rollup-linux-riscv64-musl": "4.60.4", + "@rollup/rollup-linux-s390x-gnu": "4.60.4", + "@rollup/rollup-linux-x64-gnu": "4.60.4", + "@rollup/rollup-linux-x64-musl": "4.60.4", + "@rollup/rollup-openbsd-x64": "4.60.4", + "@rollup/rollup-openharmony-arm64": "4.60.4", + "@rollup/rollup-win32-arm64-msvc": "4.60.4", + "@rollup/rollup-win32-ia32-msvc": "4.60.4", + "@rollup/rollup-win32-x64-gnu": "4.60.4", + "@rollup/rollup-win32-x64-msvc": "4.60.4", + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + } + } +} diff --git a/front/web/package.json b/front/web/package.json new file mode 100644 index 00000000..513070f7 --- /dev/null +++ b/front/web/package.json @@ -0,0 +1,13 @@ +{ + "name": "miminet-front-web", + "version": "0.1.0", + "private": true, + "description": "Vite build pipeline for miminet front-end JS.", + "scripts": { + "build": "vite build", + "dev": "vite build --watch" + }, + "devDependencies": { + "vite": "^5.4.0" + } +} diff --git a/front/web/src/main.js b/front/web/src/main.js new file mode 100644 index 00000000..b49b4c25 --- /dev/null +++ b/front/web/src/main.js @@ -0,0 +1 @@ +window.miminetEntryLoaded = true; diff --git a/front/web/vite.config.js b/front/web/vite.config.js new file mode 100644 index 00000000..28958d18 --- /dev/null +++ b/front/web/vite.config.js @@ -0,0 +1,56 @@ +import { defineConfig } from 'vite'; +import { readFileSync, mkdirSync, writeFileSync, existsSync } from 'fs'; +import { dirname, resolve } from 'path'; + +const STATIC_ROOT = resolve(__dirname, '../src/static'); +const DIST_ROOT = resolve(STATIC_ROOT, 'dist'); + +const CLASSIC_BUNDLE = [ + 'netfront/state.js', + 'netfront/show_config.js', + 'netfront/network_ops.js', + 'netfront/draw.js', + 'netfront/simulation.js', + 'netfront/update_config.js', + 'netfront/runtime.js', + 'config_forms/common.js', + 'config_forms/device.js', + 'config_forms/shared.js', + 'config_forms/helpers.js', + 'config_forms/jobs.js', + 'config_forms/edit_jobs.js', +]; + +const concatClassicScripts = () => ({ + name: 'miminet-concat-classic-scripts', + apply: 'build', + closeBundle() { + const parts = []; + for (const rel of CLASSIC_BUNDLE) { + const abs = resolve(STATIC_ROOT, rel); + if (!existsSync(abs)) { + throw new Error(`Bundle source missing: ${abs}`); + } + parts.push(readFileSync(abs, 'utf8')); + } + const outFile = resolve(DIST_ROOT, 'miminet.classic.js'); + mkdirSync(dirname(outFile), { recursive: true }); + writeFileSync(outFile, parts.join('\n')); + }, +}); + +export default defineConfig({ + build: { + outDir: DIST_ROOT, + emptyOutDir: true, + rollupOptions: { + input: resolve(__dirname, 'src/main.js'), + output: { + entryFileNames: 'miminet.entry.js', + format: 'iife', + name: 'MiminetEntry', + }, + }, + }, + plugins: [concatClassicScripts()], +});