diff --git a/.env.development.example b/.env.development.example index 920c32d92..3023a21a6 100644 --- a/.env.development.example +++ b/.env.development.example @@ -1,16 +1,38 @@ -APP_NAME=Coolify-localhost -APP_ID=development +# Coolify Configuration APP_ENV=local +APP_NAME="Coolify Development" +APP_ID=development APP_KEY= -APP_DEBUG=true APP_URL=http://localhost APP_PORT=8000 -MUX_ENABLED=false +APP_DEBUG=true +SSH_MUX_ENABLED=false +# PostgreSQL Database Configuration +DB_DATABASE=coolify +DB_USERNAME=coolify +DB_PASSWORD=password +DB_HOST=host.docker.internal +DB_PORT=5432 + +# Ray Configuration +# Set to true to enable Ray +RAY_ENABLED=false +# Set custom ray port +RAY_PORT= + +# Clockwork Configuration +CLOCKWORK_ENABLED=false +CLOCKWORK_QUEUE_COLLECT=true + +# Enable Laravel Telescope for debugging +TELESCOPE_ENABLED=false + +# Selenium Driver URL for Dusk DUSK_DRIVER_URL=http://selenium:4444 -## For Andras only -# To purge cache +# Special Keys for Andras +# For cache purging BUNNY_API_KEY= -# To upload assets +# For asset uploads BUNNY_STORAGE_API_KEY= diff --git a/.env.production b/.env.production index f15a8b0e9..099ec7c25 100644 --- a/.env.production +++ b/.env.production @@ -1,10 +1,16 @@ +# Coolify Configuration APP_ID= APP_NAME=Coolify APP_KEY= +# PostgreSQL Database Configuration +DB_USERNAME=coolify DB_PASSWORD= + +# Redis Configuration REDIS_PASSWORD= +# Pusher Configuration PUSHER_APP_ID= PUSHER_APP_KEY= PUSHER_APP_SECRET= diff --git a/.github/ISSUE_TEMPLATE/BUG_REPORT.yml b/.github/ISSUE_TEMPLATE/BUG_REPORT.yml index f3d52b1b4..42df4785e 100644 --- a/.github/ISSUE_TEMPLATE/BUG_REPORT.yml +++ b/.github/ISSUE_TEMPLATE/BUG_REPORT.yml @@ -1,46 +1,65 @@ -name: Bug report -description: "Create a new bug report." +name: 🐞 Bug Report +description: "File a new bug report." title: "[Bug]: " +labels: ["🐛 Bug", "🔍 Triage"] body: - type: markdown attributes: - value: >- - # 💎 Bounty program (with - [algora.io](https://console.algora.io/org/coollabsio/bounties/new)) + value: | + > [!IMPORTANT] + > **Please ensure you are using the latest version of Coolify before submitting an issue, as the bug may have already been fixed in a recent update.** (Of course, if you're experiencing an issue on the latest version that wasn't present in a previous version, please let us know.) + # 💎 Bounty Program (with [algora.io](https://console.algora.io/org/coollabsio/bounties/new)) + - If you would like to prioritize the issue resolution, consider adding a bounty to this issue through our [Bounty Program](https://console.algora.io/org/coollabsio/bounties/new). - If you would like to prioritize the issue resolution, you can add bounty - to this issue. - - - Click [here](https://console.algora.io/org/coollabsio/bounties/new) to - get started. - type: textarea attributes: - label: Description - description: A clear and concise description of the problem - - type: textarea - attributes: - label: Minimal Reproduction (if possible, example repository) - description: Please provide a step by step guide to reproduce the issue. + label: Error Message and Logs + description: Provide a detailed description of the error or exception you encountered, along with any relevant log output. validations: required: true + - type: textarea attributes: - label: Exception or Error - description: Please provide error logs if possible. + label: Steps to Reproduce + description: Please provide a step-by-step guide to reproduce the issue. Be as detailed as possible, otherwise we may not be able to assist you. + value: | + 1. + 2. + 3. + 4. + validations: + required: true + - type: input attributes: - label: Version - description: Coolify's version (see top of your screen). + label: Example Repository URL + description: If applicable, provide a URL to a repository demonstrating the issue. + + - type: input + attributes: + label: Coolify Version + description: Please provide the Coolify version you are using. This can be found in the top left corner of your Coolify dashboard. + placeholder: "v4.0.0-beta.335" validations: required: true - - type: checkboxes + + - type: dropdown attributes: - label: Cloud? - description: "Are you using the cloud version of Coolify?" + label: Are you using Coolify Cloud? options: - - label: 'Yes' - required: false - - label: 'No' - required: false + - "No (self-hosted)" + - "Yes (Coolify Cloud)" + validations: + required: true + + - type: input + attributes: + label: Operating System and Version (self-hosted) + description: Run `cat /etc/os-release` or `lsb_release -a` in your terminal and provide the operating system and version. + placeholder: "Ubuntu 22.04" + + - type: textarea + attributes: + label: Additional Information + description: Any other relevant details about the issue. diff --git a/.github/ISSUE_TEMPLATE/ENHANCEMENT_BOUNTY.yml b/.github/ISSUE_TEMPLATE/ENHANCEMENT_BOUNTY.yml new file mode 100644 index 000000000..ef26125e0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/ENHANCEMENT_BOUNTY.yml @@ -0,0 +1,31 @@ +name: 💎 Enhancement Bounty +description: "Propose a new feature, service, or improvement with an attached bounty." +title: "[Enhancement]: " +labels: ["✨ Enhancement", "🔍 Triage"] +body: + - type: markdown + attributes: + value: | + > [!IMPORTANT] + > **This issue template is exclusively for proposing new features, services, or improvements with an attached bounty.** Enhancements without a bounty can be discussed in the appropriate category of [Github Discussions](https://github.com/coollabsio/coolify/discussions). + + # 💎 Add a Bounty (with [algora.io](https://console.algora.io/org/coollabsio/bounties/new)) + - [Click here to add the required bounty](https://console.algora.io/org/coollabsio/bounties/new) + + - type: dropdown + attributes: + label: Request Type + description: Select the type of request you are making. + options: + - New Feature + - New Service + - Improvement + validations: + required: true + + - type: textarea + attributes: + label: Description + description: Provide a detailed description of the feature, improvement, or service you are proposing. + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 4f12f436c..92c48e2d6 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,8 +1,18 @@ blank_issues_enabled: false + contact_links: - - name: 🤔 Community Support (Chat) + - name: 🤔 Questions and Community Support url: https://coollabs.io/discord - about: Reach out to us on Discord. - - name: 🙋‍♂️ Feature Requests - url: https://github.com/coollabsio/coolify/discussions/categories/new-features - about: All feature requests will be discussed here. + about: If you have any questions, reach out to us on Discord inside the "#support" channel. + + - name: 💡 Feature Request + url: https://github.com/coollabsio/coolify/discussions/categories/feature-requests + about: Suggest a new feature for Coolify. + + - name: ⚙️ Service Request + url: https://github.com/coollabsio/coolify/discussions/categories/service-requests + about: Request a new service integration for Coolify. + + - name: 🔧 Improvements + url: https://github.com/coollabsio/coolify/discussions/categories/improvements + about: Suggest improvements to existing features for Coolify. diff --git a/.github/workflows/coolify-helper-next.yml b/.github/workflows/coolify-helper-next.yml index d9921b363..4add8516e 100644 --- a/.github/workflows/coolify-helper-next.yml +++ b/.github/workflows/coolify-helper-next.yml @@ -25,6 +25,10 @@ jobs: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Get Version + id: version + run: | + echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT - name: Build image and push to registry uses: docker/build-push-action@v5 with: @@ -33,7 +37,9 @@ jobs: file: docker/coolify-helper/Dockerfile platforms: linux/amd64 push: true - tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:next + tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next + labels: | + coolify.managed=true aarch64: runs-on: [ self-hosted, arm64 ] permissions: @@ -47,6 +53,10 @@ jobs: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Get Version + id: version + run: | + echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT - name: Build image and push to registry uses: docker/build-push-action@v5 with: @@ -55,7 +65,9 @@ jobs: file: docker/coolify-helper/Dockerfile platforms: linux/aarch64 push: true - tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:next-aarch64 + tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next-aarch64 + labels: | + coolify.managed=true merge-manifest: runs-on: ubuntu-latest permissions: @@ -75,10 +87,15 @@ jobs: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Get Version + id: version + run: | + echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT - name: Create & publish manifest run: | - docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:next-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:next + docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-next --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:next - uses: sarisia/actions-status-discord@v1 if: always() with: webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }} + diff --git a/.github/workflows/coolify-helper.yml b/.github/workflows/coolify-helper.yml index 7e8132ec6..fd4be2f11 100644 --- a/.github/workflows/coolify-helper.yml +++ b/.github/workflows/coolify-helper.yml @@ -25,6 +25,10 @@ jobs: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Get Version + id: version + run: | + echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT - name: Build image and push to registry uses: docker/build-push-action@v5 with: @@ -33,7 +37,9 @@ jobs: file: docker/coolify-helper/Dockerfile platforms: linux/amd64 push: true - tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest + tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }} + labels: | + coolify.managed=true aarch64: runs-on: [ self-hosted, arm64 ] permissions: @@ -47,6 +53,10 @@ jobs: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Get Version + id: version + run: | + echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT - name: Build image and push to registry uses: docker/build-push-action@v5 with: @@ -55,7 +65,9 @@ jobs: file: docker/coolify-helper/Dockerfile platforms: linux/aarch64 push: true - tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest-aarch64 + tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64 + labels: | + coolify.managed=true merge-manifest: runs-on: ubuntu-latest permissions: @@ -75,9 +87,13 @@ jobs: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Get Version + id: version + run: | + echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.helper.version' versions.json)"|xargs >> $GITHUB_OUTPUT - name: Create & publish manifest run: | - docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest + docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }} --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest - uses: sarisia/actions-status-discord@v1 if: always() with: diff --git a/.github/workflows/coolify-realtime.yml b/.github/workflows/coolify-realtime.yml new file mode 100644 index 000000000..75e3f1681 --- /dev/null +++ b/.github/workflows/coolify-realtime.yml @@ -0,0 +1,103 @@ +name: Coolify Realtime (v4) + +on: + push: + branches: [ "main", "next" ] + paths: + - .github/workflows/coolify-realtime.yml + - docker/coolify-realtime/Dockerfile + - docker/coolify-realtime/terminal-server.js + - docker/coolify-realtime/package.json + - docker/coolify-realtime/soketi-entrypoint.sh + +env: + REGISTRY: ghcr.io + IMAGE_NAME: "coollabsio/coolify-realtime" + +jobs: + amd64: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - uses: actions/checkout@v4 + - name: Login to ghcr.io + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Get Version + id: version + run: | + echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.realtime.version' versions.json)"|xargs >> $GITHUB_OUTPUT + - name: Build image and push to registry + uses: docker/build-push-action@v5 + with: + no-cache: true + context: . + file: docker/coolify-realtime/Dockerfile + platforms: linux/amd64 + push: true + tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }} + labels: | + coolify.managed=true + aarch64: + runs-on: [ self-hosted, arm64 ] + permissions: + contents: read + packages: write + steps: + - uses: actions/checkout@v4 + - name: Login to ghcr.io + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Get Version + id: version + run: | + echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.realtime.version' versions.json)"|xargs >> $GITHUB_OUTPUT + - name: Build image and push to registry + uses: docker/build-push-action@v5 + with: + no-cache: true + context: . + file: docker/coolify-realtime/Dockerfile + platforms: linux/aarch64 + push: true + tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64 + labels: | + coolify.managed=true + merge-manifest: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + needs: [ amd64, aarch64 ] + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Login to ghcr.io + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Get Version + id: version + run: | + echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app ghcr.io/jqlang/jq:latest '.coolify.realtime.version' versions.json)"|xargs >> $GITHUB_OUTPUT + - name: Create & publish manifest + run: | + docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }} --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest + - uses: sarisia/actions-status-discord@v1 + if: always() + with: + webhook: ${{ secrets.DISCORD_WEBHOOK_PROD_RELEASE_CHANNEL }} diff --git a/.github/workflows/pr-build.yml b/.github/workflows/pr-build.yml index cf2fae8f3..d7a680170 100644 --- a/.github/workflows/pr-build.yml +++ b/.github/workflows/pr-build.yml @@ -16,6 +16,12 @@ env: jobs: amd64: runs-on: ubuntu-latest + permissions: + contents: read + packages: write + attestations: write + id-token: write + actions: write steps: - uses: actions/checkout@v4 - name: Login to ghcr.io @@ -37,6 +43,9 @@ jobs: permissions: contents: read packages: write + attestations: write + id-token: write + actions: write steps: - uses: actions/checkout@v4 - name: Login to ghcr.io @@ -58,6 +67,9 @@ jobs: permissions: contents: read packages: write + attestations: write + id-token: write + actions: write needs: [amd64, aarch64] steps: - name: Checkout diff --git a/.github/workflows/production-build.yml b/.github/workflows/production-build.yml index e4bad6a65..c78c865bf 100644 --- a/.github/workflows/production-build.yml +++ b/.github/workflows/production-build.yml @@ -4,6 +4,8 @@ on: push: branches: ["main"] paths-ignore: + - .github/workflows/coolify-helper.yml + - docker/coolify-helper/Dockerfile - templates/service-templates.json env: diff --git a/.github/workflows/remove-labels-and-assignees-on-close.yml b/.github/workflows/remove-labels-and-assignees-on-close.yml new file mode 100644 index 000000000..04d62623c --- /dev/null +++ b/.github/workflows/remove-labels-and-assignees-on-close.yml @@ -0,0 +1,75 @@ +name: Remove Labels and Assignees on Issue Close + +on: + issues: + types: [closed] + pull_request: + types: [closed] + pull_request_target: + types: [closed] + +jobs: + remove-labels-and-assignees: + runs-on: ubuntu-latest + steps: + - name: Remove labels and assignees + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { owner, repo } = context.repo; + + async function processIssue(issueNumber) { + try { + const { data: currentLabels } = await github.rest.issues.listLabelsOnIssue({ + owner, + repo, + issue_number: issueNumber + }); + + const labelsToKeep = currentLabels + .filter(label => label.name === '⏱︎ Stale') + .map(label => label.name); + + await github.rest.issues.setLabels({ + owner, + repo, + issue_number: issueNumber, + labels: labelsToKeep + }); + + const { data: issue } = await github.rest.issues.get({ + owner, + repo, + issue_number: issueNumber + }); + + if (issue.assignees && issue.assignees.length > 0) { + await github.rest.issues.removeAssignees({ + owner, + repo, + issue_number: issueNumber, + assignees: issue.assignees.map(assignee => assignee.login) + }); + } + } catch (error) { + if (error.status !== 404) { + console.error(`Error processing issue ${issueNumber}:`, error); + } + } + } + + if (context.eventName === 'issues' || context.eventName === 'pull_request' || context.eventName === 'pull_request_target') { + const issue = context.payload.issue || context.payload.pull_request; + await processIssue(issue.number); + } + + if (context.eventName === 'pull_request' || context.eventName === 'pull_request_target') { + const { data: closedIssues } = await github.rest.search.issuesAndPullRequests({ + q: `repo:${owner}/${repo} is:issue is:closed linked:${context.payload.pull_request.number}`, + per_page: 100 + }); + for (const issue of closedIssues.items) { + await processIssue(issue.number); + } + } diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..590360ddb --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,207 @@ +# Contributing to Coolify + +> "First, thanks for considering contributing to my project. It really means a lot!" - [@andrasbacsai](https://github.com/andrasbacsai) + +You can ask for guidance anytime on our [Discord server](https://coollabs.io/discord) in the `#contribute` channel. + +## Table of Contents + +1. [Setup Development Environment](#1-setup-development-environment) +2. [Verify Installation](#2-verify-installation-optional) +3. [Fork and Setup Local Repository](#3-fork-and-setup-local-repository) +4. [Set up Environment Variables](#4-set-up-environment-variables) +5. [Start Coolify](#5-start-coolify) +6. [Start Development](#6-start-development) +7. [Development Notes](#7-development-notes) +8. [Create a Pull Request](#8-create-a-pull-request) +9. [Additional Contribution Guidelines](#additional-contribution-guidelines) + +## 1. Setup Development Environment + +Follow the steps below for your operating system: + +
+Windows + +1. Install `docker-ce`, Docker Desktop (or similar): + - Docker CE (recommended): + - Install Windows Subsystem for Linux v2 (WSL2) by following this guide: [Install WSL](https://learn.microsoft.com/en-us/windows/wsl/install) + - After installing WSL2, install Docker CE for your Linux distribution by following this guide: [Install Docker Engine](https://docs.docker.com/engine/install/) + - Make sure to choose the appropriate Linux distribution (e.g., Ubuntu) when following the Docker installation guide + - Install Docker Desktop (easier): + - Download and install [Docker Desktop for Windows](https://docs.docker.com/desktop/install/windows-install/) + - Ensure WSL2 backend is enabled in Docker Desktop settings + +2. Install Spin: + - Follow the instructions to install Spin on Windows from the [Spin documentation](https://serversideup.net/open-source/spin/docs/installation/install-windows#download-and-install-spin-into-wsl2) + +
+ +
+MacOS + +1. Install Orbstack, Docker Desktop (or similar): + - Orbstack (recommended, as it is a faster and lighter alternative to Docker Desktop): + - Download and install [Orbstack](https://docs.orbstack.dev/quick-start#installation) + - Docker Desktop: + - Download and install [Docker Desktop for Mac](https://docs.docker.com/desktop/install/mac-install/) + +2. Install Spin: + - Follow the instructions to install Spin on MacOS from the [Spin documentation](https://serversideup.net/open-source/spin/docs/installation/install-macos/#download-and-install-spin) + +
+ +
+Linux + +1. Install Docker Engine, Docker Desktop (or similar): + - Docker Engine (recommended, as there is no VM overhead): + - Follow the official [Docker Engine installation guide](https://docs.docker.com/engine/install/) for your Linux distribution + - Docker Desktop: + - If you want a GUI, you can use [Docker Desktop for Linux](https://docs.docker.com/desktop/install/linux-install/) + +2. Install Spin: + - Follow the instructions to install Spin on Linux from the [Spin documentation](https://serversideup.net/open-source/spin/docs/installation/install-linux#configure-docker-permissions) + +
+ +## 2. Verify Installation (Optional) + +After installing Docker (or Orbstack) and Spin, verify the installation: + +1. Open a terminal or command prompt +2. Run the following commands: + ```bash + docker --version + spin --version + ``` + You should see version information for both Docker and Spin. + +## 3. Fork and Setup Local Repository + +1. Fork the [Coolify](https://github.com/coollabsio/coolify) repository to your GitHub account. + +2. Install a code editor on your machine (choose one): + + | Editor | Platform | Download Link | + |--------|----------|---------------| + | Visual Studio Code (recommended free) | Windows/macOS/Linux | [Download](https://code.visualstudio.com/download) | + | Cursor (recommended but paid) | Windows/macOS/Linux | [Download](https://www.cursor.com/) | + | Zed (very fast) | macOS/Linux | [Download](https://zed.dev/download) | + +3. Clone the Coolify Repository from your fork to your local machine + - Use `git clone` in the command line, or + - Use GitHub Desktop (recommended): + - Download and install from [https://desktop.github.com/](https://desktop.github.com/) + - Open GitHub Desktop and login with your GitHub account + - Click on `File` -> `Clone Repository` select `github.com` as the repository location, then select your forked Coolify repository, choose the local path and then click `Clone` + +4. Open the cloned Coolify Repository in your chosen code editor. + +## 4. Set up Environment Variables + +1. In the Code Editor, locate the `.env.development.example` file in the root directory of your local Coolify repository. +2. Duplicate the `.env.development.example` file and rename the copy to `.env`. +3. Open the new `.env` file and review its contents. Adjust any environment variables as needed for your development setup. +4. If you encounter errors during database migrations, update the database connection settings in your `.env` file. Use the IP address or hostname of your PostgreSQL database container. You can find this information by running `docker ps` after executing `spin up`. +5. Save the changes to your `.env` file. + +## 5. Start Coolify + +1. Open a terminal in the local Coolify directory. +2. Run the following command in the terminal (leave that terminal open): + ```bash + spin up + ``` + +> [!NOTE] +> You may see some errors, but don't worry; this is expected. + +3. If you encounter permission errors, especially on macOS, use: + ```bash + sudo spin up + ``` + +> [!NOTE] +> If you change environment variables afterwards or anything seems broken, press Ctrl + C to stop the process and run `spin up` again. + +## 6. Start Development + +1. Access your Coolify instance: + - URL: `http://localhost:8000` + - Login: `test@example.com` + - Password: `password` + +2. Additional development tools: + | Tool | URL | Note | + |------|-----|------| + | Laravel Horizon (scheduler) | `http://localhost:8000/horizon` | Only accessible when logged in as root user | + | Mailpit (email catcher) | `http://localhost:8025` | | + | Telescope (debugging tool) | `http://localhost:8000/telescope` | Disabled by default | + +> [!NOTE] +> To enable Telescope, add the following to your `.env` file: +> ```env +> TELESCOPE_ENABLED=true +> ``` + +## 7. Development Notes + +When working on Coolify, keep the following in mind: + +1. **Database Migrations**: After switching branches or making changes to the database structure, always run migrations: + ```bash + docker exec -it coolify php artisan migrate + ``` + +2. **Resetting Development Setup**: To reset your development setup to a clean database with default values: + ```bash + docker exec -it coolify php artisan migrate:fresh --seed + ``` + +3. **Troubleshooting**: If you encounter unexpected behavior, ensure your database is up-to-date with the latest migrations and if possible reset the development setup to eliminate any environment-specific issues. + +> [!IMPORTANT] +> Forgetting to migrate the database can cause problems, so make it a habit to run migrations after pulling changes or switching branches. + +## 8. Create a Pull Request + +1. After making changes or adding a new service: + - Commit your changes to your forked repository. + - Push the changes to your GitHub account. + +2. Creating the Pull Request (PR): + - Navigate to the main Coolify repository on GitHub. + - Click the "Pull requests" tab. + - Click the green "New pull request" button. + - Choose your fork and branch as the compare branch. + - Click "Create pull request". + +3. Filling out the PR details: + - Give your PR a descriptive title. + - In the description, explain the changes you've made. + - Reference any related issues by using keywords like "Fixes #123" or "Closes #456". + +> [!IMPORTANT] +> Always set the base branch for your PR to the `next` branch of the Coolify repository, not the `main` branch. + +4. Submit your PR: + - Review your changes one last time. + - Click "Create pull request" to submit. + +> [!NOTE] +> Make sure your PR is out of draft mode as soon as it's ready for review. PRs that are in draft mode for a long time may be closed by maintainers. + +After submission, maintainers will review your PR and may request changes or provide feedback. + +## Additional Contribution Guidelines + +### Contributing a New Service + +To add a new service to Coolify, please refer to our documentation: +[Adding a New Service](https://coolify.io/docs/knowledge-base/contribute/service) + +### Contributing to Documentation + +To contribute to the Coolify documentation, please refer to this guide: +[Contributing to the Coolify Documentation](https://github.com/coollabsio/documentation-coolify/blob/main/CONTRIBUTING.md) diff --git a/CONTRIBUTION.md b/CONTRIBUTION.md deleted file mode 100644 index 02a21573c..000000000 --- a/CONTRIBUTION.md +++ /dev/null @@ -1,34 +0,0 @@ -# Contributing - -> "First, thanks for considering to contribute to my project. - It really means a lot!" - [@andrasbacsai](https://github.com/andrasbacsai) - -You can ask for guidance anytime on our -[Discord server](https://coollabs.io/discord) in the `#contribution` channel. - -## Code Contribution - -### 1) Setup your development environment - -- You need to have Docker Engine (or equivalent) [installed](https://docs.docker.com/engine/install/) on your system. -- For better DX, install [Spin](https://serversideup.net/open-source/spin/). - -### 2) Set your environment variables - -- Copy [.env.development.example](./.env.development.example) to .env. - -## 3) Start & setup Coolify - -- Run `spin up` - You can notice that errors will be thrown. Don't worry. - - If you see weird permission errors, especially on Mac, run `sudo spin up` instead. - -### 4) Start development -You can login your Coolify instance at `localhost:8000` with `test@example.com` and `password`. - -Your horizon (Laravel scheduler): `localhost:8000/horizon` - Only reachable if you logged in with root user. - -Mails are caught by Mailpit: `localhost:8025` - -## New Service Contribution -Check out the docs [here](https://coolify.io/docs/knowledge-base/add-a-service). - diff --git a/README.md b/README.md index ce6289d48..14a741088 100644 --- a/README.md +++ b/README.md @@ -35,20 +35,32 @@ Thank you so much! Special thanks to our biggest sponsors! -cccareers logo -hetzner logo -logto logo -bc direct logo -quantcdn logo -arcjet logo -supaguide logo -tigris logo -fractal logo -advin logo -trieve logo -blacksmith logo -latitude logo -branddev logo +### Special Sponsors + +![image](https://github.com/user-attachments/assets/c95a07df-7c5a-4e77-a35a-81f25fcbece1) + +* [CCCareers](https://cccareers.org/) - A career development platform connecting coding bootcamp graduates with job opportunities in the tech industry. +* [Hetzner](http://htznr.li/CoolifyXHetzner) - A German web hosting company offering affordable dedicated servers, cloud services, and web hosting solutions. +* [Logto](https://logto.io/?ref=coolify) - An open-source authentication and authorization solution for building secure login systems and managing user identities. +* [BC Direct](https://bc.direct/?ref=coolify.io) - A digital marketing agency specializing in e-commerce solutions and online business growth strategies. +* [QuantCDN](https://www.quantcdn.io/?ref=coolify.io) - A content delivery network (CDN) optimizing website performance through global content distribution. +* [Arcjet](https://arcjet.com/?ref=coolify.io) - A cloud-based platform providing real-time protection against API abuse and bot attacks. +* [SupaGuide](https://supa.guide/?ref=coolify.io) - A comprehensive resource hub offering guides and tutorials for web development using Supabase. +* [Tigris](https://tigrisdata.com/?ref=coolify.io) - A fully managed serverless object storage service compatible with Amazon S3 API. Offers high performance, scalability, and built-in search capabilities for efficient data management. +* [Fractal Networks](https://fractalnetworks.co/?ref=coolify.io) - A decentralized network infrastructure company focusing on secure and private communication solutions. +* [Advin](https://coolify.ad.vin/?ref=coolify.io) - A digital advertising agency specializing in programmatic advertising and data-driven marketing strategies. +* [Treive](https://trieve.ai/?ref=coolify.io) - An AI-powered search and discovery platform for enhancing information retrieval in large datasets. +* [Blacksmith](https://blacksmith.sh/?ref=coolify.io) - A cloud-native platform for automating infrastructure provisioning and management across multiple cloud providers. +* [Latitude](https://latitude.sh/?ref=coolify.io) - A cloud computing platform offering bare metal servers and cloud instances for developers and businesses. +* [Brand Dev](https://brand.dev/?ref=coolify.io) - A web development agency specializing in creating custom digital experiences and brand identities. +* [Jobscollider](https://jobscollider.com/remote-jobs?ref=coolify.io) - A job search platform connecting professionals with remote work opportunities across various industries. +* [Hostinger](https://www.hostinger.com/vps/coolify-hosting?ref=coolify.io) - A web hosting provider offering affordable hosting solutions, domain registration, and website building tools. +* [Glueops](https://www.glueops.dev/?ref=coolify.io) - A DevOps consulting company providing infrastructure automation and cloud optimization services. +* [Ubicloud](https://ubicloud.com/?ref=coolify.io) - An open-source alternative to hyperscale cloud providers, offering high-performance cloud computing services. +* [Juxtdigital](https://juxtdigital.dev/?ref=coolify.io) - A digital agency offering web development, design, and digital marketing services for businesses. +* [Saasykit](https://saasykit.com/?ref=coolify.io) - A Laravel-based boilerplate providing essential components and features for building SaaS applications quickly. +* [Massivegrid](https://massivegrid.com/?ref=coolify.io) - A cloud hosting provider offering scalable infrastructure solutions for businesses of all sizes. + ## Github Sponsors ($40+) SerpAPI @@ -71,8 +83,11 @@ Special thanks to our biggest sponsors! NiftyCo Imre Ujlaki Ilias Ism +Breakcold Paweł Pierścionek Michael Mazurczak +Formbricks +Adith Suhas ## Organizations diff --git a/RELEASE.md b/RELEASE.md new file mode 100644 index 000000000..2cb96b72b --- /dev/null +++ b/RELEASE.md @@ -0,0 +1,45 @@ +# Coolify Release Guide + +This guide outlines the release process for Coolify, intended for developers and those interested in understanding how releases are managed and deployed. + +## Release Process + +1. **Development on `next` or separate branches** + - Changes, fixes and new features are developed on the `next` or even separate branches. + +2. **Merging to `main`** + - Once changes are ready, they are merged from `next` into the `main` branch. + +3. **Building the release** + - After merging to `main`, a new release is built. + - Note: A push to `main` does not automatically mean a new version is released. + +4. **Creating a GitHub release** + - A new release is created on GitHub with the new version details. + +5. **Updating the CDN** + - The final step is updating the version information on the CDN: + [https://cdn.coollabs.io/coolify/versions.json](https://cdn.coollabs.io/coolify/versions.json) + +> [!NOTE] +> The CDN update may not occur immediately after the GitHub release. It can happen hours or even days later due to additional testing, stability checks, or potential hotfixes. + + +## Version Availability + +It's important to understand that a new version released on GitHub may not immediately become available for users to update (through manual or auto-update). + +> [!IMPORTANT] +> If you see a new release on GitHub but haven't received the update, it's likely because the CDN hasn't been updated yet. This is intentional and ensures stability and allows for hotfixes before the new version is officially released. + +## Manually Update to Specific Versions + +> [!CAUTION] +> Updating to unreleased versions is not recommended and may cause issues. Use at your own risk! + +To update your Coolify instance to a specific (unreleased) version, use the following command: + +```bash +curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash -s +``` +-> Replace `` with the version you want to update to (for example `4.0.0-beta.332`). diff --git a/app/Actions/Database/StartClickhouse.php b/app/Actions/Database/StartClickhouse.php index e97c55930..6d0063749 100644 --- a/app/Actions/Database/StartClickhouse.php +++ b/app/Actions/Database/StartClickhouse.php @@ -79,14 +79,7 @@ class StartClickhouse data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset); } if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) { - $docker_compose['services'][$container_name]['logging'] = [ - 'driver' => 'fluentd', - 'options' => [ - 'fluentd-address' => 'tcp://127.0.0.1:24224', - 'fluentd-async' => 'true', - 'fluentd-sub-second-precision' => 'true', - ], - ]; + $docker_compose['services'][$container_name]['logging'] = generate_fluentd_configuration(); } if (count($this->database->ports_mappings_array) > 0) { $docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array; @@ -102,6 +95,11 @@ class StartClickhouse if (count($volume_names) > 0) { $docker_compose['volumes'] = $volume_names; } + + // Add custom docker run options + $docker_run_options = convert_docker_run_to_compose($this->database->custom_docker_run_options); + $docker_compose = generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network); + $docker_compose = Yaml::dump($docker_compose, 10); $docker_compose_base64 = base64_encode($docker_compose); $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null"; @@ -162,6 +160,8 @@ class StartClickhouse $environment_variables->push("CLICKHOUSE_ADMIN_PASSWORD={$this->database->clickhouse_admin_password}"); } + add_coolify_default_environment_variables($this->database, $environment_variables, $environment_variables); + return $environment_variables->all(); } } diff --git a/app/Actions/Database/StartDragonfly.php b/app/Actions/Database/StartDragonfly.php index 862fc54fc..621834df0 100644 --- a/app/Actions/Database/StartDragonfly.php +++ b/app/Actions/Database/StartDragonfly.php @@ -23,7 +23,7 @@ class StartDragonfly $startCommand = "dragonfly --requirepass {$this->database->dragonfly_password}"; $container_name = $this->database->uuid; - $this->configuration_dir = database_configuration_dir().'/'.$container_name; + $this->configuration_dir = database_configuration_dir() . '/' . $container_name; $this->commands = [ "echo 'Starting {$database->name}.'", @@ -75,18 +75,11 @@ class StartDragonfly ], ], ]; - if (! is_null($this->database->limits_cpuset)) { + if (!is_null($this->database->limits_cpuset)) { data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset); } if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) { - $docker_compose['services'][$container_name]['logging'] = [ - 'driver' => 'fluentd', - 'options' => [ - 'fluentd-address' => 'tcp://127.0.0.1:24224', - 'fluentd-async' => 'true', - 'fluentd-sub-second-precision' => 'true', - ], - ]; + $docker_compose['services'][$container_name]['logging'] = generate_fluentd_configuration(); } if (count($this->database->ports_mappings_array) > 0) { $docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array; @@ -102,6 +95,11 @@ class StartDragonfly if (count($volume_names) > 0) { $docker_compose['volumes'] = $volume_names; } + + // Add custom docker run options + $docker_run_options = convert_docker_run_to_compose($this->database->custom_docker_run_options); + $docker_compose = generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network); + $docker_compose = Yaml::dump($docker_compose, 10); $docker_compose_base64 = base64_encode($docker_compose); $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null"; @@ -120,10 +118,10 @@ class StartDragonfly $local_persistent_volumes = []; foreach ($this->database->persistentStorages as $persistentStorage) { if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) { - $local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path; + $local_persistent_volumes[] = $persistentStorage->host_path . ':' . $persistentStorage->mount_path; } else { $volume_name = $persistentStorage->name; - $local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path; + $local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path; } } @@ -154,7 +152,7 @@ class StartDragonfly $environment_variables->push("$env->key=$env->real_value"); } - if ($environment_variables->filter(fn ($env) => str($env)->contains('REDIS_PASSWORD'))->isEmpty()) { + if ($environment_variables->filter(fn($env) => str($env)->contains('REDIS_PASSWORD'))->isEmpty()) { $environment_variables->push("REDIS_PASSWORD={$this->database->dragonfly_password}"); } diff --git a/app/Actions/Database/StartKeydb.php b/app/Actions/Database/StartKeydb.php index 85cb89c1c..9290efc7c 100644 --- a/app/Actions/Database/StartKeydb.php +++ b/app/Actions/Database/StartKeydb.php @@ -24,7 +24,7 @@ class StartKeydb $startCommand = "keydb-server --requirepass {$this->database->keydb_password} --appendonly yes"; $container_name = $this->database->uuid; - $this->configuration_dir = database_configuration_dir().'/'.$container_name; + $this->configuration_dir = database_configuration_dir() . '/' . $container_name; $this->commands = [ "echo 'Starting {$database->name}.'", @@ -74,18 +74,11 @@ class StartKeydb ], ], ]; - if (! is_null($this->database->limits_cpuset)) { + if (!is_null($this->database->limits_cpuset)) { data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset); } if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) { - $docker_compose['services'][$container_name]['logging'] = [ - 'driver' => 'fluentd', - 'options' => [ - 'fluentd-address' => 'tcp://127.0.0.1:24224', - 'fluentd-async' => 'true', - 'fluentd-sub-second-precision' => 'true', - ], - ]; + $docker_compose['services'][$container_name]['logging'] = generate_fluentd_configuration(); } if (count($this->database->ports_mappings_array) > 0) { $docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array; @@ -101,15 +94,19 @@ class StartKeydb if (count($volume_names) > 0) { $docker_compose['volumes'] = $volume_names; } - if (! is_null($this->database->keydb_conf) || ! empty($this->database->keydb_conf)) { + if (!is_null($this->database->keydb_conf) || !empty($this->database->keydb_conf)) { $docker_compose['services'][$container_name]['volumes'][] = [ 'type' => 'bind', - 'source' => $this->configuration_dir.'/keydb.conf', + 'source' => $this->configuration_dir . '/keydb.conf', 'target' => '/etc/keydb/keydb.conf', 'read_only' => true, ]; $docker_compose['services'][$container_name]['command'] = "keydb-server /etc/keydb/keydb.conf --requirepass {$this->database->keydb_password} --appendonly yes"; } + + // Add custom docker run options + $docker_run_options = convert_docker_run_to_compose($this->database->custom_docker_run_options); + $docker_compose = generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network); $docker_compose = Yaml::dump($docker_compose, 10); $docker_compose_base64 = base64_encode($docker_compose); $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null"; @@ -128,10 +125,10 @@ class StartKeydb $local_persistent_volumes = []; foreach ($this->database->persistentStorages as $persistentStorage) { if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) { - $local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path; + $local_persistent_volumes[] = $persistentStorage->host_path . ':' . $persistentStorage->mount_path; } else { $volume_name = $persistentStorage->name; - $local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path; + $local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path; } } @@ -162,10 +159,12 @@ class StartKeydb $environment_variables->push("$env->key=$env->real_value"); } - if ($environment_variables->filter(fn ($env) => str($env)->contains('REDIS_PASSWORD'))->isEmpty()) { + if ($environment_variables->filter(fn($env) => str($env)->contains('REDIS_PASSWORD'))->isEmpty()) { $environment_variables->push("REDIS_PASSWORD={$this->database->keydb_password}"); } + add_coolify_default_environment_variables($this->database, $environment_variables, $environment_variables); + return $environment_variables->all(); } diff --git a/app/Actions/Database/StartMariadb.php b/app/Actions/Database/StartMariadb.php index 33948192b..f37a5e361 100644 --- a/app/Actions/Database/StartMariadb.php +++ b/app/Actions/Database/StartMariadb.php @@ -21,7 +21,7 @@ class StartMariadb $this->database = $database; $container_name = $this->database->uuid; - $this->configuration_dir = database_configuration_dir().'/'.$container_name; + $this->configuration_dir = database_configuration_dir() . '/' . $container_name; $this->commands = [ "echo 'Starting {$database->name}.'", @@ -69,18 +69,11 @@ class StartMariadb ], ], ]; - if (! is_null($this->database->limits_cpuset)) { + if (!is_null($this->database->limits_cpuset)) { data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset); } if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) { - $docker_compose['services'][$container_name]['logging'] = [ - 'driver' => 'fluentd', - 'options' => [ - 'fluentd-address' => 'tcp://127.0.0.1:24224', - 'fluentd-async' => 'true', - 'fluentd-sub-second-precision' => 'true', - ], - ]; + $docker_compose['services'][$container_name]['logging'] = generate_fluentd_configuration(); } if (count($this->database->ports_mappings_array) > 0) { $docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array; @@ -96,14 +89,19 @@ class StartMariadb if (count($volume_names) > 0) { $docker_compose['volumes'] = $volume_names; } - if (! is_null($this->database->mariadb_conf) || ! empty($this->database->mariadb_conf)) { + if (!is_null($this->database->mariadb_conf) || !empty($this->database->mariadb_conf)) { $docker_compose['services'][$container_name]['volumes'][] = [ 'type' => 'bind', - 'source' => $this->configuration_dir.'/custom-config.cnf', + 'source' => $this->configuration_dir . '/custom-config.cnf', 'target' => '/etc/mysql/conf.d/custom-config.cnf', 'read_only' => true, ]; } + + // Add custom docker run options + $docker_run_options = convert_docker_run_to_compose($this->database->custom_docker_run_options); + $docker_compose = generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network); + $docker_compose = Yaml::dump($docker_compose, 10); $docker_compose_base64 = base64_encode($docker_compose); $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null"; @@ -122,10 +120,10 @@ class StartMariadb $local_persistent_volumes = []; foreach ($this->database->persistentStorages as $persistentStorage) { if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) { - $local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path; + $local_persistent_volumes[] = $persistentStorage->host_path . ':' . $persistentStorage->mount_path; } else { $volume_name = $persistentStorage->name; - $local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path; + $local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path; } } @@ -156,21 +154,23 @@ class StartMariadb $environment_variables->push("$env->key=$env->real_value"); } - if ($environment_variables->filter(fn ($env) => str($env)->contains('MARIADB_ROOT_PASSWORD'))->isEmpty()) { + if ($environment_variables->filter(fn($env) => str($env)->contains('MARIADB_ROOT_PASSWORD'))->isEmpty()) { $environment_variables->push("MARIADB_ROOT_PASSWORD={$this->database->mariadb_root_password}"); } - if ($environment_variables->filter(fn ($env) => str($env)->contains('MARIADB_DATABASE'))->isEmpty()) { + if ($environment_variables->filter(fn($env) => str($env)->contains('MARIADB_DATABASE'))->isEmpty()) { $environment_variables->push("MARIADB_DATABASE={$this->database->mariadb_database}"); } - if ($environment_variables->filter(fn ($env) => str($env)->contains('MARIADB_USER'))->isEmpty()) { + if ($environment_variables->filter(fn($env) => str($env)->contains('MARIADB_USER'))->isEmpty()) { $environment_variables->push("MARIADB_USER={$this->database->mariadb_user}"); } - if ($environment_variables->filter(fn ($env) => str($env)->contains('MARIADB_PASSWORD'))->isEmpty()) { + if ($environment_variables->filter(fn($env) => str($env)->contains('MARIADB_PASSWORD'))->isEmpty()) { $environment_variables->push("MARIADB_PASSWORD={$this->database->mariadb_password}"); } + add_coolify_default_environment_variables($this->database, $environment_variables, $environment_variables); + return $environment_variables->all(); } diff --git a/app/Actions/Database/StartMongodb.php b/app/Actions/Database/StartMongodb.php index 911054f5e..42fc8f348 100644 --- a/app/Actions/Database/StartMongodb.php +++ b/app/Actions/Database/StartMongodb.php @@ -23,7 +23,7 @@ class StartMongodb $startCommand = 'mongod'; $container_name = $this->database->uuid; - $this->configuration_dir = database_configuration_dir().'/'.$container_name; + $this->configuration_dir = database_configuration_dir() . '/' . $container_name; $this->commands = [ "echo 'Starting {$database->name}.'", @@ -77,18 +77,11 @@ class StartMongodb ], ], ]; - if (! is_null($this->database->limits_cpuset)) { + if (!is_null($this->database->limits_cpuset)) { data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset); } if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) { - $docker_compose['services'][$container_name]['logging'] = [ - 'driver' => 'fluentd', - 'options' => [ - 'fluentd-address' => 'tcp://127.0.0.1:24224', - 'fluentd-async' => 'true', - 'fluentd-sub-second-precision' => 'true', - ], - ]; + $docker_compose['services'][$container_name]['logging'] = generate_fluentd_configuration(); } if (count($this->database->ports_mappings_array) > 0) { $docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array; @@ -104,23 +97,27 @@ class StartMongodb if (count($volume_names) > 0) { $docker_compose['volumes'] = $volume_names; } - if (! is_null($this->database->mongo_conf) || ! empty($this->database->mongo_conf)) { + if (!is_null($this->database->mongo_conf) || !empty($this->database->mongo_conf)) { $docker_compose['services'][$container_name]['volumes'][] = [ 'type' => 'bind', - 'source' => $this->configuration_dir.'/mongod.conf', + 'source' => $this->configuration_dir . '/mongod.conf', 'target' => '/etc/mongo/mongod.conf', 'read_only' => true, ]; - $docker_compose['services'][$container_name]['command'] = $startCommand.' --config /etc/mongo/mongod.conf'; + $docker_compose['services'][$container_name]['command'] = $startCommand . ' --config /etc/mongo/mongod.conf'; } $this->add_default_database(); $docker_compose['services'][$container_name]['volumes'][] = [ 'type' => 'bind', - 'source' => $this->configuration_dir.'/docker-entrypoint-initdb.d', + 'source' => $this->configuration_dir . '/docker-entrypoint-initdb.d', 'target' => '/docker-entrypoint-initdb.d', 'read_only' => true, ]; + // Add custom docker run options + $docker_run_options = convert_docker_run_to_compose($this->database->custom_docker_run_options); + $docker_compose = generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network); + $docker_compose = Yaml::dump($docker_compose, 10); $docker_compose_base64 = base64_encode($docker_compose); $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null"; @@ -139,10 +136,10 @@ class StartMongodb $local_persistent_volumes = []; foreach ($this->database->persistentStorages as $persistentStorage) { if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) { - $local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path; + $local_persistent_volumes[] = $persistentStorage->host_path . ':' . $persistentStorage->mount_path; } else { $volume_name = $persistentStorage->name; - $local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path; + $local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path; } } @@ -173,18 +170,20 @@ class StartMongodb $environment_variables->push("$env->key=$env->real_value"); } - if ($environment_variables->filter(fn ($env) => str($env)->contains('MONGO_INITDB_ROOT_USERNAME'))->isEmpty()) { + if ($environment_variables->filter(fn($env) => str($env)->contains('MONGO_INITDB_ROOT_USERNAME'))->isEmpty()) { $environment_variables->push("MONGO_INITDB_ROOT_USERNAME={$this->database->mongo_initdb_root_username}"); } - if ($environment_variables->filter(fn ($env) => str($env)->contains('MONGO_INITDB_ROOT_PASSWORD'))->isEmpty()) { + if ($environment_variables->filter(fn($env) => str($env)->contains('MONGO_INITDB_ROOT_PASSWORD'))->isEmpty()) { $environment_variables->push("MONGO_INITDB_ROOT_PASSWORD={$this->database->mongo_initdb_root_password}"); } - if ($environment_variables->filter(fn ($env) => str($env)->contains('MONGO_INITDB_DATABASE'))->isEmpty()) { + if ($environment_variables->filter(fn($env) => str($env)->contains('MONGO_INITDB_DATABASE'))->isEmpty()) { $environment_variables->push("MONGO_INITDB_DATABASE={$this->database->mongo_initdb_database}"); } + add_coolify_default_environment_variables($this->database, $environment_variables, $environment_variables); + return $environment_variables->all(); } diff --git a/app/Actions/Database/StartMysql.php b/app/Actions/Database/StartMysql.php index b55d9dead..2043342fe 100644 --- a/app/Actions/Database/StartMysql.php +++ b/app/Actions/Database/StartMysql.php @@ -21,7 +21,7 @@ class StartMysql $this->database = $database; $container_name = $this->database->uuid; - $this->configuration_dir = database_configuration_dir().'/'.$container_name; + $this->configuration_dir = database_configuration_dir() . '/' . $container_name; $this->commands = [ "echo 'Starting {$database->name}.'", @@ -69,18 +69,11 @@ class StartMysql ], ], ]; - if (! is_null($this->database->limits_cpuset)) { + if (!is_null($this->database->limits_cpuset)) { data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset); } if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) { - $docker_compose['services'][$container_name]['logging'] = [ - 'driver' => 'fluentd', - 'options' => [ - 'fluentd-address' => 'tcp://127.0.0.1:24224', - 'fluentd-async' => 'true', - 'fluentd-sub-second-precision' => 'true', - ], - ]; + $docker_compose['services'][$container_name]['logging'] = generate_fluentd_configuration(); } if (count($this->database->ports_mappings_array) > 0) { $docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array; @@ -96,14 +89,19 @@ class StartMysql if (count($volume_names) > 0) { $docker_compose['volumes'] = $volume_names; } - if (! is_null($this->database->mysql_conf) || ! empty($this->database->mysql_conf)) { + if (!is_null($this->database->mysql_conf) || !empty($this->database->mysql_conf)) { $docker_compose['services'][$container_name]['volumes'][] = [ 'type' => 'bind', - 'source' => $this->configuration_dir.'/custom-config.cnf', + 'source' => $this->configuration_dir . '/custom-config.cnf', 'target' => '/etc/mysql/conf.d/custom-config.cnf', 'read_only' => true, ]; } + + // Add custom docker run options + $docker_run_options = convert_docker_run_to_compose($this->database->custom_docker_run_options); + $docker_compose = generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network); + $docker_compose = Yaml::dump($docker_compose, 10); $docker_compose_base64 = base64_encode($docker_compose); $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null"; @@ -122,10 +120,10 @@ class StartMysql $local_persistent_volumes = []; foreach ($this->database->persistentStorages as $persistentStorage) { if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) { - $local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path; + $local_persistent_volumes[] = $persistentStorage->host_path . ':' . $persistentStorage->mount_path; } else { $volume_name = $persistentStorage->name; - $local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path; + $local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path; } } @@ -156,21 +154,23 @@ class StartMysql $environment_variables->push("$env->key=$env->real_value"); } - if ($environment_variables->filter(fn ($env) => str($env)->contains('MYSQL_ROOT_PASSWORD'))->isEmpty()) { + if ($environment_variables->filter(fn($env) => str($env)->contains('MYSQL_ROOT_PASSWORD'))->isEmpty()) { $environment_variables->push("MYSQL_ROOT_PASSWORD={$this->database->mysql_root_password}"); } - if ($environment_variables->filter(fn ($env) => str($env)->contains('MYSQL_DATABASE'))->isEmpty()) { + if ($environment_variables->filter(fn($env) => str($env)->contains('MYSQL_DATABASE'))->isEmpty()) { $environment_variables->push("MYSQL_DATABASE={$this->database->mysql_database}"); } - if ($environment_variables->filter(fn ($env) => str($env)->contains('MYSQL_USER'))->isEmpty()) { + if ($environment_variables->filter(fn($env) => str($env)->contains('MYSQL_USER'))->isEmpty()) { $environment_variables->push("MYSQL_USER={$this->database->mysql_user}"); } - if ($environment_variables->filter(fn ($env) => str($env)->contains('MYSQL_PASSWORD'))->isEmpty()) { + if ($environment_variables->filter(fn($env) => str($env)->contains('MYSQL_PASSWORD'))->isEmpty()) { $environment_variables->push("MYSQL_PASSWORD={$this->database->mysql_password}"); } + add_coolify_default_environment_variables($this->database, $environment_variables, $environment_variables); + return $environment_variables->all(); } diff --git a/app/Actions/Database/StartPostgresql.php b/app/Actions/Database/StartPostgresql.php index 909f4c893..bc37fd5cf 100644 --- a/app/Actions/Database/StartPostgresql.php +++ b/app/Actions/Database/StartPostgresql.php @@ -37,6 +37,7 @@ class StartPostgresql $this->generate_init_scripts(); $this->add_custom_conf(); + $docker_compose = [ 'services' => [ $container_name => [ @@ -80,14 +81,7 @@ class StartPostgresql data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset); } if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) { - $docker_compose['services'][$container_name]['logging'] = [ - 'driver' => 'fluentd', - 'options' => [ - 'fluentd-address' => 'tcp://127.0.0.1:24224', - 'fluentd-async' => 'true', - 'fluentd-sub-second-precision' => 'true', - ], - ]; + $docker_compose['services'][$container_name]['logging'] = generate_fluentd_configuration(); } if (count($this->database->ports_mappings_array) > 0) { $docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array; @@ -126,6 +120,10 @@ class StartPostgresql 'config_file=/etc/postgresql/postgresql.conf', ]; } + // Add custom docker run options + $docker_run_options = convert_docker_run_to_compose($this->database->custom_docker_run_options); + $docker_compose = generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network); + $docker_compose = Yaml::dump($docker_compose, 10); $docker_compose_base64 = base64_encode($docker_compose); $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null"; @@ -193,6 +191,8 @@ class StartPostgresql $environment_variables->push("POSTGRES_DB={$this->database->postgres_db}"); } + add_coolify_default_environment_variables($this->database, $environment_variables, $environment_variables); + return $environment_variables->all(); } diff --git a/app/Actions/Database/StartRedis.php b/app/Actions/Database/StartRedis.php index f10afef5e..b837414d6 100644 --- a/app/Actions/Database/StartRedis.php +++ b/app/Actions/Database/StartRedis.php @@ -24,7 +24,7 @@ class StartRedis $startCommand = "redis-server --requirepass {$this->database->redis_password} --appendonly yes"; $container_name = $this->database->uuid; - $this->configuration_dir = database_configuration_dir().'/'.$container_name; + $this->configuration_dir = database_configuration_dir() . '/' . $container_name; $this->commands = [ "echo 'Starting {$database->name}.'", @@ -78,18 +78,11 @@ class StartRedis ], ], ]; - if (! is_null($this->database->limits_cpuset)) { + if (!is_null($this->database->limits_cpuset)) { data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset); } if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) { - $docker_compose['services'][$container_name]['logging'] = [ - 'driver' => 'fluentd', - 'options' => [ - 'fluentd-address' => 'tcp://127.0.0.1:24224', - 'fluentd-async' => 'true', - 'fluentd-sub-second-precision' => 'true', - ], - ]; + $docker_compose['services'][$container_name]['logging'] = generate_fluentd_configuration(); } if (count($this->database->ports_mappings_array) > 0) { $docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array; @@ -105,15 +98,20 @@ class StartRedis if (count($volume_names) > 0) { $docker_compose['volumes'] = $volume_names; } - if (! is_null($this->database->redis_conf) || ! empty($this->database->redis_conf)) { + if (!is_null($this->database->redis_conf) || !empty($this->database->redis_conf)) { $docker_compose['services'][$container_name]['volumes'][] = [ 'type' => 'bind', - 'source' => $this->configuration_dir.'/redis.conf', + 'source' => $this->configuration_dir . '/redis.conf', 'target' => '/usr/local/etc/redis/redis.conf', 'read_only' => true, ]; $docker_compose['services'][$container_name]['command'] = "redis-server /usr/local/etc/redis/redis.conf --requirepass {$this->database->redis_password} --appendonly yes"; } + + // Add custom docker run options + $docker_run_options = convert_docker_run_to_compose($this->database->custom_docker_run_options); + $docker_compose = generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network); + $docker_compose = Yaml::dump($docker_compose, 10); $docker_compose_base64 = base64_encode($docker_compose); $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null"; @@ -132,10 +130,10 @@ class StartRedis $local_persistent_volumes = []; foreach ($this->database->persistentStorages as $persistentStorage) { if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) { - $local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path; + $local_persistent_volumes[] = $persistentStorage->host_path . ':' . $persistentStorage->mount_path; } else { $volume_name = $persistentStorage->name; - $local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path; + $local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path; } } @@ -166,10 +164,12 @@ class StartRedis $environment_variables->push("$env->key=$env->real_value"); } - if ($environment_variables->filter(fn ($env) => str($env)->contains('REDIS_PASSWORD'))->isEmpty()) { + if ($environment_variables->filter(fn($env) => str($env)->contains('REDIS_PASSWORD'))->isEmpty()) { $environment_variables->push("REDIS_PASSWORD={$this->database->redis_password}"); } + add_coolify_default_environment_variables($this->database, $environment_variables, $environment_variables); + return $environment_variables->all(); } diff --git a/app/Actions/Server/CleanupDocker.php b/app/Actions/Server/CleanupDocker.php index 0009e001d..1034c13d6 100644 --- a/app/Actions/Server/CleanupDocker.php +++ b/app/Actions/Server/CleanupDocker.php @@ -2,6 +2,7 @@ namespace App\Actions\Server; +use App\Models\InstanceSettings; use App\Models\Server; use Lorisleiva\Actions\Concerns\AsAction; @@ -9,17 +10,30 @@ class CleanupDocker { use AsAction; - public function handle(Server $server, bool $force = true) + public function handle(Server $server) { - // cleanup docker images, containers, and builder caches - if ($force) { - instant_remote_process(['docker image prune -af'], $server, false); - instant_remote_process(['docker container prune -f --filter "label=coolify.managed=true"'], $server, false); - instant_remote_process(['docker builder prune -af'], $server, false); - } else { - instant_remote_process(['docker image prune -f'], $server, false); - instant_remote_process(['docker container prune -f --filter "label=coolify.managed=true"'], $server, false); - instant_remote_process(['docker builder prune -f'], $server, false); + + $commands = $this->getCommands(); + + foreach ($commands as $command) { + instant_remote_process([$command], $server, false); } } + + private function getCommands(): array + { + $settings = InstanceSettings::get(); + $helperImageVersion = data_get($settings, 'helper_version'); + $helperImage = config('coolify.helper_image'); + $helperImageWithVersion = config('coolify.helper_image').':'.$helperImageVersion; + + $commonCommands = [ + 'docker container prune -f --filter "label=coolify.managed=true"', + 'docker image prune -af --filter "label!=coolify.managed=true"', + 'docker builder prune -af', + "docker images --filter before=$helperImageWithVersion --filter reference=$helperImage | grep $helperImage | awk '{print $3}' | xargs -r docker rmi", + ]; + + return $commonCommands; + } } diff --git a/app/Actions/Server/InstallLogDrain.php b/app/Actions/Server/InstallLogDrain.php index 034d89fe7..9b6741211 100644 --- a/app/Actions/Server/InstallLogDrain.php +++ b/app/Actions/Server/InstallLogDrain.php @@ -47,7 +47,11 @@ class InstallLogDrain [FILTER] Name modify Match * - Set server_name {$server->name} + Set coolify.server_name {$server->name} + Rename COOLIFY_APP_NAME coolify.app_name + Rename COOLIFY_PROJECT_NAME coolify.project_name + Rename COOLIFY_SERVER_IP coolify.server_ip + Rename COOLIFY_ENVIRONMENT_NAME coolify.environment_name [OUTPUT] Name nrlogs Match * @@ -98,7 +102,11 @@ class InstallLogDrain [FILTER] Name modify Match * - Set server_name {$server->name} + Set coolify.server_name {$server->name} + Rename COOLIFY_APP_NAME coolify.app_name + Rename COOLIFY_PROJECT_NAME coolify.project_name + Rename COOLIFY_SERVER_IP coolify.server_ip + Rename COOLIFY_ENVIRONMENT_NAME coolify.environment_name [OUTPUT] Name http Match * diff --git a/app/Actions/Server/UpdateCoolify.php b/app/Actions/Server/UpdateCoolify.php index 8910d6e97..901f2cf77 100644 --- a/app/Actions/Server/UpdateCoolify.php +++ b/app/Actions/Server/UpdateCoolify.php @@ -2,10 +2,9 @@ namespace App\Actions\Server; +use App\Jobs\PullHelperImageJob; use App\Models\InstanceSettings; use App\Models\Server; -use Illuminate\Support\Facades\File; -use Illuminate\Support\Facades\Http; use Lorisleiva\Actions\Concerns\AsAction; class UpdateCoolify @@ -26,12 +25,7 @@ class UpdateCoolify if (! $this->server) { return; } - CleanupDocker::dispatch($this->server, false)->onQueue('high'); - $response = Http::retry(3, 1000)->get('https://cdn.coollabs.io/coolify/versions.json'); - if ($response->successful()) { - $versions = $response->json(); - File::put(base_path('versions.json'), json_encode($versions, JSON_PRETTY_PRINT)); - } + CleanupDocker::dispatch($this->server)->onQueue('high'); $this->latestVersion = get_latest_version_of_coolify(); $this->currentVersion = config('version'); if (! $manual_update) { @@ -62,10 +56,18 @@ class UpdateCoolify return; } + + $all_servers = Server::all(); + $servers = $all_servers->where('settings.is_usable', true)->where('settings.is_reachable', true)->where('ip', '!=', '1.2.3.4'); + foreach ($servers as $server) { + PullHelperImageJob::dispatch($server); + } + + instant_remote_process(["docker pull -q ghcr.io/coollabsio/coolify:{$this->latestVersion}"], $this->server, false); + remote_process([ 'curl -fsSL https://cdn.coollabs.io/coolify/upgrade.sh -o /data/coolify/source/upgrade.sh', "bash /data/coolify/source/upgrade.sh $this->latestVersion", ], $this->server); - } } diff --git a/app/Actions/Service/StartService.php b/app/Actions/Service/StartService.php index 4b6a25dcc..06d2e0efb 100644 --- a/app/Actions/Service/StartService.php +++ b/app/Actions/Service/StartService.php @@ -16,8 +16,10 @@ class StartService $service->saveComposeConfigs(); $commands[] = 'cd '.$service->workdir(); $commands[] = "echo 'Saved configuration files to {$service->workdir()}.'"; - $commands[] = "echo 'Creating Docker network.'"; - $commands[] = "docker network inspect $service->uuid >/dev/null 2>&1 || docker network create --attachable $service->uuid"; + if ($service->networks()->count() > 0) { + $commands[] = "echo 'Creating Docker network.'"; + $commands[] = "docker network inspect $service->uuid >/dev/null 2>&1 || docker network create --attachable $service->uuid"; + } $commands[] = 'echo Starting service.'; $commands[] = "echo 'Pulling images.'"; $commands[] = 'docker compose pull'; @@ -29,7 +31,7 @@ class StartService $network = $service->destination->network; $serviceNames = data_get(Yaml::parse($compose), 'services', []); foreach ($serviceNames as $serviceName => $serviceConfig) { - $commands[] = "docker network connect --alias {$serviceName}-{$service->uuid} $network {$serviceName}-{$service->uuid} || true"; + $commands[] = "docker network connect --alias {$serviceName}-{$service->uuid} $network {$serviceName}-{$service->uuid} >/dev/null 2>&1 || true"; } } $activity = remote_process($commands, $service->server, type_uuid: $service->uuid, callEventOnFinish: 'ServiceStatusChanged'); diff --git a/app/Console/Commands/CleanupStuckedResources.php b/app/Console/Commands/CleanupStuckedResources.php index fbbf2c820..68beb448a 100644 --- a/app/Console/Commands/CleanupStuckedResources.php +++ b/app/Console/Commands/CleanupStuckedResources.php @@ -3,6 +3,8 @@ namespace App\Console\Commands; use App\Models\Application; +use App\Models\ApplicationPreview; +use App\Models\ScheduledDatabaseBackup; use App\Models\ScheduledTask; use App\Models\Service; use App\Models\ServiceApplication; @@ -42,6 +44,17 @@ class CleanupStuckedResources extends Command } catch (\Throwable $e) { echo "Error in cleaning stuck application: {$e->getMessage()}\n"; } + try { + $applicationsPreviews = ApplicationPreview::get(); + foreach ($applicationsPreviews as $applicationPreview) { + if (! data_get($applicationPreview, 'application')) { + echo "Deleting stuck application preview: {$applicationPreview->uuid}\n"; + $applicationPreview->delete(); + } + } + } catch (\Throwable $e) { + echo "Error in cleaning stuck application: {$e->getMessage()}\n"; + } try { $postgresqls = StandalonePostgresql::withTrashed()->whereNotNull('deleted_at')->get(); foreach ($postgresqls as $postgresql) { @@ -153,6 +166,18 @@ class CleanupStuckedResources extends Command echo "Error in cleaning stuck scheduledtasks: {$e->getMessage()}\n"; } + try { + $scheduled_backups = ScheduledDatabaseBackup::all(); + foreach ($scheduled_backups as $scheduled_backup) { + if (! $scheduled_backup->server()) { + echo "Deleting stuck scheduledbackup: {$scheduled_backup->name}\n"; + $scheduled_backup->delete(); + } + } + } catch (\Throwable $e) { + echo "Error in cleaning stuck scheduledbackups: {$e->getMessage()}\n"; + } + // Cleanup any resources that are not attached to any environment or destination or server try { $applications = Application::all(); diff --git a/app/Console/Commands/Init.php b/app/Console/Commands/Init.php index 30d761a10..7bfd1a14f 100644 --- a/app/Console/Commands/Init.php +++ b/app/Console/Commands/Init.php @@ -132,6 +132,9 @@ class Init extends Command private function cleanup_unused_network_from_coolify_proxy() { + if (isCloud()) { + return; + } foreach ($this->servers as $server) { if (! $server->isFunctional()) { continue; diff --git a/app/Console/Commands/OpenApi.php b/app/Console/Commands/OpenApi.php new file mode 100644 index 000000000..e8d73ef47 --- /dev/null +++ b/app/Console/Commands/OpenApi.php @@ -0,0 +1,26 @@ +errorOutput(); + $error = preg_replace('/^.*an object literal,.*$/m', '', $error); + $error = preg_replace('/^\h*\v+/m', '', $error); + echo $error; + echo $process->output(); + + } +} diff --git a/app/Console/Commands/SyncBunny.php b/app/Console/Commands/SyncBunny.php index 7135cfc9c..228467f88 100644 --- a/app/Console/Commands/SyncBunny.php +++ b/app/Console/Commands/SyncBunny.php @@ -16,7 +16,7 @@ class SyncBunny extends Command * * @var string */ - protected $signature = 'sync:bunny {--templates} {--release}'; + protected $signature = 'sync:bunny {--templates} {--release} {--nightly}'; /** * The console command description. @@ -33,6 +33,7 @@ class SyncBunny extends Command $that = $this; $only_template = $this->option('templates'); $only_version = $this->option('release'); + $nightly = $this->option('nightly'); $bunny_cdn = 'https://cdn.coollabs.io'; $bunny_cdn_path = 'coolify'; $bunny_cdn_storage_name = 'coolcdn'; @@ -45,9 +46,15 @@ class SyncBunny extends Command $upgrade_script = 'upgrade.sh'; $production_env = '.env.production'; $service_template = 'service-templates.json'; - $versions = 'versions.json'; + $compose_file_location = "$parent_dir/$compose_file"; + $compose_file_prod_location = "$parent_dir/$compose_file_prod"; + $install_script_location = "$parent_dir/scripts/install.sh"; + $upgrade_script_location = "$parent_dir/scripts/upgrade.sh"; + $production_env_location = "$parent_dir/.env.production"; + $versions_location = "$parent_dir/$versions"; + PendingRequest::macro('storage', function ($fileName) use ($that) { $headers = [ 'AccessKey' => env('BUNNY_STORAGE_API_KEY'), @@ -73,8 +80,26 @@ class SyncBunny extends Command ]); }); try { + if ($nightly) { + $bunny_cdn_path = 'coolify-nightly'; + + $compose_file_location = "$parent_dir/other/nightly/$compose_file"; + $compose_file_prod_location = "$parent_dir/other/nightly/$compose_file_prod"; + $production_env_location = "$parent_dir/other/nightly/$production_env"; + $upgrade_script_location = "$parent_dir/other/nightly/$upgrade_script"; + $install_script_location = "$parent_dir/other/nightly/$install_script"; + $versions_location = "$parent_dir/other/nightly/$versions"; + } if (! $only_template && ! $only_version) { - $this->info('About to sync files (docker-compose.prod.yaml, upgrade.sh, install.sh, etc) to BunnyCDN.'); + if ($nightly) { + $this->info('About to sync files NIGHTLY (docker-compose.prod.yaml, upgrade.sh, install.sh, etc) to BunnyCDN.'); + } else { + $this->info('About to sync files PRODUCTION (docker-compose.yml, docker-compose.prod.yml, upgrade.sh, install.sh, etc) to BunnyCDN.'); + } + $confirmed = confirm('Are you sure you want to sync?'); + if (! $confirmed) { + return; + } } if ($only_template) { $this->info('About to sync service-templates.json to BunnyCDN.'); @@ -90,8 +115,12 @@ class SyncBunny extends Command return; } elseif ($only_version) { - $this->info('About to sync versions.json to BunnyCDN.'); - $file = file_get_contents("$parent_dir/$versions"); + if ($nightly) { + $this->info('About to sync NIGHLTY versions.json to BunnyCDN.'); + } else { + $this->info('About to sync PRODUCTION versions.json to BunnyCDN.'); + } + $file = file_get_contents($versions_location); $json = json_decode($file, true); $actual_version = data_get($json, 'coolify.v4.version'); @@ -100,7 +129,7 @@ class SyncBunny extends Command return; } Http::pool(fn (Pool $pool) => [ - $pool->storage(fileName: "$parent_dir/$versions")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$versions"), + $pool->storage(fileName: $versions_location)->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$versions"), $pool->purge("$bunny_cdn/$bunny_cdn_path/$versions"), ]); $this->info('versions.json uploaded & purged...'); @@ -109,11 +138,11 @@ class SyncBunny extends Command } Http::pool(fn (Pool $pool) => [ - $pool->storage(fileName: "$parent_dir/$compose_file")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file"), - $pool->storage(fileName: "$parent_dir/$compose_file_prod")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file_prod"), - $pool->storage(fileName: "$parent_dir/$production_env")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$production_env"), - $pool->storage(fileName: "$parent_dir/scripts/$upgrade_script")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$upgrade_script"), - $pool->storage(fileName: "$parent_dir/scripts/$install_script")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$install_script"), + $pool->storage(fileName: "$compose_file_location")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file"), + $pool->storage(fileName: "$compose_file_prod_location")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file_prod"), + $pool->storage(fileName: "$production_env_location")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$production_env"), + $pool->storage(fileName: "$upgrade_script_location")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$upgrade_script"), + $pool->storage(fileName: "$install_script_location")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$install_script"), ]); Http::pool(fn (Pool $pool) => [ $pool->purge("$bunny_cdn/$bunny_cdn_path/$compose_file"), diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index e8f213b16..b960a4a8b 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -4,9 +4,9 @@ namespace App\Console; use App\Jobs\CheckForUpdatesJob; use App\Jobs\CleanupInstanceStuffsJob; +use App\Jobs\CleanupStaleMultiplexedConnections; use App\Jobs\DatabaseBackupJob; use App\Jobs\DockerCleanupJob; -use App\Jobs\PullCoolifyImageJob; use App\Jobs\PullHelperImageJob; use App\Jobs\PullSentinelImageJob; use App\Jobs\PullTemplatesFromCDN; @@ -30,22 +30,24 @@ class Kernel extends ConsoleKernel $this->all_servers = Server::all(); $settings = InstanceSettings::get(); + $schedule->job(new CleanupStaleMultiplexedConnections)->hourly(); + if (isDev()) { // Instance Jobs $schedule->command('horizon:snapshot')->everyMinute(); $schedule->job(new CleanupInstanceStuffsJob)->everyMinute()->onOneServer(); - // Server Jobs $this->check_scheduled_backups($schedule); $this->check_resources($schedule); $this->check_scheduled_tasks($schedule); $schedule->command('uploads:clear')->everyTwoMinutes(); + + $schedule->command('telescope:prune')->daily(); } else { // Instance Jobs $schedule->command('horizon:snapshot')->everyFiveMinutes(); - $schedule->command('cleanup:unreachable-servers')->daily(); - $schedule->job(new PullCoolifyImageJob)->cron($settings->update_check_frequency)->onOneServer(); - $schedule->job(new PullTemplatesFromCDN)->cron($settings->update_check_frequency)->onOneServer(); + $schedule->command('cleanup:unreachable-servers')->daily()->onOneServer(); + $schedule->job(new PullTemplatesFromCDN)->cron($settings->update_check_frequency)->timezone($settings->instance_timezone)->onOneServer(); $schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer(); $this->schedule_updates($schedule); @@ -66,9 +68,19 @@ class Kernel extends ConsoleKernel $servers = $this->all_servers->where('settings.is_usable', true)->where('settings.is_reachable', true)->where('ip', '!=', '1.2.3.4'); foreach ($servers as $server) { if ($server->isSentinelEnabled()) { - $schedule->job(new PullSentinelImageJob($server))->cron($settings->update_check_frequency)->onOneServer(); + $schedule->job(function () use ($server) { + $sentinel_found = instant_remote_process(['docker inspect coolify-sentinel'], $server, false); + $sentinel_found = json_decode($sentinel_found, true); + $status = data_get($sentinel_found, '0.State.Status', 'exited'); + if ($status !== 'running') { + PullSentinelImageJob::dispatch($server); + } + })->cron($settings->update_check_frequency)->timezone($settings->instance_timezone)->onOneServer(); } - $schedule->job(new PullHelperImageJob($server))->cron($settings->update_check_frequency)->onOneServer(); + $schedule->job(new PullHelperImageJob($server)) + ->cron($settings->update_check_frequency) + ->timezone($settings->instance_timezone) + ->onOneServer(); } } @@ -77,11 +89,17 @@ class Kernel extends ConsoleKernel $settings = InstanceSettings::get(); $updateCheckFrequency = $settings->update_check_frequency; - $schedule->job(new CheckForUpdatesJob)->cron($updateCheckFrequency)->onOneServer(); + $schedule->job(new CheckForUpdatesJob) + ->cron($updateCheckFrequency) + ->timezone($settings->instance_timezone) + ->onOneServer(); if ($settings->is_auto_update_enabled) { $autoUpdateFrequency = $settings->auto_update_frequency; - $schedule->job(new UpdateCoolifyJob)->cron($autoUpdateFrequency)->onOneServer(); + $schedule->job(new UpdateCoolifyJob) + ->cron($autoUpdateFrequency) + ->timezone($settings->instance_timezone) + ->onOneServer(); } } @@ -96,7 +114,12 @@ class Kernel extends ConsoleKernel } foreach ($servers as $server) { $schedule->job(new ServerCheckJob($server))->everyMinute()->onOneServer(); - $schedule->job(new DockerCleanupJob($server))->everyTenMinutes()->onOneServer(); + $serverTimezone = $server->settings->server_timezone; + if ($server->settings->force_docker_cleanup) { + $schedule->job(new DockerCleanupJob($server))->cron($server->settings->docker_cleanup_frequency)->timezone($serverTimezone)->onOneServer(); + } else { + $schedule->job(new DockerCleanupJob($server))->everyTenMinutes()->timezone($serverTimezone)->onOneServer(); + } } } @@ -117,12 +140,19 @@ class Kernel extends ConsoleKernel continue; } + $server = $scheduled_backup->server(); + + if (! $server) { + continue; + } + $serverTimezone = $server->settings->server_timezone; + if (isset(VALID_CRON_STRINGS[$scheduled_backup->frequency])) { $scheduled_backup->frequency = VALID_CRON_STRINGS[$scheduled_backup->frequency]; } $schedule->job(new DatabaseBackupJob( backup: $scheduled_backup - ))->cron($scheduled_backup->frequency)->onOneServer(); + ))->cron($scheduled_backup->frequency)->timezone($serverTimezone)->onOneServer(); } } @@ -155,12 +185,19 @@ class Kernel extends ConsoleKernel continue; } } + + $server = $scheduled_task->server(); + if (! $server) { + continue; + } + $serverTimezone = $server->settings->server_timezone ?: config('app.timezone'); + if (isset(VALID_CRON_STRINGS[$scheduled_task->frequency])) { $scheduled_task->frequency = VALID_CRON_STRINGS[$scheduled_task->frequency]; } $schedule->job(new ScheduledTaskJob( task: $scheduled_task - ))->cron($scheduled_task->frequency)->onOneServer(); + ))->cron($scheduled_task->frequency)->timezone($serverTimezone)->onOneServer(); } } diff --git a/app/Http/Controllers/Api/ApplicationsController.php b/app/Http/Controllers/Api/ApplicationsController.php index cd4d724b4..81b173011 100644 --- a/app/Http/Controllers/Api/ApplicationsController.php +++ b/app/Http/Controllers/Api/ApplicationsController.php @@ -53,6 +53,7 @@ class ApplicationsController extends Controller summary: 'List', description: 'List all applications.', path: '/applications', + operationId: 'list-applications', security: [ ['bearerAuth' => []], ], @@ -101,6 +102,7 @@ class ApplicationsController extends Controller summary: 'Create (Public)', description: 'Create new application based on a public git repository.', path: '/applications/public', + operationId: 'create-public-application', security: [ ['bearerAuth' => []], ], @@ -201,7 +203,8 @@ class ApplicationsController extends Controller #[OA\Post( summary: 'Create (Private - GH App)', description: 'Create new application based on a private repository through a Github App.', - path: '/applications/private-gh-app', + path: '/applications/private-github-app', + operationId: 'create-private-github-app-application', security: [ ['bearerAuth' => []], ], @@ -303,6 +306,7 @@ class ApplicationsController extends Controller summary: 'Create (Private - Deploy Key)', description: 'Create new application based on a private repository through a Deploy Key.', path: '/applications/private-deploy-key', + operationId: 'create-private-deploy-key-application', security: [ ['bearerAuth' => []], ], @@ -404,6 +408,7 @@ class ApplicationsController extends Controller summary: 'Create (Dockerfile)', description: 'Create new application based on a simple Dockerfile.', path: '/applications/dockerfile', + operationId: 'create-dockerfile-application', security: [ ['bearerAuth' => []], ], @@ -490,6 +495,7 @@ class ApplicationsController extends Controller summary: 'Create (Docker Image)', description: 'Create new application based on a prebuilt docker image', path: '/applications/dockerimage', + operationId: 'create-dockerimage-application', security: [ ['bearerAuth' => []], ], @@ -573,6 +579,7 @@ class ApplicationsController extends Controller summary: 'Create (Docker Compose)', description: 'Create new application based on a docker-compose file.', path: '/applications/dockercompose', + operationId: 'create-dockercompose-application', security: [ ['bearerAuth' => []], ], @@ -1171,6 +1178,7 @@ class ApplicationsController extends Controller summary: 'Get', description: 'Get application by UUID.', path: '/applications/{uuid}', + operationId: 'get-application-by-uuid', security: [ ['bearerAuth' => []], ], @@ -1235,6 +1243,7 @@ class ApplicationsController extends Controller summary: 'Delete', description: 'Delete application by UUID.', path: '/applications/{uuid}', + operationId: 'delete-application-by-uuid', security: [ ['bearerAuth' => []], ], @@ -1321,6 +1330,7 @@ class ApplicationsController extends Controller summary: 'Update', description: 'Update application by UUID.', path: '/applications/{uuid}', + operationId: 'update-application-by-uuid', security: [ ['bearerAuth' => []], ], @@ -1450,7 +1460,7 @@ class ApplicationsController extends Controller ], 404); } $server = $application->destination->server; - $allowedFields = ['name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'static_image', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'watch_paths', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'redirect']; + $allowedFields = ['name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'static_image', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'watch_paths', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'redirect', 'instant_deploy']; $validator = customApiValidator($request->all(), [ sharedDataApplications(), @@ -1526,6 +1536,10 @@ class ApplicationsController extends Controller } $request->offsetUnset('docker_compose_domains'); } + $instantDeploy = $request->instant_deploy; + + removeUnnecessaryFieldsFromRequest($request); + $data = $request->all(); data_set($data, 'fqdn', $domains); if ($dockerComposeDomainsJson->count() > 0) { @@ -1534,6 +1548,16 @@ class ApplicationsController extends Controller $application->fill($data); $application->save(); + if ($instantDeploy) { + $deployment_uuid = new Cuid2; + + queue_application_deployment( + application: $application, + deployment_uuid: $deployment_uuid, + is_api: true, + ); + } + return response()->json([ 'uuid' => $application->uuid, ]); @@ -1543,6 +1567,7 @@ class ApplicationsController extends Controller summary: 'List Envs', description: 'List all envs by application UUID.', path: '/applications/{uuid}/envs', + operationId: 'list-envs-by-application-uuid', security: [ ['bearerAuth' => []], ], @@ -1625,6 +1650,7 @@ class ApplicationsController extends Controller summary: 'Update Env', description: 'Update env by application UUID.', path: '/applications/{uuid}/envs', + operationId: 'update-env-by-application-uuid', security: [ ['bearerAuth' => []], ], @@ -1807,6 +1833,7 @@ class ApplicationsController extends Controller summary: 'Update Envs (Bulk)', description: 'Update multiple envs by application UUID.', path: '/applications/{uuid}/envs/bulk', + operationId: 'update-envs-by-application-uuid', security: [ ['bearerAuth' => []], ], @@ -1998,6 +2025,7 @@ class ApplicationsController extends Controller summary: 'Create Env', description: 'Create env by application UUID.', path: '/applications/{uuid}/envs', + operationId: 'create-env-by-application-uuid', security: [ ['bearerAuth' => []], ], @@ -2157,6 +2185,7 @@ class ApplicationsController extends Controller summary: 'Delete Env', description: 'Delete env by UUID.', path: '/applications/{uuid}/envs/{env_uuid}', + operationId: 'delete-env-by-application-uuid', security: [ ['bearerAuth' => []], ], @@ -2242,6 +2271,7 @@ class ApplicationsController extends Controller summary: 'Start', description: 'Start application. `Post` request is also accepted.', path: '/applications/{uuid}/start', + operationId: 'start-application-by-uuid', security: [ ['bearerAuth' => []], ], @@ -2345,6 +2375,7 @@ class ApplicationsController extends Controller summary: 'Stop', description: 'Stop application. `Post` request is also accepted.', path: '/applications/{uuid}/stop', + operationId: 'stop-application-by-uuid', security: [ ['bearerAuth' => []], ], @@ -2417,6 +2448,7 @@ class ApplicationsController extends Controller summary: 'Restart', description: 'Restart application. `Post` request is also accepted.', path: '/applications/{uuid}/restart', + operationId: 'restart-application-by-uuid', security: [ ['bearerAuth' => []], ], diff --git a/app/Http/Controllers/Api/DatabasesController.php b/app/Http/Controllers/Api/DatabasesController.php index 0f1ee00d8..a205704cc 100644 --- a/app/Http/Controllers/Api/DatabasesController.php +++ b/app/Http/Controllers/Api/DatabasesController.php @@ -46,6 +46,7 @@ class DatabasesController extends Controller summary: 'List', description: 'List all databases.', path: '/databases', + operationId: 'list-databases', security: [ ['bearerAuth' => []], ], @@ -91,6 +92,7 @@ class DatabasesController extends Controller summary: 'Get', description: 'Get database by UUID.', path: '/databases/{uuid}', + operationId: 'get-database-by-uuid', security: [ ['bearerAuth' => []], ], @@ -151,6 +153,7 @@ class DatabasesController extends Controller summary: 'Update', description: 'Update database by UUID.', path: '/databases/{uuid}', + operationId: 'update-database-by-uuid', security: [ ['bearerAuth' => []], ], @@ -510,6 +513,7 @@ class DatabasesController extends Controller summary: 'Create (PostgreSQL)', description: 'Create a new PostgreSQL database.', path: '/databases/postgresql', + operationId: 'create-database-postgresql', security: [ ['bearerAuth' => []], ], @@ -575,6 +579,7 @@ class DatabasesController extends Controller summary: 'Create (Clickhouse)', description: 'Create a new Clickhouse database.', path: '/databases/clickhouse', + operationId: 'create-database-clickhouse', security: [ ['bearerAuth' => []], ], @@ -636,6 +641,7 @@ class DatabasesController extends Controller summary: 'Create (DragonFly)', description: 'Create a new DragonFly database.', path: '/databases/dragonfly', + operationId: 'create-database-dragonfly', security: [ ['bearerAuth' => []], ], @@ -696,6 +702,7 @@ class DatabasesController extends Controller summary: 'Create (Redis)', description: 'Create a new Redis database.', path: '/databases/redis', + operationId: 'create-database-redis', security: [ ['bearerAuth' => []], ], @@ -757,6 +764,7 @@ class DatabasesController extends Controller summary: 'Create (KeyDB)', description: 'Create a new KeyDB database.', path: '/databases/keydb', + operationId: 'create-database-keydb', security: [ ['bearerAuth' => []], ], @@ -818,6 +826,7 @@ class DatabasesController extends Controller summary: 'Create (MariaDB)', description: 'Create a new MariaDB database.', path: '/databases/mariadb', + operationId: 'create-database-mariadb', security: [ ['bearerAuth' => []], ], @@ -882,6 +891,7 @@ class DatabasesController extends Controller summary: 'Create (MySQL)', description: 'Create a new MySQL database.', path: '/databases/mysql', + operationId: 'create-database-mysql', security: [ ['bearerAuth' => []], ], @@ -945,6 +955,7 @@ class DatabasesController extends Controller summary: 'Create (MongoDB)', description: 'Create a new MongoDB database.', path: '/databases/mongodb', + operationId: 'create-database-mongodb', security: [ ['bearerAuth' => []], ], @@ -1514,6 +1525,7 @@ class DatabasesController extends Controller summary: 'Delete', description: 'Delete database by UUID.', path: '/databases/{uuid}', + operationId: 'delete-database-by-uuid', security: [ ['bearerAuth' => []], ], @@ -1597,6 +1609,7 @@ class DatabasesController extends Controller summary: 'Start', description: 'Start database. `Post` request is also accepted.', path: '/databases/{uuid}/start', + operationId: 'start-database-by-uuid', security: [ ['bearerAuth' => []], ], @@ -1672,6 +1685,7 @@ class DatabasesController extends Controller summary: 'Stop', description: 'Stop database. `Post` request is also accepted.', path: '/databases/{uuid}/stop', + operationId: 'stop-database-by-uuid', security: [ ['bearerAuth' => []], ], @@ -1747,6 +1761,7 @@ class DatabasesController extends Controller summary: 'Restart', description: 'Restart database. `Post` request is also accepted.', path: '/databases/{uuid}/restart', + operationId: 'restart-database-by-uuid', security: [ ['bearerAuth' => []], ], diff --git a/app/Http/Controllers/Api/DeployController.php b/app/Http/Controllers/Api/DeployController.php index 437162058..d1c8f5ea6 100644 --- a/app/Http/Controllers/Api/DeployController.php +++ b/app/Http/Controllers/Api/DeployController.php @@ -32,6 +32,7 @@ class DeployController extends Controller summary: 'List', description: 'List currently running deployments', path: '/deployments', + operationId: 'list-deployments', security: [ ['bearerAuth' => []], ], @@ -79,12 +80,13 @@ class DeployController extends Controller summary: 'Get', description: 'Get deployment by UUID.', path: '/deployments/{uuid}', + operationId: 'get-deployment-by-uuid', security: [ ['bearerAuth' => []], ], tags: ['Deployments'], parameters: [ - new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Deployment Uuid', schema: new OA\Schema(type: 'string')), + new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Deployment UUID', schema: new OA\Schema(type: 'string')), ], responses: [ new OA\Response( @@ -134,6 +136,7 @@ class DeployController extends Controller summary: 'Deploy', description: 'Deploy by tag or uuid. `Post` request also accepted.', path: '/deploy', + operationId: 'deploy-by-tag-or-uuid', security: [ ['bearerAuth' => []], ], @@ -147,7 +150,7 @@ class DeployController extends Controller responses: [ new OA\Response( response: 200, - description: 'Get deployment(s) Uuid\'s', + description: 'Get deployment(s) UUID\'s', content: [ new OA\MediaType( mediaType: 'application/json', diff --git a/app/Http/Controllers/Api/EnvironmentVariablesController.php b/app/Http/Controllers/Api/EnvironmentVariablesController.php deleted file mode 100644 index d127d0525..000000000 --- a/app/Http/Controllers/Api/EnvironmentVariablesController.php +++ /dev/null @@ -1,35 +0,0 @@ -env_uuid)->first(); - if (! $env) { - return response()->json([ - 'message' => 'Environment variable not found.', - ], 404); - } - $found_app = $env->resource()->whereRelation('environment.project.team', 'id', $teamId)->first(); - if (! $found_app) { - return response()->json([ - 'message' => 'Environment variable not found.', - ], 404); - } - $env->delete(); - - return response()->json([ - 'message' => 'Environment variable deleted.', - ]); - } -} diff --git a/app/Http/Controllers/Api/OtherController.php b/app/Http/Controllers/Api/OtherController.php index 1e48ffdbe..c085b88a5 100644 --- a/app/Http/Controllers/Api/OtherController.php +++ b/app/Http/Controllers/Api/OtherController.php @@ -13,6 +13,7 @@ class OtherController extends Controller summary: 'Version', description: 'Get Coolify version.', path: '/version', + operationId: 'version', security: [ ['bearerAuth' => []], ], @@ -43,6 +44,7 @@ class OtherController extends Controller summary: 'Enable API', description: 'Enable API (only with root permissions).', path: '/enable', + operationId: 'enable-api', security: [ ['bearerAuth' => []], ], @@ -94,6 +96,7 @@ class OtherController extends Controller summary: 'Disable API', description: 'Disable API (only with root permissions).', path: '/disable', + operationId: 'disable-api', security: [ ['bearerAuth' => []], ], @@ -158,6 +161,7 @@ class OtherController extends Controller summary: 'Healthcheck', description: 'Healthcheck endpoint.', path: '/healthcheck', + operationId: 'healthcheck', responses: [ new OA\Response( response: 200, diff --git a/app/Http/Controllers/Api/ProjectController.php b/app/Http/Controllers/Api/ProjectController.php index 6aec31e9b..f1958de2c 100644 --- a/app/Http/Controllers/Api/ProjectController.php +++ b/app/Http/Controllers/Api/ProjectController.php @@ -11,8 +11,9 @@ class ProjectController extends Controller { #[OA\Get( summary: 'List', - description: 'list projects.', + description: 'List projects.', path: '/projects', + operationId: 'list-projects', security: [ ['bearerAuth' => []], ], @@ -46,7 +47,7 @@ class ProjectController extends Controller if (is_null($teamId)) { return invalidTokenResponse(); } - $projects = Project::whereTeamId($teamId)->select('id', 'name', 'uuid')->get(); + $projects = Project::whereTeamId($teamId)->select('id', 'name', 'description', 'uuid')->get(); return response()->json(serializeApiResponse($projects), ); @@ -54,8 +55,9 @@ class ProjectController extends Controller #[OA\Get( summary: 'Get', - description: 'Get project by Uuid.', + description: 'Get project by UUID.', path: '/projects/{uuid}', + operationId: 'get-project-by-uuid', security: [ ['bearerAuth' => []], ], @@ -102,6 +104,7 @@ class ProjectController extends Controller summary: 'Environment', description: 'Get environment by name.', path: '/projects/{uuid}/{environment_name}', + operationId: 'get-environment-by-name', security: [ ['bearerAuth' => []], ], @@ -136,12 +139,15 @@ class ProjectController extends Controller return invalidTokenResponse(); } if (! $request->uuid) { - return response()->json(['message' => 'Uuid is required.'], 422); + return response()->json(['message' => 'UUID is required.'], 422); } if (! $request->environment_name) { return response()->json(['message' => 'Environment name is required.'], 422); } $project = Project::whereTeamId($teamId)->whereUuid($request->uuid)->first(); + if (! $project) { + return response()->json(['message' => 'Project not found.'], 404); + } $environment = $project->environments()->whereName($request->environment_name)->first(); if (! $environment) { return response()->json(['message' => 'Environment not found.'], 404); @@ -155,6 +161,7 @@ class ProjectController extends Controller summary: 'Create', description: 'Create Project.', path: '/projects', + operationId: 'create-project', security: [ ['bearerAuth' => []], ], @@ -167,7 +174,7 @@ class ProjectController extends Controller schema: new OA\Schema( type: 'object', properties: [ - 'uuid' => ['type' => 'string', 'description' => 'The name of the project.'], + 'name' => ['type' => 'string', 'description' => 'The name of the project.'], 'description' => ['type' => 'string', 'description' => 'The description of the project.'], ], ), @@ -250,6 +257,7 @@ class ProjectController extends Controller summary: 'Update', description: 'Update Project.', path: '/projects/{uuid}', + operationId: 'update-project-by-uuid', security: [ ['bearerAuth' => []], ], @@ -333,7 +341,7 @@ class ProjectController extends Controller } $uuid = $request->uuid; if (! $uuid) { - return response()->json(['message' => 'Uuid is required.'], 422); + return response()->json(['message' => 'UUID is required.'], 422); } $project = Project::whereTeamId($teamId)->whereUuid($uuid)->first(); @@ -355,6 +363,7 @@ class ProjectController extends Controller summary: 'Delete', description: 'Delete project by UUID.', path: '/projects/{uuid}', + operationId: 'delete-project-by-uuid', security: [ ['bearerAuth' => []], ], @@ -408,7 +417,7 @@ class ProjectController extends Controller } if (! $request->uuid) { - return response()->json(['message' => 'Uuid is required.'], 422); + return response()->json(['message' => 'UUID is required.'], 422); } $project = Project::whereTeamId($teamId)->whereUuid($request->uuid)->first(); if (! $project) { diff --git a/app/Http/Controllers/Api/ResourcesController.php b/app/Http/Controllers/Api/ResourcesController.php index ae076bb71..1fd5792e0 100644 --- a/app/Http/Controllers/Api/ResourcesController.php +++ b/app/Http/Controllers/Api/ResourcesController.php @@ -13,6 +13,7 @@ class ResourcesController extends Controller summary: 'List', description: 'Get all resources.', path: '/resources', + operationId: 'list-resources', security: [ ['bearerAuth' => []], ], diff --git a/app/Http/Controllers/Api/SecurityController.php b/app/Http/Controllers/Api/SecurityController.php index 67128234e..bb474aed3 100644 --- a/app/Http/Controllers/Api/SecurityController.php +++ b/app/Http/Controllers/Api/SecurityController.php @@ -26,6 +26,7 @@ class SecurityController extends Controller summary: 'List', description: 'List all private keys.', path: '/security/keys', + operationId: 'list-private-keys', security: [ ['bearerAuth' => []], ], @@ -68,12 +69,13 @@ class SecurityController extends Controller summary: 'Get', description: 'Get key by UUID.', path: '/security/keys/{uuid}', + operationId: 'get-private-key-by-uuid', security: [ ['bearerAuth' => []], ], tags: ['Private Keys'], parameters: [ - new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Private Key Uuid', schema: new OA\Schema(type: 'string')), + new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Private Key UUID', schema: new OA\Schema(type: 'string')), ], responses: [ new OA\Response( @@ -124,6 +126,7 @@ class SecurityController extends Controller summary: 'Create', description: 'Create a new private key.', path: '/security/keys', + operationId: 'create-private-key', security: [ ['bearerAuth' => []], ], @@ -217,6 +220,7 @@ class SecurityController extends Controller summary: 'Update', description: 'Update a private key.', path: '/security/keys', + operationId: 'update-private-key', security: [ ['bearerAuth' => []], ], @@ -313,12 +317,13 @@ class SecurityController extends Controller summary: 'Delete', description: 'Delete a private key.', path: '/security/keys/{uuid}', + operationId: 'delete-private-key-by-uuid', security: [ ['bearerAuth' => []], ], tags: ['Private Keys'], parameters: [ - new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Private Key Uuid', schema: new OA\Schema(type: 'string')), + new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Private Key UUID', schema: new OA\Schema(type: 'string')), ], responses: [ new OA\Response( diff --git a/app/Http/Controllers/Api/ServersController.php b/app/Http/Controllers/Api/ServersController.php index 9044c4a35..5f0d6bb12 100644 --- a/app/Http/Controllers/Api/ServersController.php +++ b/app/Http/Controllers/Api/ServersController.php @@ -46,6 +46,7 @@ class ServersController extends Controller summary: 'List', description: 'List all servers.', path: '/servers', + operationId: 'list-servers', security: [ ['bearerAuth' => []], ], @@ -100,12 +101,13 @@ class ServersController extends Controller summary: 'Get', description: 'Get server by UUID.', path: '/servers/{uuid}', + operationId: 'get-server-by-uuid', security: [ ['bearerAuth' => []], ], tags: ['Servers'], parameters: [ - new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Server\'s Uuid', schema: new OA\Schema(type: 'string')), + new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Server\'s UUID', schema: new OA\Schema(type: 'string')), ], responses: [ new OA\Response( @@ -177,12 +179,13 @@ class ServersController extends Controller summary: 'Resources', description: 'Get resources by server.', path: '/servers/{uuid}/resources', + operationId: 'get-resources-by-server-uuid', security: [ ['bearerAuth' => []], ], tags: ['Servers'], parameters: [ - new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Server\'s Uuid', schema: new OA\Schema(type: 'string')), + new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Server\'s UUID', schema: new OA\Schema(type: 'string')), ], responses: [ new OA\Response( @@ -254,12 +257,13 @@ class ServersController extends Controller summary: 'Domains', description: 'Get domains by server.', path: '/servers/{uuid}/domains', + operationId: 'get-domains-by-server-uuid', security: [ ['bearerAuth' => []], ], tags: ['Servers'], parameters: [ - new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Server\'s Uuid', schema: new OA\Schema(type: 'string')), + new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Server\'s UUID', schema: new OA\Schema(type: 'string')), ], responses: [ new OA\Response( @@ -401,6 +405,7 @@ class ServersController extends Controller summary: 'Create', description: 'Create Server.', path: '/servers', + operationId: 'create-server', security: [ ['bearerAuth' => []], ], @@ -545,6 +550,7 @@ class ServersController extends Controller summary: 'Update', description: 'Update Server.', path: '/servers/{uuid}', + operationId: 'update-server-by-uuid', security: [ ['bearerAuth' => []], ], @@ -655,6 +661,7 @@ class ServersController extends Controller summary: 'Delete', description: 'Delete server by UUID.', path: '/servers/{uuid}', + operationId: 'delete-server-by-uuid', security: [ ['bearerAuth' => []], ], @@ -727,6 +734,7 @@ class ServersController extends Controller summary: 'Validate', description: 'Validate server by UUID.', path: '/servers/{uuid}/validate', + operationId: 'validate-server-by-uuid', security: [ ['bearerAuth' => []], ], diff --git a/app/Http/Controllers/Api/ServicesController.php b/app/Http/Controllers/Api/ServicesController.php index 3a5b59f55..0a6154410 100644 --- a/app/Http/Controllers/Api/ServicesController.php +++ b/app/Http/Controllers/Api/ServicesController.php @@ -38,6 +38,7 @@ class ServicesController extends Controller summary: 'List', description: 'List all services.', path: '/services', + operationId: 'list-services', security: [ ['bearerAuth' => []], ], @@ -88,6 +89,7 @@ class ServicesController extends Controller summary: 'Create', description: 'Create a one-click service', path: '/services', + operationId: 'create-service', security: [ ['bearerAuth' => []], ], @@ -365,6 +367,7 @@ class ServicesController extends Controller summary: 'Get', description: 'Get service by UUID.', path: '/services/{uuid}', + operationId: 'get-service-by-uuid', security: [ ['bearerAuth' => []], ], @@ -375,7 +378,7 @@ class ServicesController extends Controller responses: [ new OA\Response( response: 200, - description: 'Get a service by Uuid.', + description: 'Get a service by UUID.', content: [ new OA\MediaType( mediaType: 'application/json', @@ -422,6 +425,7 @@ class ServicesController extends Controller summary: 'Delete', description: 'Delete service by UUID.', path: '/services/{uuid}', + operationId: 'delete-service-by-uuid', security: [ ['bearerAuth' => []], ], @@ -432,7 +436,7 @@ class ServicesController extends Controller responses: [ new OA\Response( response: 200, - description: 'Delete a service by Uuid', + description: 'Delete a service by UUID', content: [ new OA\MediaType( mediaType: 'application/json', @@ -479,10 +483,521 @@ class ServicesController extends Controller ]); } + #[OA\Get( + summary: 'List Envs', + description: 'List all envs by service UUID.', + path: '/services/{uuid}/envs', + operationId: 'list-envs-by-service-uuid', + security: [ + ['bearerAuth' => []], + ], + tags: ['Services'], + parameters: [ + new OA\Parameter( + name: 'uuid', + in: 'path', + description: 'UUID of the service.', + required: true, + schema: new OA\Schema( + type: 'string', + format: 'uuid', + ) + ), + ], + responses: [ + new OA\Response( + response: 200, + description: 'All environment variables by service UUID.', + content: [ + new OA\MediaType( + mediaType: 'application/json', + schema: new OA\Schema( + type: 'array', + items: new OA\Items(ref: '#/components/schemas/EnvironmentVariable') + ) + ), + ]), + new OA\Response( + response: 401, + ref: '#/components/responses/401', + ), + new OA\Response( + response: 400, + ref: '#/components/responses/400', + ), + new OA\Response( + response: 404, + ref: '#/components/responses/404', + ), + ] + )] + public function envs(Request $request) + { + $teamId = getTeamIdFromToken(); + if (is_null($teamId)) { + return invalidTokenResponse(); + } + $service = Service::whereRelation('environment.project.team', 'id', $teamId)->whereUuid($request->uuid)->first(); + if (! $service) { + return response()->json(['message' => 'Service not found.'], 404); + } + + $envs = $service->environment_variables->map(function ($env) { + $env->makeHidden([ + 'application_id', + 'standalone_clickhouse_id', + 'standalone_dragonfly_id', + 'standalone_keydb_id', + 'standalone_mariadb_id', + 'standalone_mongodb_id', + 'standalone_mysql_id', + 'standalone_postgresql_id', + 'standalone_redis_id', + ]); + $env = $this->removeSensitiveData($env); + + return $env; + }); + + return response()->json($envs); + } + + #[OA\Patch( + summary: 'Update Env', + description: 'Update env by service UUID.', + path: '/services/{uuid}/envs', + operationId: 'update-env-by-service-uuid', + security: [ + ['bearerAuth' => []], + ], + tags: ['Services'], + parameters: [ + new OA\Parameter( + name: 'uuid', + in: 'path', + description: 'UUID of the service.', + required: true, + schema: new OA\Schema( + type: 'string', + format: 'uuid', + ) + ), + ], + requestBody: new OA\RequestBody( + description: 'Env updated.', + required: true, + content: [ + new OA\MediaType( + mediaType: 'application/json', + schema: new OA\Schema( + type: 'object', + required: ['key', 'value'], + properties: [ + 'key' => ['type' => 'string', 'description' => 'The key of the environment variable.'], + 'value' => ['type' => 'string', 'description' => 'The value of the environment variable.'], + 'is_preview' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is used in preview deployments.'], + 'is_build_time' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is used in build time.'], + 'is_literal' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is a literal, nothing espaced.'], + 'is_multiline' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is multiline.'], + 'is_shown_once' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable\'s value is shown on the UI.'], + ], + ), + ), + ], + ), + responses: [ + new OA\Response( + response: 201, + description: 'Environment variable updated.', + content: [ + new OA\MediaType( + mediaType: 'application/json', + schema: new OA\Schema( + type: 'object', + properties: [ + 'message' => ['type' => 'string', 'example' => 'Environment variable updated.'], + ] + ) + ), + ]), + new OA\Response( + response: 401, + ref: '#/components/responses/401', + ), + new OA\Response( + response: 400, + ref: '#/components/responses/400', + ), + new OA\Response( + response: 404, + ref: '#/components/responses/404', + ), + ] + )] + public function update_env_by_uuid(Request $request) + { + $teamId = getTeamIdFromToken(); + if (is_null($teamId)) { + return invalidTokenResponse(); + } + + $service = Service::whereRelation('environment.project.team', 'id', $teamId)->whereUuid($request->uuid)->first(); + if (! $service) { + return response()->json(['message' => 'Service not found.'], 404); + } + + $validator = customApiValidator($request->all(), [ + 'key' => 'string|required', + 'value' => 'string|nullable', + 'is_build_time' => 'boolean', + 'is_literal' => 'boolean', + 'is_multiline' => 'boolean', + 'is_shown_once' => 'boolean', + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 422); + } + + $env = $service->environment_variables()->where('key', $request->key)->first(); + if (! $env) { + return response()->json(['message' => 'Environment variable not found.'], 404); + } + + $env->fill($request->all()); + $env->save(); + + return response()->json($this->removeSensitiveData($env))->setStatusCode(201); + } + + #[OA\Patch( + summary: 'Update Envs (Bulk)', + description: 'Update multiple envs by service UUID.', + path: '/services/{uuid}/envs/bulk', + operationId: 'update-envs-by-service-uuid', + security: [ + ['bearerAuth' => []], + ], + tags: ['Services'], + parameters: [ + new OA\Parameter( + name: 'uuid', + in: 'path', + description: 'UUID of the service.', + required: true, + schema: new OA\Schema( + type: 'string', + format: 'uuid', + ) + ), + ], + requestBody: new OA\RequestBody( + description: 'Bulk envs updated.', + required: true, + content: [ + new OA\MediaType( + mediaType: 'application/json', + schema: new OA\Schema( + type: 'object', + required: ['data'], + properties: [ + 'data' => [ + 'type' => 'array', + 'items' => new OA\Schema( + type: 'object', + properties: [ + 'key' => ['type' => 'string', 'description' => 'The key of the environment variable.'], + 'value' => ['type' => 'string', 'description' => 'The value of the environment variable.'], + 'is_preview' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is used in preview deployments.'], + 'is_build_time' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is used in build time.'], + 'is_literal' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is a literal, nothing espaced.'], + 'is_multiline' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is multiline.'], + 'is_shown_once' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable\'s value is shown on the UI.'], + ], + ), + ], + ], + ), + ), + ], + ), + responses: [ + new OA\Response( + response: 201, + description: 'Environment variables updated.', + content: [ + new OA\MediaType( + mediaType: 'application/json', + schema: new OA\Schema( + type: 'object', + properties: [ + 'message' => ['type' => 'string', 'example' => 'Environment variables updated.'], + ] + ) + ), + ]), + new OA\Response( + response: 401, + ref: '#/components/responses/401', + ), + new OA\Response( + response: 400, + ref: '#/components/responses/400', + ), + new OA\Response( + response: 404, + ref: '#/components/responses/404', + ), + ] + )] + public function create_bulk_envs(Request $request) + { + $teamId = getTeamIdFromToken(); + if (is_null($teamId)) { + return invalidTokenResponse(); + } + + $service = Service::whereRelation('environment.project.team', 'id', $teamId)->whereUuid($request->uuid)->first(); + if (! $service) { + return response()->json(['message' => 'Service not found.'], 404); + } + + $bulk_data = $request->get('data'); + if (! $bulk_data) { + return response()->json(['message' => 'Bulk data is required.'], 400); + } + + $updatedEnvs = collect(); + foreach ($bulk_data as $item) { + $validator = customApiValidator($item, [ + 'key' => 'string|required', + 'value' => 'string|nullable', + 'is_build_time' => 'boolean', + 'is_literal' => 'boolean', + 'is_multiline' => 'boolean', + 'is_shown_once' => 'boolean', + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 422); + } + + $env = $service->environment_variables()->updateOrCreate( + ['key' => $item['key']], + $item + ); + + $updatedEnvs->push($this->removeSensitiveData($env)); + } + + return response()->json($updatedEnvs)->setStatusCode(201); + } + + #[OA\Post( + summary: 'Create Env', + description: 'Create env by service UUID.', + path: '/services/{uuid}/envs', + operationId: 'create-env-by-service-uuid', + security: [ + ['bearerAuth' => []], + ], + tags: ['Services'], + parameters: [ + new OA\Parameter( + name: 'uuid', + in: 'path', + description: 'UUID of the service.', + required: true, + schema: new OA\Schema( + type: 'string', + format: 'uuid', + ) + ), + ], + requestBody: new OA\RequestBody( + required: true, + description: 'Env created.', + content: new OA\MediaType( + mediaType: 'application/json', + schema: new OA\Schema( + type: 'object', + properties: [ + 'key' => ['type' => 'string', 'description' => 'The key of the environment variable.'], + 'value' => ['type' => 'string', 'description' => 'The value of the environment variable.'], + 'is_preview' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is used in preview deployments.'], + 'is_build_time' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is used in build time.'], + 'is_literal' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is a literal, nothing espaced.'], + 'is_multiline' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is multiline.'], + 'is_shown_once' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable\'s value is shown on the UI.'], + ], + ), + ), + ), + responses: [ + new OA\Response( + response: 201, + description: 'Environment variable created.', + content: [ + new OA\MediaType( + mediaType: 'application/json', + schema: new OA\Schema( + type: 'object', + properties: [ + 'uuid' => ['type' => 'string', 'example' => 'nc0k04gk8g0cgsk440g0koko'], + ] + ) + ), + ]), + new OA\Response( + response: 401, + ref: '#/components/responses/401', + ), + new OA\Response( + response: 400, + ref: '#/components/responses/400', + ), + new OA\Response( + response: 404, + ref: '#/components/responses/404', + ), + ] + )] + public function create_env(Request $request) + { + $teamId = getTeamIdFromToken(); + if (is_null($teamId)) { + return invalidTokenResponse(); + } + + $service = Service::whereRelation('environment.project.team', 'id', $teamId)->whereUuid($request->uuid)->first(); + if (! $service) { + return response()->json(['message' => 'Service not found.'], 404); + } + + $validator = customApiValidator($request->all(), [ + 'key' => 'string|required', + 'value' => 'string|nullable', + 'is_build_time' => 'boolean', + 'is_literal' => 'boolean', + 'is_multiline' => 'boolean', + 'is_shown_once' => 'boolean', + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $validator->errors(), + ], 422); + } + + $existingEnv = $service->environment_variables()->where('key', $request->key)->first(); + if ($existingEnv) { + return response()->json([ + 'message' => 'Environment variable already exists. Use PATCH request to update it.', + ], 409); + } + + $env = $service->environment_variables()->create($request->all()); + + return response()->json($this->removeSensitiveData($env))->setStatusCode(201); + } + + #[OA\Delete( + summary: 'Delete Env', + description: 'Delete env by UUID.', + path: '/services/{uuid}/envs/{env_uuid}', + operationId: 'delete-env-by-service-uuid', + security: [ + ['bearerAuth' => []], + ], + tags: ['Services'], + parameters: [ + new OA\Parameter( + name: 'uuid', + in: 'path', + description: 'UUID of the service.', + required: true, + schema: new OA\Schema( + type: 'string', + format: 'uuid', + ) + ), + new OA\Parameter( + name: 'env_uuid', + in: 'path', + description: 'UUID of the environment variable.', + required: true, + schema: new OA\Schema( + type: 'string', + format: 'uuid', + ) + ), + ], + responses: [ + new OA\Response( + response: 200, + description: 'Environment variable deleted.', + content: [ + new OA\MediaType( + mediaType: 'application/json', + schema: new OA\Schema( + type: 'object', + properties: [ + 'message' => ['type' => 'string', 'example' => 'Environment variable deleted.'], + ] + ) + ), + ]), + new OA\Response( + response: 401, + ref: '#/components/responses/401', + ), + new OA\Response( + response: 400, + ref: '#/components/responses/400', + ), + new OA\Response( + response: 404, + ref: '#/components/responses/404', + ), + ] + )] + public function delete_env_by_uuid(Request $request) + { + $teamId = getTeamIdFromToken(); + if (is_null($teamId)) { + return invalidTokenResponse(); + } + + $service = Service::whereRelation('environment.project.team', 'id', $teamId)->whereUuid($request->uuid)->first(); + if (! $service) { + return response()->json(['message' => 'Service not found.'], 404); + } + + $env = EnvironmentVariable::where('uuid', $request->env_uuid) + ->where('service_id', $service->id) + ->first(); + + if (! $env) { + return response()->json(['message' => 'Environment variable not found.'], 404); + } + + $env->forceDelete(); + + return response()->json(['message' => 'Environment variable deleted.']); + } + #[OA\Get( summary: 'Start', description: 'Start service. `Post` request is also accepted.', path: '/services/{uuid}/start', + operationId: 'start-service-by-uuid', security: [ ['bearerAuth' => []], ], @@ -558,6 +1073,7 @@ class ServicesController extends Controller summary: 'Stop', description: 'Stop service. `Post` request is also accepted.', path: '/services/{uuid}/stop', + operationId: 'stop-service-by-uuid', security: [ ['bearerAuth' => []], ], @@ -633,6 +1149,7 @@ class ServicesController extends Controller summary: 'Restart', description: 'Restart service. `Post` request is also accepted.', path: '/services/{uuid}/restart', + operationId: 'restart-service-by-uuid', security: [ ['bearerAuth' => []], ], diff --git a/app/Http/Controllers/Api/TeamController.php b/app/Http/Controllers/Api/TeamController.php index 1a481e5ec..3f951c6f7 100644 --- a/app/Http/Controllers/Api/TeamController.php +++ b/app/Http/Controllers/Api/TeamController.php @@ -32,6 +32,7 @@ class TeamController extends Controller summary: 'List', description: 'Get all teams.', path: '/teams', + operationId: 'list-teams', security: [ ['bearerAuth' => []], ], @@ -79,6 +80,7 @@ class TeamController extends Controller summary: 'Get', description: 'Get team by TeamId.', path: '/teams/{id}', + operationId: 'get-team-by-id', security: [ ['bearerAuth' => []], ], @@ -129,6 +131,7 @@ class TeamController extends Controller summary: 'Members', description: 'Get members by TeamId.', path: '/teams/{id}/members', + operationId: 'get-members-by-team-id', security: [ ['bearerAuth' => []], ], @@ -189,6 +192,7 @@ class TeamController extends Controller summary: 'Authenticated Team', description: 'Get currently authenticated team.', path: '/teams/current', + operationId: 'get-current-team', security: [ ['bearerAuth' => []], ], @@ -225,6 +229,7 @@ class TeamController extends Controller summary: 'Authenticated Team Members', description: 'Get currently authenticated team members.', path: '/teams/current/members', + operationId: 'get-current-team-members', security: [ ['bearerAuth' => []], ], diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index 42c1ba43c..d8f85cff8 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -12,6 +12,7 @@ use App\Models\ApplicationPreview; use App\Models\EnvironmentVariable; use App\Models\GithubApp; use App\Models\GitlabApp; +use App\Models\InstanceSettings; use App\Models\Server; use App\Models\StandaloneDocker; use App\Models\SwarmDocker; @@ -110,10 +111,12 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue private bool $is_debug_enabled; - private $build_args; + private Collection|string $build_args; private $env_args; + private $environment_variables; + private $env_nixpacks_args; private $docker_compose; @@ -158,7 +161,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue private ?string $coolify_variables = null; - private bool $preserveRepository = true; + private bool $preserveRepository = false; public $tries = 1; @@ -167,6 +170,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue $this->application_deployment_queue = ApplicationDeploymentQueue::find($application_deployment_queue_id); $this->application = Application::find($this->application_deployment_queue->application_id); $this->build_pack = data_get($this->application, 'build_pack'); + $this->build_args = collect([]); $this->application_deployment_queue_id = $application_deployment_queue_id; $this->deployment_uuid = $this->application_deployment_queue->deployment_uuid; @@ -199,9 +203,13 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue $this->container_name = generateApplicationContainerName($this->application, $this->pull_request_id); if ($this->application->settings->custom_internal_name && ! $this->application->settings->is_consistent_container_name_enabled) { - $this->container_name = $this->application->settings->custom_internal_name; + if ($this->pull_request_id === 0) { + $this->container_name = $this->application->settings->custom_internal_name; + } else { + $this->container_name = "{$this->application->settings->custom_internal_name}-pr-{$this->pull_request_id}"; + } } - ray('New container name: ', $this->container_name); + ray('New container name: ', $this->container_name)->green(); savePrivateKeyToFs($this->server); $this->saved_outputs = collect(); @@ -277,6 +285,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue $this->original_server = $this->server; } else { $this->build_server = $buildServers->random(); + $this->application_deployment_queue->build_server_id = $this->build_server->id; $this->application_deployment_queue->addLogEntry("Found a suitable build server ({$this->build_server->name})."); $this->original_server = $this->server; $this->use_build_server = true; @@ -415,15 +424,42 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue $this->prepare_builder_image(); $this->check_git_if_build_needed(); $this->clone_repository(); + if ($this->preserveRepository) { + foreach ($this->application->fileStorages as $fileStorage) { + $path = $fileStorage->fs_path; + $saveName = 'file_stat_'.$fileStorage->id; + $realPathInGit = str($path)->replace($this->application->workdir(), $this->workdir)->value(); + // check if the file is a directory or a file inside the repository + $this->execute_remote_command( + [executeInDocker($this->deployment_uuid, "stat -c '%F' {$realPathInGit}"), 'hidden' => true, 'ignore_errors' => true, 'save' => $saveName] + ); + if ($this->saved_outputs->has($saveName)) { + $fileStat = $this->saved_outputs->get($saveName); + if ($fileStat->value() === 'directory' && ! $fileStorage->is_directory) { + $fileStorage->is_directory = true; + $fileStorage->content = null; + $fileStorage->save(); + $fileStorage->deleteStorageOnServer(); + $fileStorage->saveStorageOnServer(); + } elseif ($fileStat->value() === 'regular file' && $fileStorage->is_directory) { + $fileStorage->is_directory = false; + $fileStorage->is_based_on_git = true; + $fileStorage->save(); + $fileStorage->deleteStorageOnServer(); + $fileStorage->saveStorageOnServer(); + } + } + } + } $this->generate_image_names(); $this->cleanup_git(); $this->application->loadComposeFile(isInit: false); if ($this->application->settings->is_raw_compose_deployment_enabled) { - $this->application->parseRawCompose(); + $this->application->oldRawParser(); $yaml = $composeFile = $this->application->docker_compose_raw; $this->save_environment_variables(); } else { - $composeFile = $this->application->parseCompose(pull_request_id: $this->pull_request_id, preview_id: data_get($this, 'preview.id')); + $composeFile = $this->application->parse(pull_request_id: $this->pull_request_id, preview_id: data_get($this->preview, 'id')); $this->save_environment_variables(); if (! is_null($this->env_filename)) { $services = collect($composeFile['services']); @@ -440,11 +476,12 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue return; } - $yaml = Yaml::dump($composeFile->toArray(), 10); + $yaml = Yaml::dump(convertToArray($composeFile), 10); } $this->docker_compose_base64 = base64_encode($yaml); $this->execute_remote_command([ - executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d | tee {$this->workdir}{$this->docker_compose_location} > /dev/null"), 'hidden' => true, + executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d | tee {$this->workdir}{$this->docker_compose_location} > /dev/null"), + 'hidden' => true, ]); // Build new container to limit downtime. $this->application_deployment_queue->addLogEntry('Pulling & building required images.'); @@ -474,13 +511,18 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue // TODO } else { $this->execute_remote_command([ - "docker network inspect '{$networkId}' >/dev/null 2>&1 || docker network create --attachable '{$networkId}' >/dev/null || true", 'hidden' => true, 'ignore_errors' => true, + "docker network inspect '{$networkId}' >/dev/null 2>&1 || docker network create --attachable '{$networkId}' >/dev/null || true", + 'hidden' => true, + 'ignore_errors' => true, ], [ - "docker network connect {$networkId} coolify-proxy || true", 'hidden' => true, 'ignore_errors' => true, + "docker network connect {$networkId} coolify-proxy >/dev/null 2>&1 || true", + 'hidden' => true, + 'ignore_errors' => true, ]); } // Start compose file + $server_workdir = $this->application->workdir(); if ($this->application->settings->is_raw_compose_deployment_enabled) { if ($this->docker_compose_custom_start_command) { $this->write_deployment_configurations(); @@ -489,7 +531,6 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue ); } else { $this->write_deployment_configurations(); - $server_workdir = $this->application->workdir(); $this->docker_compose_location = '/docker-compose.yaml'; $command = "{$this->coolify_variables} docker compose"; @@ -509,15 +550,26 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue ); } else { $command = "{$this->coolify_variables} docker compose"; - if ($this->env_filename) { - $command .= " --env-file {$this->workdir}/{$this->env_filename}"; - } - $command .= " --project-name {$this->application->uuid} --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up -d"; + if ($this->preserveRepository) { + if ($this->env_filename) { + $command .= " --env-file {$server_workdir}/{$this->env_filename}"; + } + $command .= " --project-name {$this->application->uuid} --project-directory {$server_workdir} -f {$server_workdir}{$this->docker_compose_location} up -d"; + $this->write_deployment_configurations(); - $this->write_deployment_configurations(); - $this->execute_remote_command( - [executeInDocker($this->deployment_uuid, $command), 'hidden' => true], - ); + $this->execute_remote_command( + ['command' => $command, 'hidden' => true], + ); + } else { + if ($this->env_filename) { + $command .= " --env-file {$this->workdir}/{$this->env_filename}"; + } + $command .= " --project-name {$this->application->uuid} --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up -d"; + $this->execute_remote_command( + [executeInDocker($this->deployment_uuid, $command), 'hidden' => true], + ); + $this->write_deployment_configurations(); + } } } @@ -611,15 +663,16 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue [ "mkdir -p $this->configuration_dir", ], - // removing this now as we are using docker cp - // [ - // "rm -rf $this->configuration_dir/{*,.*}", - // ], [ "docker cp {$this->deployment_uuid}:{$this->workdir}/. {$this->configuration_dir}", ], ); } + foreach ($this->application->fileStorages as $fileStorage) { + if (! $fileStorage->is_based_on_git && ! $fileStorage->is_directory) { + $fileStorage->saveStorageOnServer(); + } + } if ($this->use_build_server) { $this->server = $this->build_server; } @@ -699,7 +752,8 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue $this->application_deployment_queue->addLogEntry("Pushing image to docker registry ({$this->production_image_name})."); $this->execute_remote_command( [ - executeInDocker($this->deployment_uuid, "docker push {$this->production_image_name}"), 'hidden' => true, + executeInDocker($this->deployment_uuid, "docker push {$this->production_image_name}"), + 'hidden' => true, ], ); if ($this->application->docker_registry_image_tag) { @@ -707,10 +761,14 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue $this->application_deployment_queue->addLogEntry("Tagging and pushing image with {$this->application->docker_registry_image_tag} tag."); $this->execute_remote_command( [ - executeInDocker($this->deployment_uuid, "docker tag {$this->production_image_name} {$this->application->docker_registry_image_name}:{$this->application->docker_registry_image_tag}"), 'ignore_errors' => true, 'hidden' => true, + executeInDocker($this->deployment_uuid, "docker tag {$this->production_image_name} {$this->application->docker_registry_image_name}:{$this->application->docker_registry_image_tag}"), + 'ignore_errors' => true, + 'hidden' => true, ], [ - executeInDocker($this->deployment_uuid, "docker push {$this->application->docker_registry_image_name}:{$this->application->docker_registry_image_tag}"), 'ignore_errors' => true, 'hidden' => true, + executeInDocker($this->deployment_uuid, "docker push {$this->application->docker_registry_image_name}:{$this->application->docker_registry_image_tag}"), + 'ignore_errors' => true, + 'hidden' => true, ], ); } @@ -807,14 +865,20 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue private function check_image_locally_or_remotely() { $this->execute_remote_command([ - "docker images -q {$this->production_image_name} 2>/dev/null", 'hidden' => true, 'save' => 'local_image_found', + "docker images -q {$this->production_image_name} 2>/dev/null", + 'hidden' => true, + 'save' => 'local_image_found', ]); if (str($this->saved_outputs->get('local_image_found'))->isEmpty() && $this->application->docker_registry_image_name) { $this->execute_remote_command([ - "docker pull {$this->production_image_name} 2>/dev/null", 'ignore_errors' => true, 'hidden' => true, + "docker pull {$this->production_image_name} 2>/dev/null", + 'ignore_errors' => true, + 'hidden' => true, ]); $this->execute_remote_command([ - "docker images -q {$this->production_image_name} 2>/dev/null", 'hidden' => true, 'save' => 'local_image_found', + "docker images -q {$this->production_image_name} 2>/dev/null", + 'hidden' => true, + 'save' => 'local_image_found', ]); } } @@ -847,17 +911,24 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue } if ($this->application->environment_variables_preview->where('key', 'COOLIFY_FQDN')->isEmpty()) { $envs->push("COOLIFY_FQDN={$this->preview->fqdn}"); + $envs->push("COOLIFY_DOMAIN_URL={$this->preview->fqdn}"); } if ($this->application->environment_variables_preview->where('key', 'COOLIFY_URL')->isEmpty()) { $url = str($this->preview->fqdn)->replace('http://', '')->replace('https://', ''); $envs->push("COOLIFY_URL={$url}"); + $envs->push("COOLIFY_DOMAIN_FQDN={$url}"); } - if ($this->application->environment_variables_preview->where('key', 'COOLIFY_BRANCH')->isEmpty()) { - $envs->push("COOLIFY_BRANCH={$local_branch}"); - } - if ($this->application->environment_variables_preview->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) { - $envs->push("COOLIFY_CONTAINER_NAME={$this->container_name}"); + if ($this->application->build_pack !== 'dockercompose' || $this->application->compose_parsing_version === '1' || $this->application->compose_parsing_version === '2') { + if ($this->application->environment_variables_preview->where('key', 'COOLIFY_BRANCH')->isEmpty()) { + $envs->push("COOLIFY_BRANCH=\"{$local_branch}\""); + } + if ($this->application->environment_variables_preview->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) { + $envs->push("COOLIFY_CONTAINER_NAME=\"{$this->container_name}\""); + } } + + add_coolify_default_environment_variables($this->application, $envs, $this->application->environment_variables_preview); + foreach ($sorted_environment_variables_preview as $env) { $real_value = $env->real_value; if ($env->version === '4.0.0-beta.239') { @@ -892,19 +963,31 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue } } if ($this->application->environment_variables->where('key', 'COOLIFY_FQDN')->isEmpty()) { - $envs->push("COOLIFY_FQDN={$this->application->fqdn}"); + if ($this->application->compose_parsing_version === '3') { + $envs->push("COOLIFY_URL={$this->application->fqdn}"); + } else { + $envs->push("COOLIFY_FQDN={$this->application->fqdn}"); + } } if ($this->application->environment_variables->where('key', 'COOLIFY_URL')->isEmpty()) { - $url = str_replace('http://', '', $this->application->fqdn); - $url = str_replace('https://', '', $url); - $envs->push("COOLIFY_URL={$url}"); + $url = str($this->application->fqdn)->replace('http://', '')->replace('https://', ''); + if ($this->application->compose_parsing_version === '3') { + $envs->push("COOLIFY_FQDN={$url}"); + } else { + $envs->push("COOLIFY_URL={$url}"); + } } - if ($this->application->environment_variables->where('key', 'COOLIFY_BRANCH')->isEmpty()) { - $envs->push("COOLIFY_BRANCH={$local_branch}"); - } - if ($this->application->environment_variables->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) { - $envs->push("COOLIFY_CONTAINER_NAME={$this->container_name}"); + if ($this->application->build_pack !== 'dockercompose' || $this->application->compose_parsing_version === '1' || $this->application->compose_parsing_version === '2') { + if ($this->application->environment_variables->where('key', 'COOLIFY_BRANCH')->isEmpty()) { + $envs->push("COOLIFY_BRANCH=\"{$local_branch}\""); + } + if ($this->application->environment_variables->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) { + $envs->push("COOLIFY_CONTAINER_NAME=\"{$this->container_name}\""); + } } + + add_coolify_default_environment_variables($this->application, $envs, $this->application->environment_variables); + foreach ($sorted_environment_variables as $env) { $real_value = $env->real_value; if ($env->version === '4.0.0-beta.239') { @@ -981,17 +1064,58 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue ); } } + $this->environment_variables = $envs; + } + + private function elixir_finetunes() + { + if ($this->pull_request_id === 0) { + $envType = 'environment_variables'; + } else { + $envType = 'environment_variables_preview'; + } + $mix_env = $this->application->{$envType}->where('key', 'MIX_ENV')->first(); + if ($mix_env) { + if ($mix_env->is_build_time === false) { + $this->application_deployment_queue->addLogEntry('MIX_ENV environment variable is not set as build time.', type: 'error'); + $this->application_deployment_queue->addLogEntry('Please set MIX_ENV environment variable to be build time variable if you facing any issues with the deployment.', type: 'error'); + } + } else { + $this->application_deployment_queue->addLogEntry('MIX_ENV environment variable not found.', type: 'error'); + $this->application_deployment_queue->addLogEntry('Please add MIX_ENV environment variable and set it to be build time variable if you facing any issues with the deployment.', type: 'error'); + } + $secret_key_base = $this->application->{$envType}->where('key', 'SECRET_KEY_BASE')->first(); + if ($secret_key_base) { + if ($secret_key_base->is_build_time === false) { + $this->application_deployment_queue->addLogEntry('SECRET_KEY_BASE environment variable is not set as build time.', type: 'error'); + $this->application_deployment_queue->addLogEntry('Please set SECRET_KEY_BASE environment variable to be build time variable if you facing any issues with the deployment.', type: 'error'); + } + } else { + $this->application_deployment_queue->addLogEntry('SECRET_KEY_BASE environment variable not found.', type: 'error'); + $this->application_deployment_queue->addLogEntry('Please add SECRET_KEY_BASE environment variable and set it to be build time variable if you facing any issues with the deployment.', type: 'error'); + } + $database_url = $this->application->{$envType}->where('key', 'DATABASE_URL')->first(); + if ($database_url) { + if ($database_url->is_build_time === false) { + $this->application_deployment_queue->addLogEntry('DATABASE_URL environment variable is not set as build time.', type: 'error'); + $this->application_deployment_queue->addLogEntry('Please set DATABASE_URL environment variable to be build time variable if you facing any issues with the deployment.', type: 'error'); + } + } else { + $this->application_deployment_queue->addLogEntry('DATABASE_URL environment variable not found.', type: 'error'); + $this->application_deployment_queue->addLogEntry('Please add DATABASE_URL environment variable and set it to be build time variable if you facing any issues with the deployment.', type: 'error'); + } } private function laravel_finetunes() { if ($this->pull_request_id === 0) { - $nixpacks_php_fallback_path = $this->application->environment_variables->where('key', 'NIXPACKS_PHP_FALLBACK_PATH')->first(); - $nixpacks_php_root_dir = $this->application->environment_variables->where('key', 'NIXPACKS_PHP_ROOT_DIR')->first(); + $envType = 'environment_variables'; } else { - $nixpacks_php_fallback_path = $this->application->environment_variables_preview->where('key', 'NIXPACKS_PHP_FALLBACK_PATH')->first(); - $nixpacks_php_root_dir = $this->application->environment_variables_preview->where('key', 'NIXPACKS_PHP_ROOT_DIR')->first(); + $envType = 'environment_variables_preview'; } + $nixpacks_php_fallback_path = $this->application->{$envType}->where('key', 'NIXPACKS_PHP_FALLBACK_PATH')->first(); + $nixpacks_php_root_dir = $this->application->{$envType}->where('key', 'NIXPACKS_PHP_ROOT_DIR')->first(); + if (! $nixpacks_php_fallback_path) { $nixpacks_php_fallback_path = new EnvironmentVariable; $nixpacks_php_fallback_path->key = 'NIXPACKS_PHP_FALLBACK_PATH'; @@ -1211,7 +1335,9 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue private function prepare_builder_image() { + $settings = InstanceSettings::get(); $helperImage = config('coolify.helper_image'); + $helperImage = "{$helperImage}:{$settings->helper_version}"; // Get user home directory $this->serverUserHomeDir = instant_remote_process(['echo $HOME'], $this->server); $this->dockerConfigFileExists = instant_remote_process(["test -f {$this->serverUserHomeDir}/.docker/config.json && echo 'OK' || echo 'NOK'"], $this->server); @@ -1363,7 +1489,8 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue } $this->execute_remote_command( [ - $importCommands, 'hidden' => true, + $importCommands, + 'hidden' => true, ] ); $this->create_workdir(); @@ -1447,6 +1574,9 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue data_set($parsed, 'variables.NIXPACKS_PHP_FALLBACK_PATH', $variables[0]->value); data_set($parsed, 'variables.NIXPACKS_PHP_ROOT_DIR', $variables[1]->value); } + if ($this->nixpacks_type === 'elixir') { + $this->elixir_finetunes(); + } $this->nixpacks_plan = json_encode($parsed, JSON_PRETTY_PRINT); $this->application_deployment_queue->addLogEntry("Final Nixpacks plan: {$this->nixpacks_plan}", hidden: true); if ($this->nixpacks_type === 'rust') { @@ -1573,7 +1703,10 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue // Check for custom HEALTHCHECK if ($this->application->build_pack === 'dockerfile' || $this->application->dockerfile) { $this->execute_remote_command([ - executeInDocker($this->deployment_uuid, "cat {$this->workdir}{$this->dockerfile_location}"), 'hidden' => true, 'save' => 'dockerfile_from_repo', 'ignore_errors' => true, + executeInDocker($this->deployment_uuid, "cat {$this->workdir}{$this->dockerfile_location}"), + 'hidden' => true, + 'save' => 'dockerfile_from_repo', + 'ignore_errors' => true, ]); $dockerfile = collect(str($this->saved_outputs->get('dockerfile_from_repo'))->trim()->explode("\n")); $this->application->parseHealthcheckFromDockerfile($dockerfile); @@ -1676,14 +1809,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue $docker_compose['services'][$this->container_name]['labels'] = $labels; } if ($this->server->isLogDrainEnabled() && $this->application->isLogDrainEnabled()) { - $docker_compose['services'][$this->container_name]['logging'] = [ - 'driver' => 'fluentd', - 'options' => [ - 'fluentd-address' => 'tcp://127.0.0.1:24224', - 'fluentd-async' => 'true', - 'fluentd-sub-second-precision' => 'true', - ], - ]; + $docker_compose['services'][$this->container_name]['logging'] = generate_fluentd_configuration(); } if ($this->application->settings->is_gpu_enabled) { $docker_compose['services'][$this->container_name]['deploy']['resources']['reservations']['devices'] = [ @@ -1710,13 +1836,20 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue if (count($this->application->ports_mappings_array) > 0 && $this->pull_request_id === 0) { $docker_compose['services'][$this->container_name]['ports'] = $this->application->ports_mappings_array; } + if (count($persistent_storages) > 0) { - $docker_compose['services'][$this->container_name]['volumes'] = $persistent_storages; + if (! data_get($docker_compose, 'services.'.$this->container_name.'.volumes')) { + $docker_compose['services'][$this->container_name]['volumes'] = []; + } + $docker_compose['services'][$this->container_name]['volumes'] = array_merge($docker_compose['services'][$this->container_name]['volumes'], $persistent_storages); } if (count($persistent_file_volumes) > 0) { - $docker_compose['services'][$this->container_name]['volumes'] = $persistent_file_volumes->map(function ($item) { + if (! data_get($docker_compose, 'services.'.$this->container_name.'.volumes')) { + $docker_compose['services'][$this->container_name]['volumes'] = []; + } + $docker_compose['services'][$this->container_name]['volumes'] = array_merge($docker_compose['services'][$this->container_name]['volumes'], $persistent_file_volumes->map(function ($item) { return "$item->fs_path:$item->mount_path"; - })->toArray(); + })->toArray()); } if (count($volume_names) > 0) { $docker_compose['volumes'] = $volume_names; @@ -1839,13 +1972,23 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue $this->application_deployment_queue->addLogEntry("Pulling latest image ($image) from the registry."); $this->execute_remote_command( [ - executeInDocker($this->deployment_uuid, "docker pull {$image}"), 'hidden' => true, + executeInDocker($this->deployment_uuid, "docker pull {$image}"), + 'hidden' => true, ] ); } private function build_image() { + // Add Coolify related variables to the build args + $this->environment_variables->filter(function ($key, $value) { + return str($key)->startsWith('COOLIFY_'); + })->each(function ($key, $value) { + $this->build_args->push("--build-arg '{$key}'"); + }); + + $this->build_args = $this->build_args->implode(' '); + $this->application_deployment_queue->addLogEntry('----------------------------------------'); if ($this->application->build_pack === 'static') { $this->application_deployment_queue->addLogEntry('Static deployment. Copying static assets to the image.'); @@ -1889,12 +2032,14 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf"); $this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->nixpacks_plan}' | base64 -d | tee /artifacts/thegameplan.json > /dev/null"), 'hidden' => true]); if ($this->force_rebuild) { $this->execute_remote_command([ - executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --no-cache --no-error-without-start -n {$this->build_image_name} {$this->workdir} -o {$this->workdir}"), 'hidden' => true, + executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --no-cache --no-error-without-start -n {$this->build_image_name} {$this->workdir} -o {$this->workdir}"), + 'hidden' => true, ]); $build_command = "docker build --no-cache {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile {$this->build_args} --progress plain -t {$this->build_image_name} {$this->workdir}"; } else { $this->execute_remote_command([ - executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --cache-key '{$this->application->uuid}' --no-error-without-start -n {$this->build_image_name} {$this->workdir} -o {$this->workdir}"), 'hidden' => true, + executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --cache-key '{$this->application->uuid}' --no-error-without-start -n {$this->build_image_name} {$this->workdir} -o {$this->workdir}"), + 'hidden' => true, ]); $build_command = "docker build {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile {$this->build_args} --progress plain -t {$this->build_image_name} {$this->workdir}"; } @@ -1902,10 +2047,16 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf"); $base64_build_command = base64_encode($build_command); $this->execute_remote_command( [ - executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"), 'hidden' => true, + executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"), + 'hidden' => true, ], [ - executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'), 'hidden' => true, + executeInDocker($this->deployment_uuid, 'cat /artifacts/build.sh'), + 'hidden' => true, + ], + [ + executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'), + 'hidden' => true, ] ); $this->execute_remote_command([executeInDocker($this->deployment_uuid, 'rm /artifacts/thegameplan.json'), 'hidden' => true]); @@ -1919,10 +2070,16 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf"); } $this->execute_remote_command( [ - executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"), 'hidden' => true, + executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"), + 'hidden' => true, ], [ - executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'), 'hidden' => true, + executeInDocker($this->deployment_uuid, 'cat /artifacts/build.sh'), + 'hidden' => true, + ], + [ + executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'), + 'hidden' => true, ] ); } @@ -1959,10 +2116,16 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf"); executeInDocker($this->deployment_uuid, "echo '{$nginx_config}' | base64 -d | tee {$this->workdir}/nginx.conf > /dev/null"), ], [ - executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"), 'hidden' => true, + executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"), + 'hidden' => true, ], [ - executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'), 'hidden' => true, + executeInDocker($this->deployment_uuid, 'cat /artifacts/build.sh'), + 'hidden' => true, + ], + [ + executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'), + 'hidden' => true, ] ); } else { @@ -1976,10 +2139,16 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf"); $base64_build_command = base64_encode($build_command); $this->execute_remote_command( [ - executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"), 'hidden' => true, + executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"), + 'hidden' => true, ], [ - executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'), 'hidden' => true, + executeInDocker($this->deployment_uuid, 'cat /artifacts/build.sh'), + 'hidden' => true, + ], + [ + executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'), + 'hidden' => true, ] ); } else { @@ -1988,22 +2157,30 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf"); $this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->nixpacks_plan}' | base64 -d | tee /artifacts/thegameplan.json > /dev/null"), 'hidden' => true]); if ($this->force_rebuild) { $this->execute_remote_command([ - executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --no-cache --no-error-without-start -n {$this->production_image_name} {$this->workdir} -o {$this->workdir}"), 'hidden' => true, + executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --no-cache --no-error-without-start -n {$this->production_image_name} {$this->workdir} -o {$this->workdir}"), + 'hidden' => true, ]); $build_command = "docker build --no-cache {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}"; } else { $this->execute_remote_command([ - executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --cache-key '{$this->application->uuid}' --no-error-without-start -n {$this->production_image_name} {$this->workdir} -o {$this->workdir}"), 'hidden' => true, + executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --cache-key '{$this->application->uuid}' --no-error-without-start -n {$this->production_image_name} {$this->workdir} -o {$this->workdir}"), + 'hidden' => true, ]); $build_command = "docker build {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}"; } $base64_build_command = base64_encode($build_command); $this->execute_remote_command( [ - executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"), 'hidden' => true, + executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"), + 'hidden' => true, ], [ - executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'), 'hidden' => true, + executeInDocker($this->deployment_uuid, 'cat /artifacts/build.sh'), + 'hidden' => true, + ], + [ + executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'), + 'hidden' => true, ] ); $this->execute_remote_command([executeInDocker($this->deployment_uuid, 'rm /artifacts/thegameplan.json'), 'hidden' => true]); @@ -2017,10 +2194,16 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf"); } $this->execute_remote_command( [ - executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"), 'hidden' => true, + executeInDocker($this->deployment_uuid, "echo '{$base64_build_command}' | base64 -d | tee /artifacts/build.sh > /dev/null"), + 'hidden' => true, ], [ - executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'), 'hidden' => true, + executeInDocker($this->deployment_uuid, 'cat /artifacts/build.sh'), + 'hidden' => true, + ], + [ + executeInDocker($this->deployment_uuid, 'bash /artifacts/build.sh'), + 'hidden' => true, ] ); } @@ -2135,15 +2318,14 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf"); $this->build_args->push("--build-arg {$env->key}={$value}"); } } - - $this->build_args = $this->build_args->implode(' '); - ray($this->build_args); } private function add_build_env_variables_to_dockerfile() { $this->execute_remote_command([ - executeInDocker($this->deployment_uuid, "cat {$this->workdir}{$this->dockerfile_location}"), 'hidden' => true, 'save' => 'dockerfile', + executeInDocker($this->deployment_uuid, "cat {$this->workdir}{$this->dockerfile_location}"), + 'hidden' => true, + 'save' => 'dockerfile', ]); $dockerfile = collect(str($this->saved_outputs->get('dockerfile'))->trim()->explode("\n")); if ($this->pull_request_id === 0) { @@ -2161,7 +2343,6 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf"); } else { $dockerfile->splice(1, 0, "ARG {$env->key}={$env->real_value}"); } - $dockerfile->splice(1, 0, "ARG {$env->key}={$env->real_value}"); } } $dockerfile_base64 = base64_encode($dockerfile->implode("\n")); @@ -2189,7 +2370,8 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf"); $exec = "docker exec {$containerName} {$cmd}"; $this->execute_remote_command( [ - 'command' => $exec, 'hidden' => true, + 'command' => $exec, + 'hidden' => true, ], ); @@ -2216,7 +2398,9 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf"); try { $this->execute_remote_command( [ - 'command' => $exec, 'hidden' => true, 'save' => 'post-deployment-command-output', + 'command' => $exec, + 'hidden' => true, + 'save' => 'post-deployment-command-output', ], ); } catch (Exception $e) { diff --git a/app/Jobs/ApplicationRestartJob.php b/app/Jobs/ApplicationRestartJob.php deleted file mode 100644 index 54c062197..000000000 --- a/app/Jobs/ApplicationRestartJob.php +++ /dev/null @@ -1,32 +0,0 @@ -applicationDeploymentQueueId = $applicationDeploymentQueueId; - } - - public function handle() - { - ray('Restarting application'); - } -} diff --git a/app/Jobs/CheckForUpdatesJob.php b/app/Jobs/CheckForUpdatesJob.php index 86b66fbfb..ddc264839 100644 --- a/app/Jobs/CheckForUpdatesJob.php +++ b/app/Jobs/CheckForUpdatesJob.php @@ -10,6 +10,7 @@ use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; use Illuminate\Support\Facades\Http; +use Illuminate\Support\Facades\File; class CheckForUpdatesJob implements ShouldBeEncrypted, ShouldQueue { @@ -25,12 +26,14 @@ class CheckForUpdatesJob implements ShouldBeEncrypted, ShouldQueue $response = Http::retry(3, 1000)->get('https://cdn.coollabs.io/coolify/versions.json'); if ($response->successful()) { $versions = $response->json(); + $latest_version = data_get($versions, 'coolify.v4.version'); $current_version = config('version'); if (version_compare($latest_version, $current_version, '>')) { // New version available $settings->update(['new_version_available' => true]); + File::put(base_path('versions.json'), json_encode($versions, JSON_PRETTY_PRINT)); } else { $settings->update(['new_version_available' => false]); } diff --git a/app/Jobs/CheckLogDrainContainerJob.php b/app/Jobs/CheckLogDrainContainerJob.php deleted file mode 100644 index 16ef85192..000000000 --- a/app/Jobs/CheckLogDrainContainerJob.php +++ /dev/null @@ -1,93 +0,0 @@ -server->id))->dontRelease()]; - } - - public function uniqueId(): int - { - return $this->server->id; - } - - public function healthcheck() - { - $status = instant_remote_process(["docker inspect --format='{{json .State.Status}}' coolify-log-drain"], $this->server, false); - if (str($status)->contains('running')) { - return true; - } else { - return false; - } - } - - public function handle() - { - // ray("checking log drain statuses for {$this->server->id}"); - try { - if (! $this->server->isFunctional()) { - return; - } - $containers = instant_remote_process(['docker container ls -q'], $this->server, false); - if (! $containers) { - return; - } - $containers = instant_remote_process(["docker container inspect $(docker container ls -q) --format '{{json .}}'"], $this->server); - $containers = format_docker_command_output_to_json($containers); - - $foundLogDrainContainer = $containers->filter(function ($value, $key) { - return data_get($value, 'Name') === '/coolify-log-drain'; - })->first(); - if (! $foundLogDrainContainer || ! $this->healthcheck()) { - ray('Log drain container not found or unhealthy. Restarting...'); - InstallLogDrain::run($this->server); - Sleep::for(10)->seconds(); - if ($this->healthcheck()) { - if ($this->server->log_drain_notification_sent) { - $this->server->team?->notify(new ContainerRestarted('Coolify Log Drainer', $this->server)); - $this->server->update(['log_drain_notification_sent' => false]); - } - - return; - } - if (! $this->server->log_drain_notification_sent) { - ray('Log drain container still unhealthy. Sending notification...'); - // $this->server->team?->notify(new ContainerStopped('Coolify Log Drainer', $this->server, null)); - $this->server->update(['log_drain_notification_sent' => true]); - } - } else { - if ($this->server->log_drain_notification_sent) { - $this->server->team?->notify(new ContainerRestarted('Coolify Log Drainer', $this->server)); - $this->server->update(['log_drain_notification_sent' => false]); - } - } - } catch (\Throwable $e) { - if (! isCloud()) { - send_internal_notification("CheckLogDrainContainerJob failed on ({$this->server->id}) with: ".$e->getMessage()); - } - ray($e->getMessage()); - - return handleError($e); - } - } -} diff --git a/app/Jobs/CleanupStaleMultiplexedConnections.php b/app/Jobs/CleanupStaleMultiplexedConnections.php new file mode 100644 index 000000000..bcca77c18 --- /dev/null +++ b/app/Jobs/CleanupStaleMultiplexedConnections.php @@ -0,0 +1,37 @@ +cleanupStaleConnection($server); + } + }); + } + + private function cleanupStaleConnection(Server $server) + { + $muxSocket = "/tmp/mux_{$server->id}"; + $checkCommand = "ssh -O check -o ControlPath=$muxSocket {$server->user}@{$server->ip} 2>/dev/null"; + $checkProcess = Process::run($checkCommand); + + if ($checkProcess->exitCode() !== 0) { + $closeCommand = "ssh -O exit -o ControlPath=$muxSocket {$server->user}@{$server->ip} 2>/dev/null"; + Process::run($closeCommand); + } + } +} diff --git a/app/Jobs/DatabaseBackupJob.php b/app/Jobs/DatabaseBackupJob.php index 79b00e9cd..3bd13564b 100644 --- a/app/Jobs/DatabaseBackupJob.php +++ b/app/Jobs/DatabaseBackupJob.php @@ -4,6 +4,7 @@ namespace App\Jobs; use App\Actions\Database\StopDatabase; use App\Events\BackupCreated; +use App\Models\InstanceSettings; use App\Models\S3Storage; use App\Models\ScheduledDatabaseBackup; use App\Models\ScheduledDatabaseBackupExecution; @@ -25,6 +26,7 @@ use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\Middleware\WithoutOverlapping; use Illuminate\Queue\SerializesModels; use Illuminate\Support\Str; +use Visus\Cuid2\Cuid2; class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue { @@ -56,6 +58,8 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue public ?string $backup_output = null; + public ?string $postgres_password = null; + public ?S3Storage $s3 = null; public function __construct($backup) @@ -89,8 +93,6 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue public function handle(): void { try { - BackupCreated::dispatch($this->team->id); - // Check if team is exists if (is_null($this->team)) { $this->backup->update(['status' => 'failed']); @@ -99,6 +101,9 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue return; } + + BackupCreated::dispatch($this->team->id); + $status = str(data_get($this->database, 'status')); if (! $status->startsWith('running') && $this->database->id !== 0) { ray('database not running'); @@ -134,6 +139,13 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue } else { $databasesToBackup = $this->database->postgres_user; } + $this->postgres_password = $envs->filter(function ($env) { + return str($env)->startsWith('POSTGRES_PASSWORD='); + })->first(); + if ($this->postgres_password) { + $this->postgres_password = str($this->postgres_password)->after('POSTGRES_PASSWORD=')->value(); + } + } elseif (str($databaseType)->contains('mysql')) { $this->container_name = "{$this->database->name}-$serviceUuid"; $this->directory_name = $serviceName.'-'.$this->container_name; @@ -336,7 +348,7 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue $url = $this->database->internal_db_url; if ($databaseWithCollections === 'all') { $commands[] = 'mkdir -p '.$this->backup_dir; - if (str($this->database->image)->startsWith('mongo:4.0')) { + if (str($this->database->image)->startsWith('mongo:4')) { $commands[] = "docker exec $this->container_name mongodump --uri=$url --gzip --archive > $this->backup_location"; } else { $commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=$url --gzip --archive > $this->backup_location"; @@ -351,13 +363,13 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue } $commands[] = 'mkdir -p '.$this->backup_dir; if ($collectionsToExclude->count() === 0) { - if (str($this->database->image)->startsWith('mongo:4.0')) { + if (str($this->database->image)->startsWith('mongo:4')) { $commands[] = "docker exec $this->container_name mongodump --uri=$url --gzip --archive > $this->backup_location"; } else { $commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=$url --db $databaseName --gzip --archive > $this->backup_location"; } } else { - if (str($this->database->image)->startsWith('mongo:4.0')) { + if (str($this->database->image)->startsWith('mongo:4')) { $commands[] = "docker exec $this->container_name mongodump --uri=$url --gzip --excludeCollection ".$collectionsToExclude->implode(' --excludeCollection ')." --archive > $this->backup_location"; } else { $commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=$url --db $databaseName --gzip --excludeCollection ".$collectionsToExclude->implode(' --excludeCollection ')." --archive > $this->backup_location"; @@ -381,7 +393,14 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue { try { $commands[] = 'mkdir -p '.$this->backup_dir; - $commands[] = "docker exec $this->container_name pg_dump --format=custom --no-acl --no-owner --username {$this->database->postgres_user} $database > $this->backup_location"; + $backupCommand = 'docker exec'; + if ($this->postgres_password) { + $backupCommand .= " -e PGPASSWORD=$this->postgres_password"; + } + $backupCommand .= " $this->container_name pg_dump --format=custom --no-acl --no-owner --username {$this->database->postgres_user} $database > $this->backup_location"; + + $commands[] = $backupCommand; + ray($commands); $this->backup_output = instant_remote_process($commands, $this->server); $this->backup_output = trim($this->backup_output); if ($this->backup_output === '') { @@ -452,7 +471,7 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue if ($this->backup->number_of_backups_locally === 0) { $deletable = $this->backup->executions()->where('status', 'success'); } else { - $deletable = $this->backup->executions()->where('status', 'success')->orderByDesc('created_at')->skip($this->backup->number_of_backups_locally - 1); + $deletable = $this->backup->executions()->where('status', 'success')->skip($this->backup->number_of_backups_locally - 1); } foreach ($deletable->get() as $execution) { delete_backup_locally($execution->filename, $this->server); @@ -460,6 +479,34 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue } } + // private function upload_to_s3(): void + // { + // try { + // if (is_null($this->s3)) { + // return; + // } + // $key = $this->s3->key; + // $secret = $this->s3->secret; + // // $region = $this->s3->region; + // $bucket = $this->s3->bucket; + // $endpoint = $this->s3->endpoint; + // $this->s3->testConnection(shouldSave: true); + // $configName = new Cuid2; + + // $s3_copy_dir = str($this->backup_location)->replace(backup_dir(), '/var/www/html/storage/app/backups/'); + // $commands[] = "docker exec coolify bash -c 'mc config host add {$configName} {$endpoint} $key $secret'"; + // $commands[] = "docker exec coolify bash -c 'mc cp $s3_copy_dir {$configName}/{$bucket}{$this->backup_dir}/'"; + // instant_remote_process($commands, $this->server); + // $this->add_to_backup_output('Uploaded to S3.'); + // } catch (\Throwable $e) { + // $this->add_to_backup_output($e->getMessage()); + // throw $e; + // } finally { + // $removeConfigCommands[] = "docker exec coolify bash -c 'mc config remove {$configName}'"; + // $removeConfigCommands[] = "docker exec coolify bash -c 'mc alias rm {$configName}'"; + // instant_remote_process($removeConfigCommands, $this->server, false); + // } + // } private function upload_to_s3(): void { try { @@ -477,12 +524,15 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue } else { $network = $this->database->destination->network; } - $commands[] = "docker run -d --network {$network} --name backup-of-{$this->backup->uuid} --rm -v $this->backup_location:$this->backup_location:ro ghcr.io/coollabsio/coolify-helper"; + + $this->ensureHelperImageAvailable(); + + $fullImageName = $this->getFullImageName(); + $commands[] = "docker run -d --network {$network} --name backup-of-{$this->backup->uuid} --rm -v $this->backup_location:$this->backup_location:ro {$fullImageName}"; $commands[] = "docker exec backup-of-{$this->backup->uuid} mc config host add temporary {$endpoint} $key $secret"; $commands[] = "docker exec backup-of-{$this->backup->uuid} mc cp $this->backup_location temporary/$bucket{$this->backup_dir}/"; instant_remote_process($commands, $this->server); $this->add_to_backup_output('Uploaded to S3.'); - ray('Uploaded to S3. '.$this->backup_location.' to s3://'.$bucket.$this->backup_dir); } catch (\Throwable $e) { $this->add_to_backup_output($e->getMessage()); throw $e; @@ -491,4 +541,42 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue instant_remote_process([$command], $this->server); } } + + private function ensureHelperImageAvailable(): void + { + $fullImageName = $this->getFullImageName(); + + $imageExists = $this->checkImageExists($fullImageName); + + if (! $imageExists) { + $this->pullHelperImage($fullImageName); + } + } + + private function checkImageExists(string $fullImageName): bool + { + $result = instant_remote_process(["docker image inspect {$fullImageName} >/dev/null 2>&1 && echo 'exists' || echo 'not exists'"], $this->server, false); + + return trim($result) === 'exists'; + } + + private function pullHelperImage(string $fullImageName): void + { + try { + instant_remote_process(["docker pull {$fullImageName}"], $this->server); + } catch (\Exception $e) { + $errorMessage = 'Failed to pull helper image: '.$e->getMessage(); + $this->add_to_backup_output($errorMessage); + throw new \RuntimeException($errorMessage); + } + } + + private function getFullImageName(): string + { + $settings = InstanceSettings::get(); + $helperImage = config('coolify.helper_image'); + $latestVersion = $settings->helper_version; + + return "{$helperImage}:{$latestVersion}"; + } } diff --git a/app/Jobs/DockerCleanupJob.php b/app/Jobs/DockerCleanupJob.php index 5010263ae..f95cd2920 100644 --- a/app/Jobs/DockerCleanupJob.php +++ b/app/Jobs/DockerCleanupJob.php @@ -10,6 +10,7 @@ use Illuminate\Contracts\Queue\ShouldBeEncrypted; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; +use Illuminate\Queue\Middleware\WithoutOverlapping; use Illuminate\Queue\SerializesModels; use Illuminate\Support\Facades\Log; @@ -17,21 +18,33 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; - public $timeout = 300; + public $timeout = 600; - public int|string|null $usageBefore = null; + public $tries = 1; + + public ?string $usageBefore = null; public function __construct(public Server $server) {} + public function middleware(): array + { + return [new WithoutOverlapping($this->server->id)]; + } + + public function uniqueId(): int + { + return $this->server->id; + } + public function handle(): void { try { if (! $this->server->isFunctional()) { return; } - if ($this->server->settings->is_force_cleanup_enabled) { + if ($this->server->settings->force_docker_cleanup) { Log::info('DockerCleanupJob force cleanup on '.$this->server->name); - CleanupDocker::run(server: $this->server, force: true); + CleanupDocker::run(server: $this->server); return; } @@ -39,12 +52,12 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue $this->usageBefore = $this->server->getDiskUsage(); if (str($this->usageBefore)->isEmpty() || $this->usageBefore === null || $this->usageBefore === 0) { Log::info('DockerCleanupJob force cleanup on '.$this->server->name); - CleanupDocker::run(server: $this->server, force: true); + CleanupDocker::run(server: $this->server); return; } - if ($this->usageBefore >= $this->server->settings->cleanup_after_percentage) { - CleanupDocker::run(server: $this->server, force: false); + if ($this->usageBefore >= $this->server->settings->docker_cleanup_threshold) { + CleanupDocker::run(server: $this->server); $usageAfter = $this->server->getDiskUsage(); if ($usageAfter < $this->usageBefore) { $this->server->team?->notify(new DockerCleanup($this->server, 'Saved '.($this->usageBefore - $usageAfter).'% disk space.')); @@ -56,7 +69,8 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue Log::info('No need to clean up '.$this->server->name); } } catch (\Throwable $e) { - ray($e->getMessage()); + CleanupDocker::run(server: $this->server); + Log::error('DockerCleanupJob failed: '.$e->getMessage()); throw $e; } } diff --git a/app/Jobs/InstanceAutoUpdateJob.php b/app/Jobs/InstanceAutoUpdateJob.php deleted file mode 100644 index 1bbfcf8cb..000000000 --- a/app/Jobs/InstanceAutoUpdateJob.php +++ /dev/null @@ -1,28 +0,0 @@ -get('https://cdn.coollabs.io/coolify/versions.json'); - if ($response->successful()) { - $versions = $response->json(); - File::put(base_path('versions.json'), json_encode($versions, JSON_PRETTY_PRINT)); - } - $latest_version = get_latest_version_of_coolify(); - instant_remote_process(["docker pull -q ghcr.io/coollabsio/coolify:{$latest_version}"], $server, false); - - $current_version = config('version'); - if (! $settings->is_auto_update_enabled) { - return; - } - if ($latest_version === $current_version) { - return; - } - if (version_compare($latest_version, $current_version, '<')) { - return; - } - } catch (\Throwable $e) { - throw $e; - } - } -} diff --git a/app/Jobs/PullHelperImageJob.php b/app/Jobs/PullHelperImageJob.php index 30a1b8026..63b7fa920 100644 --- a/app/Jobs/PullHelperImageJob.php +++ b/app/Jobs/PullHelperImageJob.php @@ -2,6 +2,7 @@ namespace App\Jobs; +use App\Models\InstanceSettings; use App\Models\Server; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldBeEncrypted; @@ -10,6 +11,7 @@ use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\Middleware\WithoutOverlapping; use Illuminate\Queue\SerializesModels; +use Illuminate\Support\Facades\Http; class PullHelperImageJob implements ShouldBeEncrypted, ShouldQueue { @@ -32,10 +34,20 @@ class PullHelperImageJob implements ShouldBeEncrypted, ShouldQueue public function handle(): void { try { - $helperImage = config('coolify.helper_image'); - ray("Pulling {$helperImage}"); - instant_remote_process(["docker pull -q {$helperImage}"], $this->server, false); - ray('PullHelperImageJob done'); + $response = Http::retry(3, 1000)->get('https://cdn.coollabs.io/coolify/versions.json'); + if ($response->successful()) { + $versions = $response->json(); + $settings = InstanceSettings::get(); + $latest_version = data_get($versions, 'coolify.helper.version'); + $current_version = $settings->helper_version; + if (version_compare($latest_version, $current_version, '>')) { + // New version available + // $helperImage = config('coolify.helper_image'); + // instant_remote_process(["docker pull -q {$helperImage}:{$latest_version}"], $this->server); + $settings->update(['helper_version' => $latest_version]); + } + } + } catch (\Throwable $e) { send_internal_notification('PullHelperImageJob failed with: '.$e->getMessage()); ray($e->getMessage()); diff --git a/app/Jobs/ScheduledTaskJob.php b/app/Jobs/ScheduledTaskJob.php index 819e28f89..93d5fca70 100644 --- a/app/Jobs/ScheduledTaskJob.php +++ b/app/Jobs/ScheduledTaskJob.php @@ -36,6 +36,8 @@ class ScheduledTaskJob implements ShouldQueue public array $containers = []; + public string $server_timezone; + public function __construct($task) { $this->task = $task; @@ -47,6 +49,19 @@ class ScheduledTaskJob implements ShouldQueue throw new \RuntimeException('ScheduledTaskJob failed: No resource found.'); } $this->team = Team::find($task->team_id); + $this->server_timezone = $this->getServerTimezone(); + } + + private function getServerTimezone(): string + { + if ($this->resource instanceof Application) { + $timezone = $this->resource->destination->server->settings->server_timezone; + return $timezone; + } elseif ($this->resource instanceof Service) { + $timezone = $this->resource->server->settings->server_timezone; + return $timezone; + } + return 'UTC'; } public function middleware(): array @@ -61,6 +76,7 @@ class ScheduledTaskJob implements ShouldQueue public function handle(): void { + try { $this->task_log = ScheduledTaskExecution::create([ 'scheduled_task_id' => $this->task->id, @@ -78,12 +94,12 @@ class ScheduledTaskJob implements ShouldQueue } elseif ($this->resource->type() == 'service') { $this->resource->applications()->get()->each(function ($application) { if (str(data_get($application, 'status'))->contains('running')) { - $this->containers[] = data_get($application, 'name').'-'.data_get($this->resource, 'uuid'); + $this->containers[] = data_get($application, 'name') . '-' . data_get($this->resource, 'uuid'); } }); $this->resource->databases()->get()->each(function ($database) { if (str(data_get($database, 'status'))->contains('running')) { - $this->containers[] = data_get($database, 'name').'-'.data_get($this->resource, 'uuid'); + $this->containers[] = data_get($database, 'name') . '-' . data_get($this->resource, 'uuid'); } }); } @@ -96,8 +112,8 @@ class ScheduledTaskJob implements ShouldQueue } foreach ($this->containers as $containerName) { - if (count($this->containers) == 1 || str_starts_with($containerName, $this->task->container.'-'.$this->resource->uuid)) { - $cmd = "sh -c '".str_replace("'", "'\''", $this->task->command)."'"; + if (count($this->containers) == 1 || str_starts_with($containerName, $this->task->container . '-' . $this->resource->uuid)) { + $cmd = "sh -c '" . str_replace("'", "'\''", $this->task->command) . "'"; $exec = "docker exec {$containerName} {$cmd}"; $this->task_output = instant_remote_process([$exec], $this->server, true); $this->task_log->update([ @@ -121,6 +137,7 @@ class ScheduledTaskJob implements ShouldQueue $this->team?->notify(new TaskFailed($this->task, $e->getMessage())); // send_internal_notification('ScheduledTaskJob failed with: ' . $e->getMessage()); throw $e; + } finally { } } } diff --git a/app/Jobs/ServerCheckJob.php b/app/Jobs/ServerCheckJob.php index 1db15cfd4..540085385 100644 --- a/app/Jobs/ServerCheckJob.php +++ b/app/Jobs/ServerCheckJob.php @@ -26,6 +26,8 @@ class ServerCheckJob implements ShouldBeEncrypted, ShouldQueue public $tries = 3; + public $timeout = 60; + public $containers; public $applications; @@ -43,15 +45,15 @@ class ServerCheckJob implements ShouldBeEncrypted, ShouldQueue public function __construct(public Server $server) {} - // public function middleware(): array - // { - // return [(new WithoutOverlapping($this->server->uuid))]; - // } + public function middleware(): array + { + return [(new WithoutOverlapping($this->server->id))]; + } - // public function uniqueId(): int - // { - // return $this->server->uuid; - // } + public function uniqueId(): int + { + return $this->server->id; + } public function handle() { @@ -79,7 +81,6 @@ class ServerCheckJob implements ShouldBeEncrypted, ShouldQueue } GetContainersStatus::run($this->server, $this->containers, $containerReplicates); $this->checkLogDrainContainer(); - $this->checkSentinel(); } } catch (\Throwable $e) { @@ -90,21 +91,6 @@ class ServerCheckJob implements ShouldBeEncrypted, ShouldQueue } - private function checkSentinel() - { - if ($this->server->isSentinelEnabled()) { - $sentinelContainerFound = $this->containers->filter(function ($value, $key) { - return data_get($value, 'Name') === '/coolify-sentinel'; - })->first(); - if ($sentinelContainerFound) { - $status = data_get($sentinelContainerFound, 'State.Status'); - if ($status !== 'running') { - PullSentinelImageJob::dispatch($this); - } - } - } - } - private function serverStatus() { ['uptime' => $uptime] = $this->server->validateConnection(); @@ -140,6 +126,9 @@ class ServerCheckJob implements ShouldBeEncrypted, ShouldQueue private function checkLogDrainContainer() { + if (! $this->server->isLogDrainEnabled()) { + return; + } $foundLogDrainContainer = $this->containers->filter(function ($value, $key) { return data_get($value, 'Name') === '/coolify-log-drain'; })->first(); diff --git a/app/Livewire/Boarding/Index.php b/app/Livewire/Boarding/Index.php index 147a1ad6f..af05ad767 100644 --- a/app/Livewire/Boarding/Index.php +++ b/app/Livewire/Boarding/Index.php @@ -73,6 +73,8 @@ class Index extends Component } $this->privateKeyName = generate_random_name(); $this->remoteServerName = generate_random_name(); + $this->remoteServerPort = $this->remoteServerPort; + $this->remoteServerUser = $this->remoteServerUser; if (isDev()) { $this->privateKey = '-----BEGIN OPENSSH PRIVATE KEY----- b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW @@ -154,6 +156,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA== $this->servers = Server::ownedByCurrentTeam(['name'])->where('id', '!=', 0)->get(); if ($this->servers->count() > 0) { $this->selectedExistingServer = $this->servers->first()->id; + $this->updateServerDetails(); $this->currentState = 'select-existing-server'; return; @@ -173,9 +176,18 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA== } $this->selectedExistingPrivateKey = $this->createdServer->privateKey->id; $this->serverPublicKey = $this->createdServer->privateKey->publicKey(); + $this->updateServerDetails(); $this->currentState = 'validate-server'; } + private function updateServerDetails() + { + if ($this->createdServer) { + $this->remoteServerPort = $this->createdServer->port; + $this->remoteServerUser = $this->createdServer->user; + } + } + public function getProxyType() { // Set Default Proxy Type @@ -235,11 +247,12 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA== public function saveServer() { $this->validate([ - 'remoteServerName' => 'required', - 'remoteServerHost' => 'required', + 'remoteServerName' => 'required|string', + 'remoteServerHost' => 'required|string', 'remoteServerPort' => 'required|integer', - 'remoteServerUser' => 'required', + 'remoteServerUser' => 'required|string', ]); + $this->privateKey = formatPrivateKey($this->privateKey); $foundServer = Server::whereIp($this->remoteServerHost)->first(); if ($foundServer) { @@ -269,7 +282,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA== public function validateServer() { try { - config()->set('coolify.mux_enabled', false); + config()->set('constants.ssh.mux_enabled', false); // EC2 does not have `uptime` command, lol instant_remote_process(['ls /'], $this->createdServer, true); @@ -277,9 +290,12 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA== $this->createdServer->settings()->update([ 'is_reachable' => true, ]); + $this->serverReachable = true; } catch (\Throwable $e) { $this->serverReachable = false; - $this->createdServer->delete(); + $this->createdServer->settings()->update([ + 'is_reachable' => false, + ]); return handleError(error: $e, livewire: $this); } @@ -296,6 +312,10 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA== ]); $this->getProxyType(); } catch (\Throwable $e) { + $this->createdServer->settings()->update([ + 'is_usable' => false, + ]); + return handleError(error: $e, livewire: $this); } } @@ -349,6 +369,21 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA== ); } + public function saveAndValidateServer() + { + $this->validate([ + 'remoteServerPort' => 'required|integer|min:1|max:65535', + 'remoteServerUser' => 'required|string', + ]); + + $this->createdServer->update([ + 'port' => $this->remoteServerPort, + 'user' => $this->remoteServerUser, + 'timezone' => 'UTC', + ]); + $this->validateServer(); + } + private function createNewPrivateKey() { $this->privateKeyName = generate_random_name(); diff --git a/app/Livewire/CommandCenter/Index.php b/app/Livewire/CommandCenter/Index.php deleted file mode 100644 index 0a05e811f..000000000 --- a/app/Livewire/CommandCenter/Index.php +++ /dev/null @@ -1,21 +0,0 @@ -servers = Server::isReachable()->get(); - } - - public function render() - { - return view('livewire.command-center.index'); - } -} diff --git a/app/Livewire/Dashboard.php b/app/Livewire/Dashboard.php index 215d3b16c..1f0b68dd3 100644 --- a/app/Livewire/Dashboard.php +++ b/app/Livewire/Dashboard.php @@ -49,15 +49,6 @@ class Dashboard extends Component ])->sortBy('id')->groupBy('server_name')->toArray(); } - // public function getIptables() - // { - // $servers = Server::ownedByCurrentTeam()->get(); - // foreach ($servers as $server) { - // checkRequiredCommands($server); - // $iptables = instant_remote_process(['docker run --privileged --net=host --pid=host --ipc=host --volume /:/host busybox chroot /host bash -c "iptables -L -n | jc --iptables"'], $server); - // ray($iptables); - // } - // } public function render() { return view('livewire.dashboard'); diff --git a/app/Livewire/Project/Application/Advanced.php b/app/Livewire/Project/Application/Advanced.php index cde3322db..a3a688f7c 100644 --- a/app/Livewire/Project/Application/Advanced.php +++ b/app/Livewire/Project/Application/Advanced.php @@ -66,9 +66,9 @@ class Advanced extends Component $this->dispatch('resetDefaultLabels', false); } if ($this->application->settings->is_raw_compose_deployment_enabled) { - $this->application->parseRawCompose(); + $this->application->oldRawParser(); } else { - $this->application->parseCompose(); + $this->application->parse(); } $this->application->settings->save(); $this->dispatch('success', 'Settings saved.'); @@ -96,6 +96,12 @@ class Advanced extends Component } else { $this->application->settings->custom_internal_name = null; } + if (is_null($this->application->settings->custom_internal_name)) { + $this->application->settings->save(); + $this->dispatch('success', 'Custom name saved.'); + + return; + } $customInternalName = $this->application->settings->custom_internal_name; $server = $this->application->destination->server; $allApplications = $server->applications(); diff --git a/app/Livewire/Project/Application/Deployment/Show.php b/app/Livewire/Project/Application/Deployment/Show.php index 84a24255c..f2968f6d9 100644 --- a/app/Livewire/Project/Application/Deployment/Show.php +++ b/app/Livewire/Project/Application/Deployment/Show.php @@ -4,6 +4,7 @@ namespace App\Livewire\Project\Application\Deployment; use App\Models\Application; use App\Models\ApplicationDeploymentQueue; +use Illuminate\Support\Collection; use Livewire\Component; class Show extends Component @@ -69,6 +70,20 @@ class Show extends Component } } + public function getLogLinesProperty() + { + return decode_remote_command_output($this->application_deployment_queue)->map(function ($logLine) { + $logLine['line'] = e($logLine['line']); + $logLine['line'] = preg_replace( + '/(https?:\/\/[^\s]+)/', + '$1', + $logLine['line'], + ); + + return $logLine; + }); + } + public function render() { return view('livewire.project.application.deployment.show'); diff --git a/app/Livewire/Project/Application/DeploymentNavbar.php b/app/Livewire/Project/Application/DeploymentNavbar.php index b3e39d23d..5fccce792 100644 --- a/app/Livewire/Project/Application/DeploymentNavbar.php +++ b/app/Livewire/Project/Application/DeploymentNavbar.php @@ -55,9 +55,14 @@ class DeploymentNavbar extends Component public function cancel() { $kill_command = "docker rm -f {$this->application_deployment_queue->deployment_uuid}"; + $build_server_id = $this->application_deployment_queue->build_server_id; $server_id = $this->application_deployment_queue->server_id ?? $this->application->destination->server_id; try { - $server = Server::find($server_id); + if ($this->application->settings->is_build_server_enabled) { + $server = Server::find($build_server_id); + } else { + $server = Server::find($server_id); + } if ($this->application_deployment_queue->logs) { $previous_logs = json_decode($this->application_deployment_queue->logs, associative: true, flags: JSON_THROW_ON_ERROR); diff --git a/app/Livewire/Project/Application/General.php b/app/Livewire/Project/Application/General.php index 77593bf0a..d2700f444 100644 --- a/app/Livewire/Project/Application/General.php +++ b/app/Livewire/Project/Application/General.php @@ -3,7 +3,6 @@ namespace App\Livewire\Project\Application; use App\Models\Application; -use App\Models\LocalFileVolume; use Illuminate\Support\Collection; use Livewire\Component; use Visus\Cuid2\Cuid2; @@ -30,6 +29,8 @@ class General extends Component public ?string $ports_exposes = null; + public bool $is_preserve_repository_enabled = false; + public bool $is_container_label_escape_enabled = true; public $customLabels; @@ -130,7 +131,7 @@ class General extends Component public function mount() { try { - $this->parsedServices = $this->application->parseCompose(); + $this->parsedServices = $this->application->parse(); if (is_null($this->parsedServices) || empty($this->parsedServices)) { $this->dispatch('error', 'Failed to parse your docker-compose file. Please check the syntax and try again.'); @@ -145,6 +146,7 @@ class General extends Component } $this->parsedServiceDomains = $this->application->docker_compose_domains ? json_decode($this->application->docker_compose_domains, true) : []; $this->ports_exposes = $this->application->ports_exposes; + $this->is_preserve_repository_enabled = $this->application->settings->is_preserve_repository_enabled; $this->is_container_label_escape_enabled = $this->application->settings->is_container_label_escape_enabled; $this->customLabels = $this->application->parseContainerLabels(); if (! $this->customLabels && $this->application->destination->server->proxyType() !== 'NONE' && ! $this->application->settings->is_container_label_readonly_enabled) { @@ -168,9 +170,21 @@ class General extends Component $this->application->settings->save(); $this->dispatch('success', 'Settings saved.'); $this->application->refresh(); + + // If port_exposes changed, reset default labels if ($this->ports_exposes !== $this->application->ports_exposes || $this->is_container_label_escape_enabled !== $this->application->settings->is_container_label_escape_enabled) { $this->resetDefaultLabels(false); } + if ($this->is_preserve_repository_enabled !== $this->application->settings->is_preserve_repository_enabled) { + if ($this->application->settings->is_preserve_repository_enabled === false) { + $this->application->fileStorages->each(function ($storage) { + $storage->is_based_on_git = $this->application->settings->is_preserve_repository_enabled; + $storage->save(); + }); + } + + } + } public function loadComposeFile($isInit = false) @@ -179,39 +193,18 @@ class General extends Component if ($isInit && $this->application->docker_compose_raw) { return; } + + // Must reload the application to get the latest database changes + // Why? Not sure, but it works. + // $this->application->refresh(); + ['parsedServices' => $this->parsedServices, 'initialDockerComposeLocation' => $this->initialDockerComposeLocation] = $this->application->loadComposeFile($isInit); if (is_null($this->parsedServices)) { $this->dispatch('error', 'Failed to parse your docker-compose file. Please check the syntax and try again.'); return; } - $compose = $this->application->parseCompose(); - $services = data_get($compose, 'services'); - if ($services) { - $volumes = collect($services)->map(function ($service) { - return data_get($service, 'volumes'); - })->flatten()->filter(function ($volume) { - return str($volume)->startsWith('/data/coolify'); - })->unique()->values(); - foreach ($volumes as $volume) { - $source = str($volume)->before(':'); - $target = str($volume)->after(':')->beforeLast(':'); - - LocalFileVolume::updateOrCreate( - [ - 'mount_path' => $target, - 'resource_id' => $this->application->id, - 'resource_type' => get_class($this->application), - ], - [ - 'fs_path' => $source, - 'mount_path' => $target, - 'resource_id' => $this->application->id, - 'resource_type' => get_class($this->application), - ] - ); - } - } + $this->application->parse(); $this->dispatch('success', 'Docker compose file loaded.'); $this->dispatch('compose_loaded'); $this->dispatch('refreshStorages'); diff --git a/app/Livewire/Project/Application/Previews.php b/app/Livewire/Project/Application/Previews.php index 78fab7e44..397e159ad 100644 --- a/app/Livewire/Project/Application/Previews.php +++ b/app/Livewire/Project/Application/Previews.php @@ -81,8 +81,15 @@ class Previews extends Component return; } - $fqdn = generateFqdn($this->application->destination->server, $this->application->uuid); + if ($this->application->build_pack === 'dockercompose') { + $preview->generate_preview_fqdn_compose(); + $this->application->refresh(); + $this->dispatch('success', 'Domain generated.'); + return; + } + + $fqdn = generateFqdn($this->application->destination->server, $this->application->uuid); $url = Url::fromString($fqdn); $template = $this->application->preview_url_template; $host = $url->getHost(); diff --git a/app/Livewire/Project/Database/BackupExecutions.php b/app/Livewire/Project/Database/BackupExecutions.php index b6827895f..e16397652 100644 --- a/app/Livewire/Project/Database/BackupExecutions.php +++ b/app/Livewire/Project/Database/BackupExecutions.php @@ -11,9 +11,8 @@ use Livewire\Attributes\On; class BackupExecutions extends Component { public ?ScheduledDatabaseBackup $backup = null; - + public $database; public $executions = []; - public $setDeletableBackup; public $delete_backup_s3 = true; @@ -79,10 +78,56 @@ class BackupExecutions extends Component public function refreshBackupExecutions(): void { if ($this->backup) { - $this->executions = $this->backup->executions()->get()->sortBy('created_at'); + $this->executions = $this->backup->executions()->get(); } } + public function mount(ScheduledDatabaseBackup $backup) + { + $this->backup = $backup; + $this->database = $backup->database; + $this->refreshBackupExecutions(); + } + + public function server() + { + if ($this->database) { + $server = null; + + if ($this->database instanceof \App\Models\ServiceDatabase) { + $server = $this->database->service->destination->server; + } elseif ($this->database->destination && $this->database->destination->server) { + $server = $this->database->destination->server; + } + if ($server) { + return $server; + } + } + return null; + } + + public function getServerTimezone() + { + $server = $this->server(); + if (!$server) { + return 'UTC'; + } + $serverTimezone = $server->settings->server_timezone; + return $serverTimezone; + } + + public function formatDateInServerTimezone($date) + { + $serverTimezone = $this->getServerTimezone(); + $dateObj = new \DateTime($date); + try { + $dateObj->setTimezone(new \DateTimeZone($serverTimezone)); + } catch (\Exception $e) { + $dateObj->setTimezone(new \DateTimeZone('UTC')); + } + return $dateObj->format('Y-m-d H:i:s T'); + } + public function render() { return view('livewire.project.database.backup-executions', [ diff --git a/app/Livewire/Project/Database/Clickhouse/General.php b/app/Livewire/Project/Database/Clickhouse/General.php index ffdbe95c3..a6e2a1320 100644 --- a/app/Livewire/Project/Database/Clickhouse/General.php +++ b/app/Livewire/Project/Database/Clickhouse/General.php @@ -31,6 +31,7 @@ class General extends Component 'database.is_public' => 'nullable|boolean', 'database.public_port' => 'nullable|integer', 'database.is_log_drain_enabled' => 'nullable|boolean', + 'database.custom_docker_run_options' => 'nullable', ]; protected $validationAttributes = [ @@ -42,6 +43,7 @@ class General extends Component 'database.ports_mappings' => 'Port Mapping', 'database.is_public' => 'Is Public', 'database.public_port' => 'Public Port', + 'database.custom_docker_run_options' => 'Custom Docker Run Options', ]; public function mount() @@ -54,7 +56,7 @@ class General extends Component public function instantSaveAdvanced() { try { - if (! $this->server->isLogDrainEnabled()) { + if (!$this->server->isLogDrainEnabled()) { $this->database->is_log_drain_enabled = false; $this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.'); @@ -71,14 +73,14 @@ class General extends Component public function instantSave() { try { - if ($this->database->is_public && ! $this->database->public_port) { + if ($this->database->is_public && !$this->database->public_port) { $this->dispatch('error', 'Public port is required.'); $this->database->is_public = false; return; } if ($this->database->is_public) { - if (! str($this->database->status)->startsWith('running')) { + if (!str($this->database->status)->startsWith('running')) { $this->dispatch('error', 'Database must be started to be publicly accessible.'); $this->database->is_public = false; @@ -93,7 +95,7 @@ class General extends Component $this->db_url_public = $this->database->external_db_url; $this->database->save(); } catch (\Throwable $e) { - $this->database->is_public = ! $this->database->is_public; + $this->database->is_public = !$this->database->is_public; return handleError($e, $this); } diff --git a/app/Livewire/Project/Database/Dragonfly/General.php b/app/Livewire/Project/Database/Dragonfly/General.php index f81f4a2f0..00e0ff09f 100644 --- a/app/Livewire/Project/Database/Dragonfly/General.php +++ b/app/Livewire/Project/Database/Dragonfly/General.php @@ -30,6 +30,7 @@ class General extends Component 'database.is_public' => 'nullable|boolean', 'database.public_port' => 'nullable|integer', 'database.is_log_drain_enabled' => 'nullable|boolean', + 'database.custom_docker_run_options' => 'nullable', ]; protected $validationAttributes = [ @@ -40,6 +41,7 @@ class General extends Component 'database.ports_mappings' => 'Port Mapping', 'database.is_public' => 'Is Public', 'database.public_port' => 'Public Port', + 'database.custom_docker_run_options' => 'Custom Docker Run Options', ]; public function mount() @@ -52,7 +54,7 @@ class General extends Component public function instantSaveAdvanced() { try { - if (! $this->server->isLogDrainEnabled()) { + if (!$this->server->isLogDrainEnabled()) { $this->database->is_log_drain_enabled = false; $this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.'); @@ -86,14 +88,14 @@ class General extends Component public function instantSave() { try { - if ($this->database->is_public && ! $this->database->public_port) { + if ($this->database->is_public && !$this->database->public_port) { $this->dispatch('error', 'Public port is required.'); $this->database->is_public = false; return; } if ($this->database->is_public) { - if (! str($this->database->status)->startsWith('running')) { + if (!str($this->database->status)->startsWith('running')) { $this->dispatch('error', 'Database must be started to be publicly accessible.'); $this->database->is_public = false; @@ -108,7 +110,7 @@ class General extends Component $this->db_url_public = $this->database->external_db_url; $this->database->save(); } catch (\Throwable $e) { - $this->database->is_public = ! $this->database->is_public; + $this->database->is_public = !$this->database->is_public; return handleError($e, $this); } diff --git a/app/Livewire/Project/Database/Keydb/General.php b/app/Livewire/Project/Database/Keydb/General.php index 2b78c9f10..320feeac7 100644 --- a/app/Livewire/Project/Database/Keydb/General.php +++ b/app/Livewire/Project/Database/Keydb/General.php @@ -31,6 +31,7 @@ class General extends Component 'database.is_public' => 'nullable|boolean', 'database.public_port' => 'nullable|integer', 'database.is_log_drain_enabled' => 'nullable|boolean', + 'database.custom_docker_run_options' => 'nullable', ]; protected $validationAttributes = [ @@ -42,6 +43,7 @@ class General extends Component 'database.ports_mappings' => 'Port Mapping', 'database.is_public' => 'Is Public', 'database.public_port' => 'Public Port', + 'database.custom_docker_run_options' => 'Custom Docker Run Options', ]; public function mount() @@ -55,7 +57,7 @@ class General extends Component public function instantSaveAdvanced() { try { - if (! $this->server->isLogDrainEnabled()) { + if (!$this->server->isLogDrainEnabled()) { $this->database->is_log_drain_enabled = false; $this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.'); @@ -92,14 +94,14 @@ class General extends Component public function instantSave() { try { - if ($this->database->is_public && ! $this->database->public_port) { + if ($this->database->is_public && !$this->database->public_port) { $this->dispatch('error', 'Public port is required.'); $this->database->is_public = false; return; } if ($this->database->is_public) { - if (! str($this->database->status)->startsWith('running')) { + if (!str($this->database->status)->startsWith('running')) { $this->dispatch('error', 'Database must be started to be publicly accessible.'); $this->database->is_public = false; @@ -114,7 +116,7 @@ class General extends Component $this->db_url_public = $this->database->external_db_url; $this->database->save(); } catch (\Throwable $e) { - $this->database->is_public = ! $this->database->is_public; + $this->database->is_public = !$this->database->is_public; return handleError($e, $this); } diff --git a/app/Livewire/Project/Database/Mariadb/General.php b/app/Livewire/Project/Database/Mariadb/General.php index 858d7b383..70545910c 100644 --- a/app/Livewire/Project/Database/Mariadb/General.php +++ b/app/Livewire/Project/Database/Mariadb/General.php @@ -34,6 +34,7 @@ class General extends Component 'database.is_public' => 'nullable|boolean', 'database.public_port' => 'nullable|integer', 'database.is_log_drain_enabled' => 'nullable|boolean', + 'database.custom_docker_run_options' => 'nullable', ]; protected $validationAttributes = [ @@ -48,6 +49,7 @@ class General extends Component 'database.ports_mappings' => 'Port Mapping', 'database.is_public' => 'Is Public', 'database.public_port' => 'Public Port', + 'database.custom_docker_run_options' => 'Custom Docker Options', ]; public function mount() @@ -61,7 +63,7 @@ class General extends Component public function instantSaveAdvanced() { try { - if (! $this->server->isLogDrainEnabled()) { + if (!$this->server->isLogDrainEnabled()) { $this->database->is_log_drain_enabled = false; $this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.'); @@ -98,14 +100,14 @@ class General extends Component public function instantSave() { try { - if ($this->database->is_public && ! $this->database->public_port) { + if ($this->database->is_public && !$this->database->public_port) { $this->dispatch('error', 'Public port is required.'); $this->database->is_public = false; return; } if ($this->database->is_public) { - if (! str($this->database->status)->startsWith('running')) { + if (!str($this->database->status)->startsWith('running')) { $this->dispatch('error', 'Database must be started to be publicly accessible.'); $this->database->is_public = false; @@ -120,7 +122,7 @@ class General extends Component $this->db_url_public = $this->database->external_db_url; $this->database->save(); } catch (\Throwable $e) { - $this->database->is_public = ! $this->database->is_public; + $this->database->is_public = !$this->database->is_public; return handleError($e, $this); } diff --git a/app/Livewire/Project/Database/Mongodb/General.php b/app/Livewire/Project/Database/Mongodb/General.php index 5a5ef8a62..d23b66c00 100644 --- a/app/Livewire/Project/Database/Mongodb/General.php +++ b/app/Livewire/Project/Database/Mongodb/General.php @@ -33,6 +33,7 @@ class General extends Component 'database.is_public' => 'nullable|boolean', 'database.public_port' => 'nullable|integer', 'database.is_log_drain_enabled' => 'nullable|boolean', + 'database.custom_docker_run_options' => 'nullable', ]; protected $validationAttributes = [ @@ -46,6 +47,7 @@ class General extends Component 'database.ports_mappings' => 'Port Mapping', 'database.is_public' => 'Is Public', 'database.public_port' => 'Public Port', + 'database.custom_docker_run_options' => 'Custom Docker Run Options', ]; public function mount() @@ -59,7 +61,7 @@ class General extends Component public function instantSaveAdvanced() { try { - if (! $this->server->isLogDrainEnabled()) { + if (!$this->server->isLogDrainEnabled()) { $this->database->is_log_drain_enabled = false; $this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.'); @@ -99,14 +101,14 @@ class General extends Component public function instantSave() { try { - if ($this->database->is_public && ! $this->database->public_port) { + if ($this->database->is_public && !$this->database->public_port) { $this->dispatch('error', 'Public port is required.'); $this->database->is_public = false; return; } if ($this->database->is_public) { - if (! str($this->database->status)->startsWith('running')) { + if (!str($this->database->status)->startsWith('running')) { $this->dispatch('error', 'Database must be started to be publicly accessible.'); $this->database->is_public = false; @@ -121,7 +123,7 @@ class General extends Component $this->db_url_public = $this->database->external_db_url; $this->database->save(); } catch (\Throwable $e) { - $this->database->is_public = ! $this->database->is_public; + $this->database->is_public = !$this->database->is_public; return handleError($e, $this); } diff --git a/app/Livewire/Project/Database/Mysql/General.php b/app/Livewire/Project/Database/Mysql/General.php index 58d8e03a8..29a9cbae2 100644 --- a/app/Livewire/Project/Database/Mysql/General.php +++ b/app/Livewire/Project/Database/Mysql/General.php @@ -34,6 +34,7 @@ class General extends Component 'database.is_public' => 'nullable|boolean', 'database.public_port' => 'nullable|integer', 'database.is_log_drain_enabled' => 'nullable|boolean', + 'database.custom_docker_run_options' => 'nullable', ]; protected $validationAttributes = [ @@ -48,6 +49,7 @@ class General extends Component 'database.ports_mappings' => 'Port Mapping', 'database.is_public' => 'Is Public', 'database.public_port' => 'Public Port', + 'database.custom_docker_run_options' => 'Custom Docker Run Options', ]; public function mount() @@ -60,7 +62,7 @@ class General extends Component public function instantSaveAdvanced() { try { - if (! $this->server->isLogDrainEnabled()) { + if (!$this->server->isLogDrainEnabled()) { $this->database->is_log_drain_enabled = false; $this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.'); @@ -97,14 +99,14 @@ class General extends Component public function instantSave() { try { - if ($this->database->is_public && ! $this->database->public_port) { + if ($this->database->is_public && !$this->database->public_port) { $this->dispatch('error', 'Public port is required.'); $this->database->is_public = false; return; } if ($this->database->is_public) { - if (! str($this->database->status)->startsWith('running')) { + if (!str($this->database->status)->startsWith('running')) { $this->dispatch('error', 'Database must be started to be publicly accessible.'); $this->database->is_public = false; @@ -119,7 +121,7 @@ class General extends Component $this->db_url_public = $this->database->external_db_url; $this->database->save(); } catch (\Throwable $e) { - $this->database->is_public = ! $this->database->is_public; + $this->database->is_public = !$this->database->is_public; return handleError($e, $this); } diff --git a/app/Livewire/Project/Database/Postgresql/General.php b/app/Livewire/Project/Database/Postgresql/General.php index eabbbd679..c12fa49f3 100644 --- a/app/Livewire/Project/Database/Postgresql/General.php +++ b/app/Livewire/Project/Database/Postgresql/General.php @@ -49,6 +49,7 @@ class General extends Component 'database.is_public' => 'nullable|boolean', 'database.public_port' => 'nullable|integer', 'database.is_log_drain_enabled' => 'nullable|boolean', + 'database.custom_docker_run_options' => 'nullable', ]; protected $validationAttributes = [ @@ -65,6 +66,7 @@ class General extends Component 'database.ports_mappings' => 'Port Mapping', 'database.is_public' => 'Is Public', 'database.public_port' => 'Public Port', + 'database.custom_docker_run_options' => 'Custom Docker Run Options', ]; public function mount() diff --git a/app/Livewire/Project/Database/Redis/General.php b/app/Livewire/Project/Database/Redis/General.php index a7ce0161a..fd2f9834f 100644 --- a/app/Livewire/Project/Database/Redis/General.php +++ b/app/Livewire/Project/Database/Redis/General.php @@ -31,6 +31,7 @@ class General extends Component 'database.is_public' => 'nullable|boolean', 'database.public_port' => 'nullable|integer', 'database.is_log_drain_enabled' => 'nullable|boolean', + 'database.custom_docker_run_options' => 'nullable', ]; protected $validationAttributes = [ @@ -42,6 +43,7 @@ class General extends Component 'database.ports_mappings' => 'Port Mapping', 'database.is_public' => 'Is Public', 'database.public_port' => 'Public Port', + 'database.custom_docker_run_options' => 'Custom Docker Options', ]; public function mount() @@ -55,7 +57,7 @@ class General extends Component public function instantSaveAdvanced() { try { - if (! $this->server->isLogDrainEnabled()) { + if (!$this->server->isLogDrainEnabled()) { $this->database->is_log_drain_enabled = false; $this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.'); @@ -86,14 +88,14 @@ class General extends Component public function instantSave() { try { - if ($this->database->is_public && ! $this->database->public_port) { + if ($this->database->is_public && !$this->database->public_port) { $this->dispatch('error', 'Public port is required.'); $this->database->is_public = false; return; } if ($this->database->is_public) { - if (! str($this->database->status)->startsWith('running')) { + if (!str($this->database->status)->startsWith('running')) { $this->dispatch('error', 'Database must be started to be publicly accessible.'); $this->database->is_public = false; @@ -108,7 +110,7 @@ class General extends Component $this->db_url_public = $this->database->external_db_url; $this->database->save(); } catch (\Throwable $e) { - $this->database->is_public = ! $this->database->is_public; + $this->database->is_public = !$this->database->is_public; return handleError($e, $this); } diff --git a/app/Livewire/Project/New/DockerCompose.php b/app/Livewire/Project/New/DockerCompose.php index 633ce5bda..199a20cf6 100644 --- a/app/Livewire/Project/New/DockerCompose.php +++ b/app/Livewire/Project/New/DockerCompose.php @@ -5,6 +5,8 @@ namespace App\Livewire\Project\New; use App\Models\EnvironmentVariable; use App\Models\Project; use App\Models\Service; +use App\Models\StandaloneDocker; +use App\Models\SwarmDocker; use Illuminate\Support\Str; use Livewire\Component; use Symfony\Component\Yaml\Yaml; @@ -58,12 +60,26 @@ class DockerCompose extends Component $project = Project::where('uuid', $this->parameters['project_uuid'])->first(); $environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first(); + + $destination_uuid = $this->query['destination']; + $destination = StandaloneDocker::where('uuid', $destination_uuid)->first(); + if (! $destination) { + $destination = SwarmDocker::where('uuid', $destination_uuid)->first(); + } + if (! $destination) { + throw new \Exception('Destination not found. What?!'); + } + $destination_class = $destination->getMorphClass(); + $service = Service::create([ 'name' => 'service'.Str::random(10), 'docker_compose_raw' => $this->dockerComposeRaw, 'environment_id' => $environment->id, 'server_id' => (int) $server_id, + 'destination_id' => $destination->id, + 'destination_type' => $destination_class, ]); + $variables = parseEnvFormatToArray($this->envFile); foreach ($variables as $key => $variable) { EnvironmentVariable::create([ diff --git a/app/Livewire/Project/New/PublicGitRepository.php b/app/Livewire/Project/New/PublicGitRepository.php index b29fe8cab..b5c5cb1db 100644 --- a/app/Livewire/Project/New/PublicGitRepository.php +++ b/app/Livewire/Project/New/PublicGitRepository.php @@ -99,6 +99,16 @@ class PublicGitRepository extends Component } } + public function updatedDockerComposeLocation() + { + if ($this->docker_compose_location) { + $this->docker_compose_location = rtrim($this->docker_compose_location, '/'); + if (! str($this->docker_compose_location)->startsWith('/')) { + $this->docker_compose_location = '/'.$this->docker_compose_location; + } + } + } + public function updatedBuildPack() { if ($this->build_pack === 'nixpacks') { diff --git a/app/Livewire/Project/New/Select.php b/app/Livewire/Project/New/Select.php index b25290f71..3c5f3901b 100644 --- a/app/Livewire/Project/New/Select.php +++ b/app/Livewire/Project/New/Select.php @@ -45,6 +45,8 @@ class Select extends Component public ?string $selectedEnvironment = null; + public string $postgresql_type = 'postgres:16-alpine'; + public ?string $existingPostgresqlUrl = null; public ?string $search = null; @@ -202,6 +204,8 @@ class Select extends Component $docker = $this->standaloneDockers->first() ?? $this->swarmDockers->first(); if ($docker) { $this->setDestination($docker->uuid); + + return $this->whatToDoNext(); } } $this->current_step = 'destinations'; @@ -211,15 +215,38 @@ class Select extends Component { $this->destination_uuid = $destination_uuid; + return $this->whatToDoNext(); + } + + public function setPostgresqlType(string $type) + { + $this->postgresql_type = $type; + return redirect()->route('project.resource.create', [ 'project_uuid' => $this->parameters['project_uuid'], 'environment_name' => $this->parameters['environment_name'], 'type' => $this->type, 'destination' => $this->destination_uuid, 'server_id' => $this->server_id, + 'database_image' => $this->postgresql_type, ]); } + public function whatToDoNext() + { + if ($this->type === 'postgresql') { + $this->current_step = 'select-postgresql-type'; + } else { + return redirect()->route('project.resource.create', [ + 'project_uuid' => $this->parameters['project_uuid'], + 'environment_name' => $this->parameters['environment_name'], + 'type' => $this->type, + 'destination' => $this->destination_uuid, + 'server_id' => $this->server_id, + ]); + } + } + public function loadServers() { $this->servers = Server::isUsable()->get(); diff --git a/app/Livewire/Project/Resource/Create.php b/app/Livewire/Project/Resource/Create.php index 341dd93d8..5c6a37d6d 100644 --- a/app/Livewire/Project/Resource/Create.php +++ b/app/Livewire/Project/Resource/Create.php @@ -18,6 +18,7 @@ class Create extends Component $type = str(request()->query('type')); $destination_uuid = request()->query('destination'); $server_id = request()->query('server_id'); + $database_image = request()->query('database_image'); $project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first(); if (! $project) { @@ -33,7 +34,11 @@ class Create extends Component if (in_array($type, DATABASE_TYPES)) { if ($type->value() === 'postgresql') { - $database = create_standalone_postgresql($environment->id, $destination_uuid); + $database = create_standalone_postgresql( + environmentId: $environment->id, + destinationUuid: $destination_uuid, + databaseImage: $database_image + ); } elseif ($type->value() === 'redis') { $database = create_standalone_redis($environment->id, $destination_uuid); } elseif ($type->value() === 'mongodb') { @@ -86,18 +91,16 @@ class Create extends Component $oneClickDotEnvs->each(function ($value) use ($service) { $key = str()->before($value, '='); $value = str(str()->after($value, '=')); - $generatedValue = $value; - if ($value->contains('SERVICE_')) { - $command = $value->after('SERVICE_')->beforeLast('_'); - $generatedValue = generateEnvValue($command->value(), $service); + if ($value) { + EnvironmentVariable::create([ + 'key' => $key, + 'value' => $value, + 'service_id' => $service->id, + 'is_build_time' => false, + 'is_preview' => false, + ]); } - EnvironmentVariable::create([ - 'key' => $key, - 'value' => $generatedValue, - 'service_id' => $service->id, - 'is_build_time' => false, - 'is_preview' => false, - ]); + }); } $service->parse(isNew: true); diff --git a/app/Livewire/Project/Service/Configuration.php b/app/Livewire/Project/Service/Configuration.php index a55af5777..a2e48fee7 100644 --- a/app/Livewire/Project/Service/Configuration.php +++ b/app/Livewire/Project/Service/Configuration.php @@ -25,6 +25,7 @@ class Configuration extends Component return [ "echo-private:user.{$userId},ServiceStatusChanged" => 'check_status', 'check_status', + 'refresh' => '$refresh', ]; } @@ -75,6 +76,12 @@ class Configuration extends Component { try { GetContainersStatus::run($this->service->server); + $this->service->applications->each(function ($application) { + $application->refresh(); + }); + $this->service->databases->each(function ($database) { + $database->refresh(); + }); $this->dispatch('$refresh'); } catch (\Exception $e) { return handleError($e, $this); diff --git a/app/Livewire/Project/Service/EditCompose.php b/app/Livewire/Project/Service/EditCompose.php index f67b95a8a..dc043e65a 100644 --- a/app/Livewire/Project/Service/EditCompose.php +++ b/app/Livewire/Project/Service/EditCompose.php @@ -11,7 +11,11 @@ class EditCompose extends Component public $serviceId; - protected $listeners = ['refreshEnvs', 'envsUpdated']; + protected $listeners = [ + 'refreshEnvs', + 'envsUpdated', + 'refresh' => 'envsUpdated', + ]; protected $rules = [ 'service.docker_compose_raw' => 'required', @@ -39,6 +43,7 @@ class EditCompose extends Component { $this->dispatch('info', 'Saving new docker compose...'); $this->dispatch('saveCompose', $this->service->docker_compose_raw); + $this->dispatch('refreshStorages'); } public function instantSave() diff --git a/app/Livewire/Project/Service/FileStorage.php b/app/Livewire/Project/Service/FileStorage.php index 9f0d94aad..9fac897bf 100644 --- a/app/Livewire/Project/Service/FileStorage.php +++ b/app/Livewire/Project/Service/FileStorage.php @@ -35,6 +35,7 @@ class FileStorage extends Component 'fileStorage.fs_path' => 'required', 'fileStorage.mount_path' => 'required', 'fileStorage.content' => 'nullable', + 'fileStorage.is_based_on_git' => 'required|boolean', ]; public function mount() @@ -47,6 +48,7 @@ class FileStorage extends Component $this->workdir = null; $this->fs_path = $this->fileStorage->fs_path; } + $this->fileStorage->loadStorageOnServer(); } public function convertToDirectory() @@ -55,6 +57,7 @@ class FileStorage extends Component $this->fileStorage->deleteStorageOnServer(); $this->fileStorage->is_directory = true; $this->fileStorage->content = null; + $this->fileStorage->is_based_on_git = false; $this->fileStorage->save(); $this->fileStorage->saveStorageOnServer(); } catch (\Throwable $e) { @@ -70,6 +73,9 @@ class FileStorage extends Component $this->fileStorage->deleteStorageOnServer(); $this->fileStorage->is_directory = false; $this->fileStorage->content = null; + if (data_get($this->resource, 'settings.is_preserve_repository_enabled')) { + $this->fileStorage->is_based_on_git = true; + } $this->fileStorage->save(); $this->fileStorage->saveStorageOnServer(); } catch (\Throwable $e) { diff --git a/app/Livewire/Project/Service/Navbar.php b/app/Livewire/Project/Service/Navbar.php index 66d3079ec..51f38d2db 100644 --- a/app/Livewire/Project/Service/Navbar.php +++ b/app/Livewire/Project/Service/Navbar.php @@ -21,6 +21,7 @@ class Navbar extends Component public $isDeploymentProgress = false; public $docker_cleanup = true; + public $title = 'Configuration'; public function mount() { diff --git a/app/Livewire/Project/Service/StackForm.php b/app/Livewire/Project/Service/StackForm.php index 3f62202c8..04bb136db 100644 --- a/app/Livewire/Project/Service/StackForm.php +++ b/app/Livewire/Project/Service/StackForm.php @@ -53,7 +53,7 @@ class StackForm extends Component public function saveCompose($raw) { $this->service->docker_compose_raw = $raw; - $this->submit(); + $this->submit(notify: false); } public function instantSave() @@ -62,7 +62,7 @@ class StackForm extends Component $this->dispatch('success', 'Service settings saved.'); } - public function submit() + public function submit($notify = true) { try { $this->validate(); @@ -76,7 +76,7 @@ class StackForm extends Component $this->service->refresh(); $this->service->saveComposeConfigs(); $this->dispatch('refreshEnvs'); - $this->dispatch('success', 'Service saved.'); + $notify && $this->dispatch('success', 'Service saved.'); } catch (\Throwable $e) { return handleError($e, $this); } finally { diff --git a/app/Livewire/Project/Shared/EnvironmentVariable/All.php b/app/Livewire/Project/Shared/EnvironmentVariable/All.php index 9e6760293..055788b57 100644 --- a/app/Livewire/Project/Shared/EnvironmentVariable/All.php +++ b/app/Livewire/Project/Shared/EnvironmentVariable/All.php @@ -23,8 +23,9 @@ class All extends Component public string $view = 'normal'; protected $listeners = [ - 'refreshEnvs', 'saveKey' => 'submit', + 'refreshEnvs', + 'environmentVariableDeleted' => 'refreshEnvs', ]; protected $rules = [ @@ -40,220 +41,240 @@ class All extends Component $this->showPreview = true; } $this->modalId = new Cuid2; - $this->sortMe(); - $this->getDevView(); - } - - public function sortMe() - { - if ($this->resourceClass === 'App\Models\Application' && data_get($this->resource, 'build_pack') !== 'dockercompose') { - if ($this->resource->settings->is_env_sorting_enabled) { - $this->resource->environment_variables = $this->resource->environment_variables->sortBy('key'); - $this->resource->environment_variables_preview = $this->resource->environment_variables_preview->sortBy('key'); - } else { - $this->resource->environment_variables = $this->resource->environment_variables->sortBy('id'); - $this->resource->environment_variables_preview = $this->resource->environment_variables_preview->sortBy('id'); - } - } - $this->getDevView(); + $this->sortEnvironmentVariables(); } public function instantSave() { - if ($this->resourceClass === 'App\Models\Application' && data_get($this->resource, 'build_pack') !== 'dockercompose') { - $this->resource->settings->save(); - $this->dispatch('success', 'Environment variable settings updated.'); - $this->sortMe(); + $this->resource->settings->save(); + $this->sortEnvironmentVariables(); + $this->dispatch('success', 'Environment variable settings updated.'); + } + + public function sortEnvironmentVariables() + { + if ($this->resource->type() === 'application') { + $this->resource->load(['environment_variables', 'environment_variables_preview']); + } else { + $this->resource->load(['environment_variables']); } + + $sortBy = data_get($this->resource, 'settings.is_env_sorting_enabled') ? 'key' : 'order'; + + $sortFunction = function ($variables) use ($sortBy) { + if (! $variables) { + return $variables; + } + if ($sortBy === 'key') { + return $variables->sortBy(function ($item) { + return strtolower($item->key); + }, SORT_NATURAL | SORT_FLAG_CASE)->values(); + } else { + return $variables->sortBy('order')->values(); + } + }; + + $this->resource->environment_variables = $sortFunction($this->resource->environment_variables); + $this->resource->environment_variables_preview = $sortFunction($this->resource->environment_variables_preview); + + $this->getDevView(); } public function getDevView() { - $this->variables = $this->resource->environment_variables->map(function ($item) { + $this->variables = $this->formatEnvironmentVariables($this->resource->environment_variables); + if ($this->showPreview) { + $this->variablesPreview = $this->formatEnvironmentVariables($this->resource->environment_variables_preview); + } + } + + private function formatEnvironmentVariables($variables) + { + return $variables->map(function ($item) { if ($item->is_shown_once) { - return "$item->key=(locked secret)"; + return "$item->key=(Locked Secret, delete and add again to change)"; } if ($item->is_multiline) { - return "$item->key=(multiline, edit in normal view)"; + return "$item->key=(Multiline environment variable, edit in normal view)"; } return "$item->key=$item->value"; - })->join(' -'); - if ($this->showPreview) { - $this->variablesPreview = $this->resource->environment_variables_preview->map(function ($item) { - if ($item->is_shown_once) { - return "$item->key=(locked secret)"; - } - if ($item->is_multiline) { - return "$item->key=(multiline, edit in normal view)"; - } - - return "$item->key=$item->value"; - })->join(' -'); - } + })->join("\n"); } public function switch() { - if ($this->view === 'normal') { - $this->view = 'dev'; - } else { - $this->view = 'normal'; - } - $this->sortMe(); + $this->view = $this->view === 'normal' ? 'dev' : 'normal'; + $this->sortEnvironmentVariables(); } - public function saveVariables($isPreview) + public function submit($data = null) { - if ($isPreview) { - $variables = parseEnvFormatToArray($this->variablesPreview); - $this->resource->environment_variables_preview()->whereNotIn('key', array_keys($variables))->delete(); - } else { - $variables = parseEnvFormatToArray($this->variables); - $this->resource->environment_variables()->whereNotIn('key', array_keys($variables))->delete(); - } - foreach ($variables as $key => $variable) { - if ($isPreview) { - $found = $this->resource->environment_variables_preview()->where('key', $key)->first(); + try { + if ($data === null) { + $this->handleBulkSubmit(); } else { - $found = $this->resource->environment_variables()->where('key', $key)->first(); + $this->handleSingleSubmit($data); } - if ($found) { - if ($found->is_shown_once || $found->is_multiline) { - continue; + + $this->updateOrder(); + $this->sortEnvironmentVariables(); + } catch (\Throwable $e) { + return handleError($e, $this); + } + } + + private function updateOrder() + { + $variables = parseEnvFormatToArray($this->variables); + $order = 1; + foreach ($variables as $key => $value) { + $env = $this->resource->environment_variables()->where('key', $key)->first(); + if ($env) { + $env->order = $order; + $env->save(); + } + $order++; + } + + if ($this->showPreview) { + $previewVariables = parseEnvFormatToArray($this->variablesPreview); + $order = 1; + foreach ($previewVariables as $key => $value) { + $env = $this->resource->environment_variables_preview()->where('key', $key)->first(); + if ($env) { + $env->order = $order; + $env->save(); } - $found->value = $variable; - // if (str($found->value)->startsWith('{{') && str($found->value)->endsWith('}}')) { - // $type = str($found->value)->after('{{')->before('.')->value; - // if (! collect(SHARED_VARIABLE_TYPES)->contains($type)) { - // $this->dispatch('error', 'Invalid shared variable type.', 'Valid types are: team, project, environment.'); + $order++; + } + } + } - // return; - // } - // } - $found->save(); + private function handleBulkSubmit() + { + $variables = parseEnvFormatToArray($this->variables); + $this->deleteRemovedVariables(false, $variables); + $this->updateOrCreateVariables(false, $variables); - continue; + if ($this->showPreview) { + $previewVariables = parseEnvFormatToArray($this->variablesPreview); + $this->deleteRemovedVariables(true, $previewVariables); + $this->updateOrCreateVariables(true, $previewVariables); + } + + $this->dispatch('success', 'Environment variables updated.'); + } + + private function handleSingleSubmit($data) + { + $found = $this->resource->environment_variables()->where('key', $data['key'])->first(); + if ($found) { + $this->dispatch('error', 'Environment variable already exists.'); + + return; + } + + $maxOrder = $this->resource->environment_variables()->max('order') ?? 0; + $environment = $this->createEnvironmentVariable($data); + $environment->order = $maxOrder + 1; + $environment->save(); + } + + private function createEnvironmentVariable($data) + { + $environment = new EnvironmentVariable; + $environment->key = $data['key']; + $environment->value = $data['value']; + $environment->is_build_time = $data['is_build_time'] ?? false; + $environment->is_multiline = $data['is_multiline'] ?? false; + $environment->is_literal = $data['is_literal'] ?? false; + $environment->is_preview = $data['is_preview'] ?? false; + + $resourceType = $this->resource->type(); + $resourceIdField = $this->getResourceIdField($resourceType); + + if ($resourceIdField) { + $environment->$resourceIdField = $this->resource->id; + } + + return $environment; + } + + private function getResourceIdField($resourceType) + { + $resourceTypes = [ + 'application' => 'application_id', + 'standalone-postgresql' => 'standalone_postgresql_id', + 'standalone-redis' => 'standalone_redis_id', + 'standalone-mongodb' => 'standalone_mongodb_id', + 'standalone-mysql' => 'standalone_mysql_id', + 'standalone-mariadb' => 'standalone_mariadb_id', + 'standalone-keydb' => 'standalone_keydb_id', + 'standalone-dragonfly' => 'standalone_dragonfly_id', + 'standalone-clickhouse' => 'standalone_clickhouse_id', + 'service' => 'service_id', + ]; + + return $resourceTypes[$resourceType] ?? null; + } + + private function deleteRemovedVariables($isPreview, $variables) + { + $method = $isPreview ? 'environment_variables_preview' : 'environment_variables'; + $this->resource->$method()->whereNotIn('key', array_keys($variables))->delete(); + } + + private function updateOrCreateVariables($isPreview, $variables) + { + foreach ($variables as $key => $value) { + $method = $isPreview ? 'environment_variables_preview' : 'environment_variables'; + $found = $this->resource->$method()->where('key', $key)->first(); + + if ($found) { + if (! $found->is_shown_once && ! $found->is_multiline) { + $found->value = $value; + $found->save(); + } } else { $environment = new EnvironmentVariable; $environment->key = $key; - $environment->value = $variable; - // if (str($environment->value)->startsWith('{{') && str($environment->value)->endsWith('}}')) { - // $type = str($environment->value)->after('{{')->before('.')->value; - // if (! collect(SHARED_VARIABLE_TYPES)->contains($type)) { - // $this->dispatch('error', 'Invalid shared variable type.', 'Valid types are: team, project, environment.'); - - // return; - // } - // } + $environment->value = $value; $environment->is_build_time = false; $environment->is_multiline = false; - $environment->is_preview = $isPreview ? true : false; - switch ($this->resource->type()) { - case 'application': - $environment->application_id = $this->resource->id; - break; - case 'standalone-postgresql': - $environment->standalone_postgresql_id = $this->resource->id; - break; - case 'standalone-redis': - $environment->standalone_redis_id = $this->resource->id; - break; - case 'standalone-mongodb': - $environment->standalone_mongodb_id = $this->resource->id; - break; - case 'standalone-mysql': - $environment->standalone_mysql_id = $this->resource->id; - break; - case 'standalone-mariadb': - $environment->standalone_mariadb_id = $this->resource->id; - break; - case 'standalone-keydb': - $environment->standalone_keydb_id = $this->resource->id; - break; - case 'standalone-dragonfly': - $environment->standalone_dragonfly_id = $this->resource->id; - break; - case 'standalone-clickhouse': - $environment->standalone_clickhouse_id = $this->resource->id; - break; - case 'service': - $environment->service_id = $this->resource->id; - break; - } + $environment->is_preview = $isPreview; + + $this->setEnvironmentResourceId($environment); $environment->save(); } } - if ($isPreview) { - $this->dispatch('success', 'Preview environment variables updated.'); - } else { - $this->dispatch('success', 'Environment variables updated.'); + } + + private function setEnvironmentResourceId($environment) + { + $resourceTypes = [ + 'application' => 'application_id', + 'standalone-postgresql' => 'standalone_postgresql_id', + 'standalone-redis' => 'standalone_redis_id', + 'standalone-mongodb' => 'standalone_mongodb_id', + 'standalone-mysql' => 'standalone_mysql_id', + 'standalone-mariadb' => 'standalone_mariadb_id', + 'standalone-keydb' => 'standalone_keydb_id', + 'standalone-dragonfly' => 'standalone_dragonfly_id', + 'standalone-clickhouse' => 'standalone_clickhouse_id', + 'service' => 'service_id', + ]; + + $resourceType = $this->resource->type(); + if (isset($resourceTypes[$resourceType])) { + $environment->{$resourceTypes[$resourceType]} = $this->resource->id; } - $this->refreshEnvs(); } public function refreshEnvs() { $this->resource->refresh(); + $this->sortEnvironmentVariables(); $this->getDevView(); } - - public function submit($data) - { - try { - $found = $this->resource->environment_variables()->where('key', $data['key'])->first(); - if ($found) { - $this->dispatch('error', 'Environment variable already exists.'); - - return; - } - $environment = new EnvironmentVariable; - $environment->key = $data['key']; - $environment->value = $data['value']; - $environment->is_build_time = $data['is_build_time']; - $environment->is_multiline = $data['is_multiline']; - $environment->is_literal = $data['is_literal']; - $environment->is_preview = $data['is_preview']; - - switch ($this->resource->type()) { - case 'application': - $environment->application_id = $this->resource->id; - break; - case 'standalone-postgresql': - $environment->standalone_postgresql_id = $this->resource->id; - break; - case 'standalone-redis': - $environment->standalone_redis_id = $this->resource->id; - break; - case 'standalone-mongodb': - $environment->standalone_mongodb_id = $this->resource->id; - break; - case 'standalone-mysql': - $environment->standalone_mysql_id = $this->resource->id; - break; - case 'standalone-mariadb': - $environment->standalone_mariadb_id = $this->resource->id; - break; - case 'standalone-keydb': - $environment->standalone_keydb_id = $this->resource->id; - break; - case 'standalone-dragonfly': - $environment->standalone_dragonfly_id = $this->resource->id; - break; - case 'standalone-clickhouse': - $environment->standalone_clickhouse_id = $this->resource->id; - break; - case 'service': - $environment->service_id = $this->resource->id; - break; - } - $environment->save(); - $this->refreshEnvs(); - $this->dispatch('success', 'Environment variable added.'); - } catch (\Throwable $e) { - return handleError($e, $this); - } - } } diff --git a/app/Livewire/Project/Shared/EnvironmentVariable/Show.php b/app/Livewire/Project/Shared/EnvironmentVariable/Show.php index e63871602..463ceecad 100644 --- a/app/Livewire/Project/Shared/EnvironmentVariable/Show.php +++ b/app/Livewire/Project/Shared/EnvironmentVariable/Show.php @@ -24,7 +24,8 @@ class Show extends Component public string $type; protected $listeners = [ - 'refresh' => 'refresh', + 'refreshEnvs' => 'refresh', + 'refresh', 'compose_loaded' => '$refresh', ]; @@ -129,7 +130,8 @@ class Show extends Component { try { $this->env->delete(); - $this->dispatch('refreshEnvs'); + $this->dispatch('environmentVariableDeleted'); + $this->dispatch('success', 'Environment variable deleted successfully.'); } catch (\Exception $e) { return handleError($e); } diff --git a/app/Livewire/Project/Shared/ExecuteContainerCommand.php b/app/Livewire/Project/Shared/ExecuteContainerCommand.php index 343915d9c..d95443621 100644 --- a/app/Livewire/Project/Shared/ExecuteContainerCommand.php +++ b/app/Livewire/Project/Shared/ExecuteContainerCommand.php @@ -2,18 +2,16 @@ namespace App\Livewire\Project\Shared; -use App\Actions\Server\RunCommand; use App\Models\Application; use App\Models\Server; use App\Models\Service; use Illuminate\Support\Collection; +use Livewire\Attributes\On; use Livewire\Component; class ExecuteContainerCommand extends Component { - public string $command; - - public string $container; + public $container; public Collection $containers; @@ -23,8 +21,6 @@ class ExecuteContainerCommand extends Component public string $type; - public string $workDir = ''; - public Server $server; public Collection $servers; @@ -33,11 +29,13 @@ class ExecuteContainerCommand extends Component 'server' => 'required', 'container' => 'required', 'command' => 'required', - 'workDir' => 'nullable', ]; public function mount() { + if (! auth()->user()->isAdmin()) { + abort(403); + } $this->parameters = get_route_parameters(); $this->containers = collect(); $this->servers = collect(); @@ -62,24 +60,13 @@ class ExecuteContainerCommand extends Component if ($this->resource->destination->server->isFunctional()) { $this->servers = $this->servers->push($this->resource->destination->server); } - $this->container = $this->resource->uuid; - $this->containers->push($this->container); } elseif (data_get($this->parameters, 'service_uuid')) { $this->type = 'service'; $this->resource = Service::where('uuid', $this->parameters['service_uuid'])->firstOrFail(); - $this->resource->applications()->get()->each(function ($application) { - $this->containers->push(data_get($application, 'name').'-'.data_get($this->resource, 'uuid')); - }); - $this->resource->databases()->get()->each(function ($database) { - $this->containers->push(data_get($database, 'name').'-'.data_get($this->resource, 'uuid')); - }); if ($this->resource->server->isFunctional()) { $this->servers = $this->servers->push($this->resource->server); } } - if ($this->containers->count() > 0) { - $this->container = $this->containers->first(); - } } public function loadContainers() @@ -102,44 +89,65 @@ class ExecuteContainerCommand extends Component ]; $this->containers = $this->containers->push($payload); } + } elseif (data_get($this->parameters, 'database_uuid')) { + if ($this->resource->isRunning()) { + $this->containers = $this->containers->push([ + 'server' => $server, + 'container' => [ + 'Names' => $this->resource->uuid, + ], + ]); + } + } elseif (data_get($this->parameters, 'service_uuid')) { + $this->resource->applications()->get()->each(function ($application) { + ray($application); + if ($application->isRunning()) { + $this->containers->push([ + 'server' => $this->resource->server, + 'container' => [ + 'Names' => data_get($application, 'name').'-'.data_get($this->resource, 'uuid'), + ], + ]); + } + }); + $this->resource->databases()->get()->each(function ($database) { + if ($database->isRunning()) { + $this->containers->push([ + 'server' => $this->resource->server, + 'container' => [ + 'Names' => data_get($database, 'name').'-'.data_get($this->resource, 'uuid'), + ], + ]); + } + }); } + } if ($this->containers->count() > 0) { - if (data_get($this->parameters, 'application_uuid')) { - $this->container = data_get($this->containers->first(), 'container.Names'); - } elseif (data_get($this->parameters, 'database_uuid')) { - $this->container = $this->containers->first(); - } elseif (data_get($this->parameters, 'service_uuid')) { - $this->container = $this->containers->first(); - } + $this->container = $this->containers->first(); } } - public function runCommand() + #[On('connectToContainer')] + public function connectToContainer() { try { - if (data_get($this->parameters, 'application_uuid')) { - $container = $this->containers->where('container.Names', $this->container)->first(); - $container_name = data_get($container, 'container.Names'); - if (is_null($container)) { - throw new \RuntimeException('Container not found.'); - } - $server = data_get($container, 'server'); - } else { - $container_name = $this->container; - $server = $this->servers->first(); + $container_name = data_get($this->container, 'container.Names'); + if (is_null($container_name)) { + throw new \RuntimeException('Container not found.'); } + $server = data_get($this->container, 'server'); + if ($server->isForceDisabled()) { throw new \RuntimeException('Server is disabled.'); } - $cmd = "sh -c 'if [ -f ~/.profile ]; then . ~/.profile; fi; ".str_replace("'", "'\''", $this->command)."'"; - if (! empty($this->workDir)) { - $exec = "docker exec -w {$this->workDir} {$container_name} {$cmd}"; - } else { - $exec = "docker exec {$container_name} {$cmd}"; - } - $activity = RunCommand::run(server: $server, command: $exec); - $this->dispatch('activityMonitor', $activity->id); + + $this->dispatch('send-terminal-command', + true, + $container_name, + $server->uuid, + ); + } catch (\Throwable $e) { return handleError($e, $this); } diff --git a/app/Livewire/Project/Shared/GetLogs.php b/app/Livewire/Project/Shared/GetLogs.php index edcaf0f34..deccc875c 100644 --- a/app/Livewire/Project/Shared/GetLogs.php +++ b/app/Livewire/Project/Shared/GetLogs.php @@ -97,7 +97,7 @@ class GetLogs extends Component if (! $refresh && ($this->resource?->getMorphClass() === 'App\Models\Service' || str($this->container)->contains('-pr-'))) { return; } - if (! $this->numberOfLines) { + if ($this->numberOfLines <= 0) { $this->numberOfLines = 1000; } if ($this->container) { diff --git a/app/Livewire/Project/Shared/ScheduledTask/All.php b/app/Livewire/Project/Shared/ScheduledTask/All.php index 4d8c87dbf..b383e294a 100644 --- a/app/Livewire/Project/Shared/ScheduledTask/All.php +++ b/app/Livewire/Project/Shared/ScheduledTask/All.php @@ -26,7 +26,7 @@ class All extends Component $this->containerNames = $this->containerNames->merge($this->resource->databases()->pluck('name')); } elseif ($this->resource->type() == 'application') { if ($this->resource->build_pack === 'dockercompose') { - $parsed = $this->resource->parseCompose(); + $parsed = $this->resource->parse(); $containers = collect(data_get($parsed, 'services'))->keys(); $this->containerNames = $containers; } else { diff --git a/app/Livewire/Project/Shared/ScheduledTask/Executions.php b/app/Livewire/Project/Shared/ScheduledTask/Executions.php index 7a2e14e89..5bd6b4b9b 100644 --- a/app/Livewire/Project/Shared/ScheduledTask/Executions.php +++ b/app/Livewire/Project/Shared/ScheduledTask/Executions.php @@ -7,8 +7,8 @@ use Livewire\Component; class Executions extends Component { public $executions = []; - public $selectedKey; + public $task; public function getListeners() { @@ -26,4 +26,44 @@ class Executions extends Component } $this->selectedKey = $key; } + + public function server() + { + if (!$this->task) { + return null; + } + + if ($this->task->application) { + if ($this->task->application->destination && $this->task->application->destination->server) { + return $this->task->application->destination->server; + } + } elseif ($this->task->service) { + if ($this->task->service->destination && $this->task->service->destination->server) { + return $this->task->service->destination->server; + } + } + return null; + } + + public function getServerTimezone() + { + $server = $this->server(); + if (!$server) { + return 'UTC'; + } + $serverTimezone = $server->settings->server_timezone; + return $serverTimezone; + } + + public function formatDateInServerTimezone($date) + { + $serverTimezone = $this->getServerTimezone(); + $dateObj = new \DateTime($date); + try { + $dateObj->setTimezone(new \DateTimeZone($serverTimezone)); + } catch (\Exception $e) { + $dateObj->setTimezone(new \DateTimeZone('UTC')); + } + return $dateObj->format('Y-m-d H:i:s T'); + } } diff --git a/app/Livewire/Project/Shared/Terminal.php b/app/Livewire/Project/Shared/Terminal.php new file mode 100644 index 000000000..802e65a30 --- /dev/null +++ b/app/Livewire/Project/Shared/Terminal.php @@ -0,0 +1,43 @@ +whereUuid($serverUuid)->firstOrFail(); + + if ($isContainer) { + $status = getContainerStatus($server, $identifier); + if ($status !== 'running') { + return; + } + $command = generateSshCommand($server, "docker exec -it {$identifier} sh -c 'if [ -f ~/.profile ]; then . ~/.profile; fi; if [ -n \"\$SHELL\" ]; then exec \$SHELL; else sh; fi'"); + } else { + $command = generateSshCommand($server, "sh -c 'if [ -f ~/.profile ]; then . ~/.profile; fi; if [ -n \"\$SHELL\" ]; then exec \$SHELL; else sh; fi'"); + } + + // ssh command is sent back to frontend then to websocket + // this is done because the websocket connection is not available here + // a better solution would be to remove websocket on NodeJS and work with something like + // 1. Laravel Pusher/Echo connection (not possible without a sdk) + // 2. Ratchet / Revolt / ReactPHP / Event Loop (possible but hard to implement and huge dependencies) + // 3. Just found out about this https://github.com/sirn-se/websocket-php, perhaps it can be used + // 4. Follow-up discussions here: + // - https://github.com/coollabsio/coolify/issues/2298 + // - https://github.com/coollabsio/coolify/discussions/3362 + $this->dispatch('send-back-command', $command); + } + + public function render() + { + return view('livewire.project.shared.terminal'); + } +} diff --git a/app/Livewire/RunCommand.php b/app/Livewire/RunCommand.php deleted file mode 100644 index c2d3adeea..000000000 --- a/app/Livewire/RunCommand.php +++ /dev/null @@ -1,43 +0,0 @@ - 'required', - 'command' => 'required', - ]; - - protected $validationAttributes = [ - 'server' => 'server', - 'command' => 'command', - ]; - - public function mount($servers) - { - $this->servers = $servers; - $this->server = $servers[0]->uuid; - } - - public function runCommand() - { - $this->validate(); - try { - $activity = ServerRunCommand::run(server: Server::where('uuid', $this->server)->first(), command: $this->command); - $this->dispatch('activityMonitor', $activity->id); - } catch (\Throwable $e) { - return handleError($e, $this); - } - } -} diff --git a/app/Livewire/Server/Form.php b/app/Livewire/Server/Form.php index 9934ea345..3b3747a81 100644 --- a/app/Livewire/Server/Form.php +++ b/app/Livewire/Server/Form.php @@ -18,13 +18,17 @@ class Form extends Component public ?string $wildcard_domain = null; - public int $cleanup_after_percentage; - public bool $dockerInstallationStarted = false; public bool $revalidate = false; - protected $listeners = ['serverInstalled', 'revalidate' => '$refresh']; + public $timezones; + + protected $listeners = [ + 'serverInstalled', + 'refreshServerShow' => 'serverInstalled', + 'revalidate' => '$refresh', + ]; protected $rules = [ 'server.name' => 'required', @@ -37,7 +41,6 @@ class Form extends Component 'server.settings.is_swarm_manager' => 'required|boolean', 'server.settings.is_swarm_worker' => 'required|boolean', 'server.settings.is_build_server' => 'required|boolean', - 'server.settings.is_force_cleanup_enabled' => 'required|boolean', 'server.settings.concurrent_builds' => 'required|integer|min:1', 'server.settings.dynamic_timeout' => 'required|integer|min:1', 'server.settings.is_metrics_enabled' => 'required|boolean', @@ -46,6 +49,10 @@ class Form extends Component 'server.settings.metrics_history_days' => 'required|integer|min:1', 'wildcard_domain' => 'nullable|url', 'server.settings.is_server_api_enabled' => 'required|boolean', + 'server.settings.server_timezone' => 'required|string|timezone', + 'server.settings.force_docker_cleanup' => 'required|boolean', + 'server.settings.docker_cleanup_frequency' => 'required_if:server.settings.force_docker_cleanup,true|string', + 'server.settings.docker_cleanup_threshold' => 'required_if:server.settings.force_docker_cleanup,false|integer|min:1|max:100', ]; protected $validationAttributes = [ @@ -66,12 +73,27 @@ class Form extends Component 'server.settings.metrics_refresh_rate_seconds' => 'Metrics Interval', 'server.settings.metrics_history_days' => 'Metrics History', 'server.settings.is_server_api_enabled' => 'Server API', + 'server.settings.server_timezone' => 'Server Timezone', ]; - public function mount() + public function mount(Server $server) { + $this->server = $server; + $this->timezones = collect(timezone_identifiers_list())->sort()->values()->toArray(); $this->wildcard_domain = $this->server->settings->wildcard_domain; - $this->cleanup_after_percentage = $this->server->settings->cleanup_after_percentage; + $this->server->settings->docker_cleanup_threshold = $this->server->settings->docker_cleanup_threshold; + $this->server->settings->docker_cleanup_frequency = $this->server->settings->docker_cleanup_frequency; + } + + public function updated($field) + { + if ($field === 'server.settings.docker_cleanup_frequency') { + $frequency = $this->server->settings->docker_cleanup_frequency; + if (empty($frequency) || ! validate_cron_expression($frequency)) { + $this->dispatch('error', 'Invalid Cron / Human expression for Docker Cleanup Frequency. Resetting to default 10 minutes.'); + $this->server->settings->docker_cleanup_frequency = '*/10 * * * *'; + } + } } public function serverInstalled() @@ -116,7 +138,6 @@ class Form extends Component } if ($this->server->settings->isDirty('is_server_api_enabled') && $this->server->settings->is_server_api_enabled === true) { ray('Starting sentinel'); - } } else { ray('Sentinel is not enabled'); @@ -172,27 +193,49 @@ class Form extends Component public function submit() { - if (isCloud() && ! isDev()) { - $this->validate(); - $this->validate([ - 'server.ip' => 'required', - ]); - } else { - $this->validate(); - } - $uniqueIPs = Server::all()->reject(function (Server $server) { - return $server->id === $this->server->id; - })->pluck('ip')->toArray(); - if (in_array($this->server->ip, $uniqueIPs)) { - $this->dispatch('error', 'IP address is already in use by another team.'); + try { + if (isCloud() && ! isDev()) { + $this->validate(); + $this->validate([ + 'server.ip' => 'required', + ]); + } else { + $this->validate(); + } + $uniqueIPs = Server::all()->reject(function (Server $server) { + return $server->id === $this->server->id; + })->pluck('ip')->toArray(); + if (in_array($this->server->ip, $uniqueIPs)) { + $this->dispatch('error', 'IP address is already in use by another team.'); - return; + return; + } + refresh_server_connection($this->server->privateKey); + $this->server->settings->wildcard_domain = $this->wildcard_domain; + if ($this->server->settings->force_docker_cleanup) { + $this->server->settings->docker_cleanup_frequency = $this->server->settings->docker_cleanup_frequency; + } else { + $this->server->settings->docker_cleanup_threshold = $this->server->settings->docker_cleanup_threshold; + } + $currentTimezone = $this->server->settings->getOriginal('server_timezone'); + $newTimezone = $this->server->settings->server_timezone; + if ($currentTimezone !== $newTimezone || $currentTimezone === '') { + $this->server->settings->server_timezone = $newTimezone; + $this->server->settings->save(); + } + + $this->server->settings->save(); + $this->server->save(); + $this->dispatch('success', 'Server updated.'); + } catch (\Throwable $e) { + return handleError($e, $this); } - refresh_server_connection($this->server->privateKey); - $this->server->settings->wildcard_domain = $this->wildcard_domain; - $this->server->settings->cleanup_after_percentage = $this->cleanup_after_percentage; + } + + public function updatedServerSettingsServerTimezone($value) + { + $this->server->settings->server_timezone = $value; $this->server->settings->save(); - $this->server->save(); - $this->dispatch('success', 'Server updated.'); + $this->dispatch('success', 'Server timezone updated.'); } } diff --git a/app/Livewire/Server/New/ByIp.php b/app/Livewire/Server/New/ByIp.php index 5f69835d7..f80152435 100644 --- a/app/Livewire/Server/New/ByIp.php +++ b/app/Livewire/Server/New/ByIp.php @@ -2,10 +2,10 @@ namespace App\Livewire\Server\New; -use App\Enums\ProxyStatus; use App\Enums\ProxyTypes; use App\Models\Server; use App\Models\Team; +use Illuminate\Support\Collection; use Livewire\Component; class ByIp extends Component @@ -40,7 +40,7 @@ class ByIp extends Component public bool $is_build_server = false; - public $swarm_managers = []; + public Collection $swarm_managers; protected $rules = [ 'name' => 'required|string', @@ -102,11 +102,6 @@ class ByIp extends Component 'port' => $this->port, 'team_id' => currentTeam()->id, 'private_key_id' => $this->private_key_id, - 'proxy' => [ - // set default proxy type to traefik v2 - 'type' => ProxyTypes::TRAEFIK->value, - 'status' => ProxyStatus::EXITED->value, - ], ]; if ($this->is_swarm_worker) { $payload['swarm_cluster'] = $this->selected_swarm_cluster; @@ -115,6 +110,9 @@ class ByIp extends Component data_forget($payload, 'proxy'); } $server = Server::create($payload); + $server->proxy->set('status', 'exited'); + $server->proxy->set('type', ProxyTypes::TRAEFIK->value); + $server->save(); if ($this->is_build_server) { $this->is_swarm_manager = false; $this->is_swarm_worker = false; diff --git a/app/Livewire/Server/Show.php b/app/Livewire/Server/Show.php index 0751b186e..a5e94a19a 100644 --- a/app/Livewire/Server/Show.php +++ b/app/Livewire/Server/Show.php @@ -14,7 +14,7 @@ class Show extends Component public $parameters = []; - protected $listeners = ['refreshServerShow' => '$refresh']; + protected $listeners = ['refreshServerShow']; public function mount() { @@ -29,6 +29,12 @@ class Show extends Component } } + public function refreshServerShow() + { + $this->server->refresh(); + $this->dispatch('$refresh'); + } + public function submit() { $this->dispatch('serverRefresh', false); diff --git a/app/Livewire/Settings/Index.php b/app/Livewire/Settings/Index.php index f593fb78b..c52970258 100644 --- a/app/Livewire/Settings/Index.php +++ b/app/Livewire/Settings/Index.php @@ -40,6 +40,7 @@ class Index extends Component 'settings.is_auto_update_enabled' => 'boolean', 'auto_update_frequency' => 'string', 'update_check_frequency' => 'string', + 'settings.instance_timezone' => 'required|string|timezone', ]; protected $validationAttributes = [ @@ -54,6 +55,8 @@ class Index extends Component 'update_check_frequency' => 'Update Check Frequency', ]; + public $timezones; + public function mount() { if (isInstanceAdmin()) { @@ -65,6 +68,7 @@ class Index extends Component $this->is_api_enabled = $this->settings->is_api_enabled; $this->auto_update_frequency = $this->settings->auto_update_frequency; $this->update_check_frequency = $this->settings->update_check_frequency; + $this->timezones = collect(timezone_identifiers_list())->sort()->values()->toArray(); } else { return redirect()->route('dashboard'); } @@ -166,6 +170,13 @@ class Index extends Component } } + public function updatedSettingsInstanceTimezone($value) + { + $this->settings->instance_timezone = $value; + $this->settings->save(); + $this->dispatch('success', 'Instance timezone updated.'); + } + public function render() { return view('livewire.settings.index'); diff --git a/app/Livewire/SharedVariables/Environment/Show.php b/app/Livewire/SharedVariables/Environment/Show.php index e025d8f7c..daf1df212 100644 --- a/app/Livewire/SharedVariables/Environment/Show.php +++ b/app/Livewire/SharedVariables/Environment/Show.php @@ -16,7 +16,7 @@ class Show extends Component public array $parameters; - protected $listeners = ['refreshEnvs' => '$refresh', 'saveKey' => 'saveKey']; + protected $listeners = ['refreshEnvs' => '$refresh', 'saveKey']; public function saveKey($data) { diff --git a/app/Livewire/Terminal/Index.php b/app/Livewire/Terminal/Index.php new file mode 100644 index 000000000..945b25714 --- /dev/null +++ b/app/Livewire/Terminal/Index.php @@ -0,0 +1,76 @@ +user()->isAdmin()) { + abort(403); + } + $this->servers = Server::isReachable()->get(); + $this->containers = $this->getAllActiveContainers(); + } + + private function getAllActiveContainers() + { + return collect($this->servers)->flatMap(function ($server) { + if (! $server->isFunctional()) { + return []; + } + + return $server->loadAllContainers()->map(function ($container) use ($server) { + $state = data_get_str($container, 'State')->lower(); + if ($state->contains('running')) { + return [ + 'name' => data_get($container, 'Names'), + 'connection_name' => data_get($container, 'Names'), + 'uuid' => data_get($container, 'Names'), + 'status' => data_get_str($container, 'State')->lower(), + 'server' => $server, + 'server_uuid' => $server->uuid, + ]; + } + + return null; + })->filter(); + }); + } + + public function updatedSelectedUuid() + { + $this->connectToContainer(); + } + + #[On('connectToContainer')] + public function connectToContainer() + { + if ($this->selected_uuid === 'default') { + $this->dispatch('error', 'Please select a server or a container.'); + + return; + } + $container = collect($this->containers)->firstWhere('uuid', $this->selected_uuid); + $this->dispatch('send-terminal-command', + isset($container), + $container['connection_name'] ?? $this->selected_uuid, + $container['server_uuid'] ?? $this->selected_uuid + ); + } + + public function render() + { + return view('livewire.terminal.index'); + } +} diff --git a/app/Livewire/Upgrade.php b/app/Livewire/Upgrade.php index da7b5860d..dfbd945f5 100644 --- a/app/Livewire/Upgrade.php +++ b/app/Livewire/Upgrade.php @@ -4,7 +4,6 @@ namespace App\Livewire; use App\Actions\Server\UpdateCoolify; use App\Models\InstanceSettings; -use Illuminate\Support\Facades\Http; use Livewire\Component; class Upgrade extends Component @@ -22,13 +21,8 @@ class Upgrade extends Component public function checkUpdate() { try { - $settings = InstanceSettings::get(); - $response = Http::retry(3, 1000)->get('https://cdn.coollabs.io/coolify/versions.json'); - if ($response->successful()) { - $versions = $response->json(); - $this->latestVersion = data_get($versions, 'coolify.v4.version'); - } - $this->isUpgradeAvailable = $settings->new_version_available; + $this->latestVersion = get_latest_version_of_coolify(); + $this->isUpgradeAvailable = data_get(InstanceSettings::get(), 'new_version_available', false); } catch (\Throwable $e) { return handleError($e, $this); diff --git a/app/Models/Application.php b/app/Models/Application.php index 2873ee7da..8c7520eaf 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -104,6 +104,8 @@ class Application extends BaseModel { use SoftDeletes; + private static $parserVersion = '3'; + protected $guarded = []; protected $appends = ['server_status']; @@ -127,7 +129,7 @@ class Application extends BaseModel ApplicationSetting::create([ 'application_id' => $application->id, ]); - $application->compose_parsing_version = '2'; + $application->compose_parsing_version = self::$parserVersion; $application->save(); }); static::forceDeleting(function ($application) { @@ -140,6 +142,7 @@ class Application extends BaseModel $task->delete(); } $application->tags()->detach(); + $application->previews()->delete(); }); } @@ -474,23 +477,6 @@ class Application extends BaseModel ); } - public function dockerComposePrLocation(): Attribute - { - return Attribute::make( - set: function ($value) { - if (is_null($value) || $value === '') { - return '/docker-compose.yaml'; - } else { - if ($value !== '/') { - return Str::start(Str::replaceEnd('/', '', $value), '/'); - } - - return Str::start($value, '/'); - } - } - ); - } - public function baseDirectory(): Attribute { return Attribute::make( @@ -541,12 +527,12 @@ class Application extends BaseModel $main_server_status = $this->destination->server->isFunctional(); foreach ($additional_servers_status as $status) { $server_status = str($status)->before(':')->value(); - if ($main_server_status !== $server_status) { + if ($server_status !== 'running') { return false; } } - return true; + return $main_server_status; } } ); @@ -1102,7 +1088,7 @@ class Application extends BaseModel } } - public function parseRawCompose() + public function oldRawParser() { try { $yaml = Yaml::parse($this->docker_compose_raw); @@ -1162,9 +1148,11 @@ class Application extends BaseModel instant_remote_process($commands, $this->destination->server, false); } - public function parseCompose(int $pull_request_id = 0, ?int $preview_id = null) + public function parse(int $pull_request_id = 0, ?int $preview_id = null) { - if ($this->docker_compose_raw) { + if ($this->compose_parsing_version === '3') { + return newParser($this, $pull_request_id, $preview_id); + } elseif ($this->docker_compose_raw) { return parseDockerComposeFile(resource: $this, isNew: false, pull_request_id: $pull_request_id, preview_id: $preview_id); } else { return collect([]); @@ -1216,7 +1204,7 @@ class Application extends BaseModel if ($composeFileContent) { $this->docker_compose_raw = $composeFileContent; $this->save(); - $parsedServices = $this->parseCompose(); + $parsedServices = $this->parse(); if ($this->docker_compose_domains) { $json = collect(json_decode($this->docker_compose_domains)); $names = collect(data_get($parsedServices, 'services'))->keys()->toArray(); diff --git a/app/Models/ApplicationPreview.php b/app/Models/ApplicationPreview.php index 57d20e3aa..04a0ab27e 100644 --- a/app/Models/ApplicationPreview.php +++ b/app/Models/ApplicationPreview.php @@ -12,9 +12,9 @@ class ApplicationPreview extends BaseModel protected static function booted() { static::deleting(function ($preview) { - if ($preview->application->build_pack === 'dockercompose') { + if (data_get($preview, 'application.build_pack') === 'dockercompose') { $server = $preview->application->destination->server; - $composeFile = $preview->application->parseCompose(pull_request_id: $preview->pull_request_id); + $composeFile = $preview->application->parse(pull_request_id: $preview->pull_request_id); $volumes = data_get($composeFile, 'volumes'); $networks = data_get($composeFile, 'networks'); $networkKeys = collect($networks)->keys(); diff --git a/app/Models/EnvironmentVariable.php b/app/Models/EnvironmentVariable.php index 5e1d8ae13..138775aba 100644 --- a/app/Models/EnvironmentVariable.php +++ b/app/Models/EnvironmentVariable.php @@ -6,7 +6,6 @@ use App\Models\EnvironmentVariable as ModelsEnvironmentVariable; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Model; use OpenApi\Attributes as OA; -use Symfony\Component\Yaml\Yaml; use Visus\Cuid2\Cuid2; #[OA\Schema( @@ -97,8 +96,22 @@ class EnvironmentVariable extends Model $resource = Application::find($this->application_id); } elseif ($this->service_id) { $resource = Service::find($this->service_id); - } elseif ($this->database_id) { - $resource = getResourceByUuid($this->parameters['database_uuid'], data_get(auth()->user()->currentTeam(), 'id')); + } elseif ($this->standalone_postgresql_id) { + $resource = StandalonePostgresql::find($this->standalone_postgresql_id); + } elseif ($this->standalone_redis_id) { + $resource = StandaloneRedis::find($this->standalone_redis_id); + } elseif ($this->standalone_mongodb_id) { + $resource = StandaloneMongodb::find($this->standalone_mongodb_id); + } elseif ($this->standalone_mysql_id) { + $resource = StandaloneMysql::find($this->standalone_mysql_id); + } elseif ($this->standalone_mariadb_id) { + $resource = StandaloneMariadb::find($this->standalone_mariadb_id); + } elseif ($this->standalone_keydb_id) { + $resource = StandaloneKeydb::find($this->standalone_keydb_id); + } elseif ($this->standalone_dragonfly_id) { + $resource = StandaloneDragonfly::find($this->standalone_dragonfly_id); + } elseif ($this->standalone_clickhouse_id) { + $resource = StandaloneClickhouse::find($this->standalone_clickhouse_id); } return $resource; @@ -122,63 +135,6 @@ class EnvironmentVariable extends Model ); } - protected function isFoundInCompose(): Attribute - { - return Attribute::make( - get: function () { - if (! $this->application_id) { - return true; - } - $found_in_compose = false; - $found_in_args = false; - $resource = $this->resource(); - $compose = data_get($resource, 'docker_compose_raw'); - if (! $compose) { - return true; - } - $yaml = Yaml::parse($compose); - $services = collect(data_get($yaml, 'services')); - if ($services->isEmpty()) { - return false; - } - foreach ($services as $service) { - $environments = collect(data_get($service, 'environment')); - $args = collect(data_get($service, 'build.args')); - if ($environments->isEmpty() && $args->isEmpty()) { - $found_in_compose = false; - break; - } - - $found_in_compose = $environments->contains(function ($item) { - if (str($item)->contains('=')) { - $item = str($item)->before('='); - } - - return strpos($item, $this->key) !== false; - }); - - if ($found_in_compose) { - break; - } - - $found_in_args = $args->contains(function ($item) { - if (str($item)->contains('=')) { - $item = str($item)->before('='); - } - - return strpos($item, $this->key) !== false; - }); - - if ($found_in_args) { - break; - } - } - - return $found_in_compose || $found_in_args; - } - ); - } - protected function isShared(): Attribute { return Attribute::make( @@ -201,8 +157,10 @@ class EnvironmentVariable extends Model $environment_variable = trim($environment_variable); $sharedEnvsFound = str($environment_variable)->matchAll('/{{(.*?)}}/'); if ($sharedEnvsFound->isEmpty()) { + return $environment_variable; } + foreach ($sharedEnvsFound as $sharedEnv) { $type = str($sharedEnv)->match('/(.*?)\./'); if (! collect(SHARED_VARIABLE_TYPES)->contains($type)) { diff --git a/app/Models/InstanceSettings.php b/app/Models/InstanceSettings.php index 5bd421956..27a181ee4 100644 --- a/app/Models/InstanceSettings.php +++ b/app/Models/InstanceSettings.php @@ -37,6 +37,30 @@ class InstanceSettings extends Model implements SendsEmail ); } + public function updateCheckFrequency(): Attribute + { + return Attribute::make( + set: function ($value) { + return translate_cron_expression($value); + }, + get: function ($value) { + return translate_cron_expression($value); + } + ); + } + + public function autoUpdateFrequency(): Attribute + { + return Attribute::make( + set: function ($value) { + return translate_cron_expression($value); + }, + get: function ($value) { + return translate_cron_expression($value); + } + ); + } + public static function get() { return InstanceSettings::findOrFail(0); diff --git a/app/Models/LocalFileVolume.php b/app/Models/LocalFileVolume.php index a436f5797..d528099ff 100644 --- a/app/Models/LocalFileVolume.php +++ b/app/Models/LocalFileVolume.php @@ -24,8 +24,9 @@ class LocalFileVolume extends BaseModel return $this->morphTo('resource'); } - public function deleteStorageOnServer() + public function loadStorageOnServer() { + $this->load(['service']); $isService = data_get($this->resource, 'service'); if ($isService) { $workdir = $this->resource->service->workdir(); @@ -35,17 +36,46 @@ class LocalFileVolume extends BaseModel $server = $this->resource->destination->server; } $commands = collect([]); - $fs_path = data_get($this, 'fs_path'); - $isFile = instant_remote_process(["test -f $fs_path && echo OK || echo NOK"], $server); - $isDir = instant_remote_process(["test -d $fs_path && echo OK || echo NOK"], $server); - if ($fs_path && $fs_path != '/' && $fs_path != '.' && $fs_path != '..') { - ray($isFile, $isDir); + $path = data_get_str($this, 'fs_path'); + if ($path->startsWith('.')) { + $path = $path->after('.'); + $path = $workdir.$path; + } + $isFile = instant_remote_process(["test -f $path && echo OK || echo NOK"], $server); + if ($isFile === 'OK') { + $content = instant_remote_process(["cat $path"], $server, false); + $this->content = $content; + $this->is_directory = false; + $this->save(); + } + } + + public function deleteStorageOnServer() + { + $this->load(['service']); + $isService = data_get($this->resource, 'service'); + if ($isService) { + $workdir = $this->resource->service->workdir(); + $server = $this->resource->service->server; + } else { + $workdir = $this->resource->workdir(); + $server = $this->resource->destination->server; + } + $commands = collect([]); + $path = data_get_str($this, 'fs_path'); + if ($path->startsWith('.')) { + $path = $path->after('.'); + $path = $workdir.$path; + } + $isFile = instant_remote_process(["test -f $path && echo OK || echo NOK"], $server); + $isDir = instant_remote_process(["test -d $path && echo OK || echo NOK"], $server); + if ($path && $path != '/' && $path != '.' && $path != '..') { if ($isFile === 'OK') { - $commands->push("rm -rf $fs_path > /dev/null 2>&1 || true"); + $commands->push("rm -rf $path > /dev/null 2>&1 || true"); } elseif ($isDir === 'OK') { - $commands->push("rm -rf $fs_path > /dev/null 2>&1 || true"); - $commands->push("rmdir $fs_path > /dev/null 2>&1 || true"); + $commands->push("rm -rf $path > /dev/null 2>&1 || true"); + $commands->push("rmdir $path > /dev/null 2>&1 || true"); } } if ($commands->count() > 0) { @@ -55,6 +85,7 @@ class LocalFileVolume extends BaseModel public function saveStorageOnServer() { + $this->load(['service']); $isService = data_get($this->resource, 'service'); if ($isService) { $workdir = $this->resource->service->workdir(); @@ -74,30 +105,36 @@ class LocalFileVolume extends BaseModel $commands->push("mkdir -p $parent_dir > /dev/null 2>&1 || true"); } } - $fileVolume = $this; - $path = str(data_get($fileVolume, 'fs_path')); - $content = data_get($fileVolume, 'content'); + $path = data_get_str($this, 'fs_path'); + $content = data_get($this, 'content'); if ($path->startsWith('.')) { $path = $path->after('.'); $path = $workdir.$path; } $isFile = instant_remote_process(["test -f $path && echo OK || echo NOK"], $server); $isDir = instant_remote_process(["test -d $path && echo OK || echo NOK"], $server); - if ($isFile == 'OK' && $fileVolume->is_directory) { + if ($isFile == 'OK' && $this->is_directory) { $content = instant_remote_process(["cat $path"], $server, false); - $fileVolume->is_directory = false; - $fileVolume->content = $content; - $fileVolume->save(); + $this->is_directory = false; + $this->content = $content; + $this->save(); FileStorageChanged::dispatch(data_get($server, 'team_id')); throw new \Exception('The following file is a file on the server, but you are trying to mark it as a directory. Please delete the file on the server or mark it as directory.'); - } elseif ($isDir == 'OK' && ! $fileVolume->is_directory) { - $fileVolume->is_directory = true; - $fileVolume->save(); - throw new \Exception('The following file is a directory on the server, but you are trying to mark it as a file.

Please delete the directory on the server or mark it as directory.'); + } elseif ($isDir == 'OK' && ! $this->is_directory) { + if ($path == '/' || $path == '.' || $path == '..' || $path == '' || str($path)->isEmpty() || is_null($path)) { + $this->is_directory = true; + $this->save(); + throw new \Exception('The following file is a directory on the server, but you are trying to mark it as a file.

Please delete the directory on the server or mark it as directory.'); + } + instant_remote_process([ + "rm -fr $path", + "touch $path", + ], $server, false); + FileStorageChanged::dispatch(data_get($server, 'team_id')); } - if ($isDir == 'NOK' && ! $fileVolume->is_directory) { - $chmod = data_get($fileVolume, 'chmod'); - $chown = data_get($fileVolume, 'chown'); + if ($isDir == 'NOK' && ! $this->is_directory) { + $chmod = data_get($this, 'chmod'); + $chown = data_get($this, 'chown'); if ($content) { $content = base64_encode($content); $commands->push("echo '$content' | base64 -d | tee $path > /dev/null"); @@ -111,7 +148,7 @@ class LocalFileVolume extends BaseModel if ($chmod) { $commands->push("chmod $chmod $path"); } - } elseif ($isDir == 'NOK' && $fileVolume->is_directory) { + } elseif ($isDir == 'NOK' && $this->is_directory) { $commands->push("mkdir -p $path > /dev/null 2>&1 || true"); } diff --git a/app/Models/Project.php b/app/Models/Project.php index 77f62d770..18481751c 100644 --- a/app/Models/Project.php +++ b/app/Models/Project.php @@ -11,6 +11,7 @@ use OpenApi\Attributes as OA; 'id' => ['type' => 'integer'], 'uuid' => ['type' => 'string'], 'name' => ['type' => 'string'], + 'description' => ['type' => 'string'], 'environments' => new OA\Property( property: 'environments', type: 'array', diff --git a/app/Models/ScheduledDatabaseBackup.php b/app/Models/ScheduledDatabaseBackup.php index edd840e7d..50a0c8173 100644 --- a/app/Models/ScheduledDatabaseBackup.php +++ b/app/Models/ScheduledDatabaseBackup.php @@ -22,7 +22,8 @@ class ScheduledDatabaseBackup extends BaseModel public function executions(): HasMany { - return $this->hasMany(ScheduledDatabaseBackupExecution::class); + // Last execution first + return $this->hasMany(ScheduledDatabaseBackupExecution::class)->orderBy('created_at', 'desc'); } public function s3() @@ -34,4 +35,14 @@ class ScheduledDatabaseBackup extends BaseModel { return $this->hasMany(ScheduledDatabaseBackupExecution::class)->where('created_at', '>=', now()->subDays($days))->get(); } + public function server() + { + if ($this->database) { + if ($this->database->destination && $this->database->destination->server) { + $server = $this->database->destination->server; + return $server; + } + } + return null; + } } diff --git a/app/Models/ScheduledTask.php b/app/Models/ScheduledTask.php index 1cb805e8e..82f0036a5 100644 --- a/app/Models/ScheduledTask.php +++ b/app/Models/ScheduledTask.php @@ -4,6 +4,8 @@ namespace App\Models; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasOne; +use App\Models\Service; +use App\Models\Application; class ScheduledTask extends BaseModel { @@ -26,6 +28,28 @@ class ScheduledTask extends BaseModel public function executions(): HasMany { - return $this->hasMany(ScheduledTaskExecution::class); + // Last execution first + return $this->hasMany(ScheduledTaskExecution::class)->orderBy('created_at', 'desc'); + } + + public function server() + { + if ($this->application) { + if ($this->application->destination && $this->application->destination->server) { + $server = $this->application->destination->server; + return $server; + } + } elseif ($this->service) { + if ($this->service->destination && $this->service->destination->server) { + $server = $this->service->destination->server; + return $server; + } + } elseif ($this->database) { + if ($this->database->destination && $this->database->destination->server) { + $server = $this->database->destination->server; + return $server; + } + } + return null; } } diff --git a/app/Models/Server.php b/app/Models/Server.php index 8a7325beb..90e6ade14 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -112,6 +112,16 @@ class Server extends BaseModel 'proxy', ]; + protected $fillable = [ + 'name', + 'ip', + 'port', + 'user', + 'description', + 'private_key_id', + 'team_id', + ]; + protected $guarded = []; public static function isReachable() @@ -295,6 +305,13 @@ respond 404 'service' => 'coolify-realtime', 'rule' => "Host(`{$host}`) && PathPrefix(`/app`)", ], + 'coolify-terminal-ws' => [ + 'entryPoints' => [ + 0 => 'http', + ], + 'service' => 'coolify-terminal', + 'rule' => "Host(`{$host}`) && PathPrefix(`/terminal/ws`)", + ], ], 'services' => [ 'coolify' => [ @@ -315,6 +332,15 @@ respond 404 ], ], ], + 'coolify-terminal' => [ + 'loadBalancer' => [ + 'servers' => [ + 0 => [ + 'url' => 'http://coolify-realtime:6002', + ], + ], + ], + ], ], ], ]; @@ -344,6 +370,16 @@ respond 404 'certresolver' => 'letsencrypt', ], ]; + $traefik_dynamic_conf['http']['routers']['coolify-terminal-wss'] = [ + 'entryPoints' => [ + 0 => 'https', + ], + 'service' => 'coolify-terminal', + 'rule' => "Host(`{$host}`) && PathPrefix(`/terminal/ws`)", + 'tls' => [ + 'certresolver' => 'letsencrypt', + ], + ]; } $yaml = Yaml::dump($traefik_dynamic_conf, 12, 2); $yaml = @@ -377,6 +413,9 @@ $schema://$host { handle /app/* { reverse_proxy coolify-realtime:6001 } + handle /terminal/ws { + reverse_proxy coolify-realtime:6002 + } reverse_proxy coolify:80 }"; $base64 = base64_encode($caddy_file); @@ -649,7 +688,7 @@ $schema://$host { } } - public function getDiskUsage() + public function getDiskUsage(): ?string { return instant_remote_process(["df /| tail -1 | awk '{ print $5}' | sed 's/%//g'"], $this, false); } @@ -736,6 +775,18 @@ $schema://$host { } } + public function loadAllContainers(): Collection + { + if ($this->isFunctional()) { + $containers = instant_remote_process(["docker ps -a --format '{{json .}}'"], $this); + $containers = format_docker_command_output_to_json($containers); + + return collect($containers); + } + + return collect([]); + } + public function loadUnmanagedContainers(): Collection { if ($this->isFunctional()) { @@ -880,7 +931,7 @@ $schema://$host { public function muxFilename() { - return "{$this->ip}_{$this->port}_{$this->user}"; + return $this->uuid; } public function team() @@ -957,7 +1008,7 @@ $schema://$host { public function validateConnection() { - config()->set('coolify.mux_enabled', false); + config()->set('constants.ssh.mux_enabled', false); $server = Server::find($this->id); if (! $server) { diff --git a/app/Models/ServerSetting.php b/app/Models/ServerSetting.php index c39982b91..c44a393b4 100644 --- a/app/Models/ServerSetting.php +++ b/app/Models/ServerSetting.php @@ -2,6 +2,7 @@ namespace App\Models; +use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Model; use OpenApi\Attributes as OA; @@ -10,10 +11,10 @@ use OpenApi\Attributes as OA; type: 'object', properties: [ 'id' => ['type' => 'integer'], - 'cleanup_after_percentage' => ['type' => 'integer'], 'concurrent_builds' => ['type' => 'integer'], 'dynamic_timeout' => ['type' => 'integer'], 'force_disabled' => ['type' => 'boolean'], + 'force_server_cleanup' => ['type' => 'boolean'], 'is_build_server' => ['type' => 'boolean'], 'is_cloudflare_tunnel' => ['type' => 'boolean'], 'is_jump_server' => ['type' => 'boolean'], @@ -37,6 +38,8 @@ use OpenApi\Attributes as OA; 'metrics_history_days' => ['type' => 'integer'], 'metrics_refresh_rate_seconds' => ['type' => 'integer'], 'metrics_token' => ['type' => 'string'], + 'docker_cleanup_frequency' => ['type' => 'string'], + 'docker_cleanup_threshold' => ['type' => 'integer'], 'server_id' => ['type' => 'integer'], 'wildcard_domain' => ['type' => 'string'], 'created_at' => ['type' => 'string'], @@ -47,8 +50,25 @@ class ServerSetting extends Model { protected $guarded = []; + protected $casts = [ + 'force_docker_cleanup' => 'boolean', + 'docker_cleanup_threshold' => 'integer', + ]; + public function server() { return $this->belongsTo(Server::class); } + + public function dockerCleanupFrequency(): Attribute + { + return Attribute::make( + set: function ($value) { + return translate_cron_expression($value); + }, + get: function ($value) { + return translate_cron_expression($value); + } + ); + } } diff --git a/app/Models/Service.php b/app/Models/Service.php index 64796283c..80464db7d 100644 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -9,9 +9,10 @@ use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Support\Collection; use Illuminate\Support\Facades\Process; use Illuminate\Process\InvokedProcess; +use Illuminate\Support\Facades\Storage; use OpenApi\Attributes as OA; use Spatie\Url\Url; -use Symfony\Component\Yaml\Yaml; +use Visus\Cuid2\Cuid2; #[OA\Schema( description: 'Service model', @@ -25,6 +26,7 @@ use Symfony\Component\Yaml\Yaml; 'description' => ['type' => 'string', 'description' => 'The description of the service.'], 'docker_compose_raw' => ['type' => 'string', 'description' => 'The raw docker-compose.yml file of the service.'], 'docker_compose' => ['type' => 'string', 'description' => 'The docker-compose.yml file that is parsed and modified by Coolify.'], + 'destination_type' => ['type' => 'string', 'description' => 'Destination type.'], 'destination_id' => ['type' => 'integer', 'description' => 'The unique identifier of the destination where the service is running.'], 'connect_to_docker_network' => ['type' => 'boolean', 'description' => 'The flag to connect the service to the predefined Docker network.'], 'is_container_label_escape_enabled' => ['type' => 'boolean', 'description' => 'The flag to enable the container label escape.'], @@ -40,10 +42,20 @@ class Service extends BaseModel { use HasFactory, SoftDeletes; + private static $parserVersion = '3'; + protected $guarded = []; protected $appends = ['server_status']; + protected static function booted() + { + static::created(function ($service) { + $service->compose_parsing_version = self::$parserVersion; + $service->save(); + }); + } + public function isConfigurationChanged(bool $save = false) { $domains = $this->applications()->get()->pluck('fqdn')->sort()->toArray(); @@ -272,6 +284,41 @@ class Service extends BaseModel foreach ($applications as $application) { $image = str($application->image)->before(':')->value(); switch ($image) { + case str($image)?->contains('rabbitmq'): + $data = collect([]); + $host_port = $this->environment_variables()->where('key', 'PORT')->first(); + $username = $this->environment_variables()->where('key', 'SERVICE_USER_RABBITMQ')->first(); + $password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_RABBITMQ')->first(); + if ($host_port) { + $data = $data->merge([ + 'Host Port Binding' => [ + 'key' => data_get($host_port, 'key'), + 'value' => data_get($host_port, 'value'), + 'rules' => 'required', + ], + ]); + } + if ($username) { + $data = $data->merge([ + 'Username' => [ + 'key' => data_get($username, 'key'), + 'value' => data_get($username, 'value'), + 'rules' => 'required', + ], + ]); + } + if ($password) { + $data = $data->merge([ + 'Password' => [ + 'key' => data_get($password, 'key'), + 'value' => data_get($password, 'value'), + 'rules' => 'required', + 'isPassword' => true, + ], + ]); + } + $fields->put('RabbitMQ', $data->toArray()); + break; case str($image)?->contains('tolgee'): $data = collect([]); $admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_TOLGEE')->first(); @@ -571,6 +618,9 @@ class Service extends BaseModel default: $data = collect([]); $admin_user = $this->environment_variables()->where('key', 'SERVICE_USER_ADMIN')->first(); + // Chaskiq + $admin_email = $this->environment_variables()->where('key', 'ADMIN_EMAIL')->first(); + $admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_ADMIN')->first(); if ($admin_user) { $data = $data->merge([ @@ -592,6 +642,15 @@ class Service extends BaseModel ], ]); } + if ($admin_email) { + $data = $data->merge([ + 'Email' => [ + 'key' => 'ADMIN_EMAIL', + 'value' => data_get($admin_email, 'value'), + 'rules' => 'required|email', + ], + ]); + } $fields->put('Admin', $data->toArray()); break; case str($image)?->contains('vaultwarden'): @@ -675,7 +734,7 @@ class Service extends BaseModel } $data = $data->merge([ 'Root User' => [ - 'key' => 'N/A', + 'key' => 'GITLAB_ROOT_USER', 'value' => 'root', 'rules' => 'required', 'isPassword' => true, @@ -684,6 +743,32 @@ class Service extends BaseModel $fields->put('GitLab', $data->toArray()); break; + case str($image)->contains('code-server'): + $data = collect([]); + $password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_64_PASSWORDCODESERVER')->first(); + if ($password) { + $data = $data->merge([ + 'Password' => [ + 'key' => data_get($password, 'key'), + 'value' => data_get($password, 'value'), + 'rules' => 'required', + 'isPassword' => true, + ], + ]); + } + $sudoPassword = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_SUDOCODESERVER')->first(); + if ($sudoPassword) { + $data = $data->merge([ + 'Sudo Password' => [ + 'key' => data_get($sudoPassword, 'key'), + 'value' => data_get($sudoPassword, 'value'), + 'rules' => 'required', + 'isPassword' => true, + ], + ]); + } + $fields->put('Code Server', $data->toArray()); + break; } } $databases = $this->databases()->get(); @@ -730,8 +815,8 @@ class Service extends BaseModel $fields->put('PostgreSQL', $data->toArray()); break; case str($image)->contains('mysql'): - $userVariables = ['SERVICE_USER_MYSQL', 'SERVICE_USER_WORDPRESS']; - $passwordVariables = ['SERVICE_PASSWORD_MYSQL', 'SERVICE_PASSWORD_WORDPRESS']; + $userVariables = ['SERVICE_USER_MYSQL', 'SERVICE_USER_WORDPRESS', 'MYSQL_USER']; + $passwordVariables = ['SERVICE_PASSWORD_MYSQL', 'SERVICE_PASSWORD_WORDPRESS', 'MYSQL_PASSWORD']; $rootPasswordVariables = ['SERVICE_PASSWORD_MYSQLROOT', 'SERVICE_PASSWORD_ROOT']; $dbNameVariables = ['MYSQL_DATABASE']; $mysql_user = $this->environment_variables()->whereIn('key', $userVariables)->first(); @@ -780,10 +865,10 @@ class Service extends BaseModel $fields->put('MySQL', $data->toArray()); break; case str($image)->contains('mariadb'): - $userVariables = ['SERVICE_USER_MARIADB', 'SERVICE_USER_WORDPRESS', '_APP_DB_USER']; - $passwordVariables = ['SERVICE_PASSWORD_MARIADB', 'SERVICE_PASSWORD_WORDPRESS', '_APP_DB_PASS']; - $rootPasswordVariables = ['SERVICE_PASSWORD_MARIADBROOT', 'SERVICE_PASSWORD_ROOT', '_APP_DB_ROOT_PASS']; - $dbNameVariables = ['SERVICE_DATABASE_MARIADB', 'SERVICE_DATABASE_WORDPRESS', '_APP_DB_SCHEMA']; + $userVariables = ['SERVICE_USER_MARIADB', 'SERVICE_USER_WORDPRESS', '_APP_DB_USER', 'SERVICE_USER_MYSQL', 'MYSQL_USER']; + $passwordVariables = ['SERVICE_PASSWORD_MARIADB', 'SERVICE_PASSWORD_WORDPRESS', '_APP_DB_PASS', 'MYSQL_PASSWORD']; + $rootPasswordVariables = ['SERVICE_PASSWORD_MARIADBROOT', 'SERVICE_PASSWORD_ROOT', '_APP_DB_ROOT_PASS', 'MYSQL_ROOT_PASSWORD']; + $dbNameVariables = ['SERVICE_DATABASE_MARIADB', 'SERVICE_DATABASE_WORDPRESS', '_APP_DB_SCHEMA', 'MYSQL_DATABASE']; $mariadb_user = $this->environment_variables()->whereIn('key', $userVariables)->first(); $mariadb_password = $this->environment_variables()->whereIn('key', $passwordVariables)->first(); $mariadb_root_password = $this->environment_variables()->whereIn('key', $rootPasswordVariables)->first(); @@ -830,6 +915,7 @@ class Service extends BaseModel } $fields->put('MariaDB', $data->toArray()); break; + } } @@ -964,7 +1050,8 @@ class Service extends BaseModel public function environment_variables(): HasMany { - return $this->hasMany(EnvironmentVariable::class)->orderBy('key', 'asc'); + + return $this->hasMany(EnvironmentVariable::class)->orderByRaw("key LIKE 'SERVICE%' DESC, value ASC"); } public function environment_variables_preview(): HasMany @@ -980,21 +1067,36 @@ class Service extends BaseModel public function saveComposeConfigs() { $workdir = $this->workdir(); - $commands[] = "mkdir -p $workdir"; + + instant_remote_process([ + "mkdir -p $workdir", + "cd $workdir", + ], $this->server); + + $filename = new Cuid2.'-docker-compose.yml'; + Storage::disk('local')->put("tmp/{$filename}", $this->docker_compose); + $path = Storage::path("tmp/{$filename}"); + instant_scp($path, "{$workdir}/docker-compose.yml", $this->server); + Storage::disk('local')->delete("tmp/{$filename}"); + $commands[] = "cd $workdir"; - - $json = Yaml::parse($this->docker_compose); - $this->docker_compose = Yaml::dump($json, 10, 2, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK); - $docker_compose_base64 = base64_encode($this->docker_compose); - - $commands[] = "echo $docker_compose_base64 | base64 -d | tee docker-compose.yml > /dev/null"; $commands[] = 'rm -f .env || true'; $envs_from_coolify = $this->environment_variables()->get(); - foreach ($envs_from_coolify as $env) { + $sorted = $envs_from_coolify->sortBy(function ($env) { + if (str($env->key)->startsWith('SERVICE_')) { + return 1; + } + if (str($env->value)->startsWith('$SERVICE_') || str($env->value)->startsWith('${SERVICE_')) { + return 2; + } + + return 3; + }); + foreach ($sorted as $env) { $commands[] = "echo '{$env->key}={$env->real_value}' >> .env"; } - if ($envs_from_coolify->count() === 0) { + if ($sorted->count() === 0) { $commands[] = 'touch .env'; } instant_remote_process($commands, $this->server); @@ -1002,7 +1104,14 @@ class Service extends BaseModel public function parse(bool $isNew = false): Collection { - return parseDockerComposeFile($this, $isNew); + if ($this->compose_parsing_version === '3') { + return newParser($this); + } elseif ($this->docker_compose_raw) { + return parseDockerComposeFile($this, $isNew); + } else { + return collect([]); + } + } public function networks() diff --git a/app/Models/ServiceApplication.php b/app/Models/ServiceApplication.php index 9df825869..f0c78cc08 100644 --- a/app/Models/ServiceApplication.php +++ b/app/Models/ServiceApplication.php @@ -32,6 +32,16 @@ class ServiceApplication extends BaseModel return ServiceApplication::whereRelation('service.environment.project.team', 'id', $teamId)->orderBy('name'); } + public function isRunning() + { + return str($this->status)->contains('running'); + } + + public function isExited() + { + return str($this->status)->contains('exited'); + } + public function isLogDrainEnabled() { return data_get($this, 'is_log_drain_enabled', false); diff --git a/app/Models/ServiceDatabase.php b/app/Models/ServiceDatabase.php index 7bbcb4f4e..2e25f4402 100644 --- a/app/Models/ServiceDatabase.php +++ b/app/Models/ServiceDatabase.php @@ -25,6 +25,16 @@ class ServiceDatabase extends BaseModel remote_process(["docker restart {$container_id}"], $this->service->server); } + public function isRunning() + { + return str($this->status)->contains('running'); + } + + public function isExited() + { + return str($this->status)->contains('exited'); + } + public function isLogDrainEnabled() { return data_get($this, 'is_log_drain_enabled', false); diff --git a/app/Models/StandaloneClickhouse.php b/app/Models/StandaloneClickhouse.php index 4cd194cd8..ee5c3becc 100644 --- a/app/Models/StandaloneClickhouse.php +++ b/app/Models/StandaloneClickhouse.php @@ -75,6 +75,11 @@ class StandaloneClickhouse extends BaseModel } } + public function isRunning() + { + return (bool) str($this->status)->contains('running'); + } + public function isExited() { return (bool) str($this->status)->startsWith('exited'); diff --git a/app/Models/StandaloneDragonfly.php b/app/Models/StandaloneDragonfly.php index 8726b2546..361abf110 100644 --- a/app/Models/StandaloneDragonfly.php +++ b/app/Models/StandaloneDragonfly.php @@ -75,6 +75,11 @@ class StandaloneDragonfly extends BaseModel } } + public function isRunning() + { + return (bool) str($this->status)->contains('running'); + } + public function isExited() { return (bool) str($this->status)->startsWith('exited'); diff --git a/app/Models/StandaloneKeydb.php b/app/Models/StandaloneKeydb.php index 607cacade..e05879371 100644 --- a/app/Models/StandaloneKeydb.php +++ b/app/Models/StandaloneKeydb.php @@ -75,6 +75,11 @@ class StandaloneKeydb extends BaseModel } } + public function isRunning() + { + return (bool) str($this->status)->contains('running'); + } + public function isExited() { return (bool) str($this->status)->startsWith('exited'); @@ -209,7 +214,7 @@ class StandaloneKeydb extends BaseModel protected function internalDbUrl(): Attribute { return new Attribute( - get: fn () => "redis://{$this->keydb_password}@{$this->uuid}:6379/0", + get: fn () => "redis://:{$this->keydb_password}@{$this->uuid}:6379/0", ); } @@ -218,7 +223,7 @@ class StandaloneKeydb extends BaseModel return new Attribute( get: function () { if ($this->is_public && $this->public_port) { - return "redis://{$this->keydb_password}@{$this->destination->server->getIp}:{$this->public_port}/0"; + return "redis://:{$this->keydb_password}@{$this->destination->server->getIp}:{$this->public_port}/0"; } return null; diff --git a/app/Models/StandaloneMariadb.php b/app/Models/StandaloneMariadb.php index d88653e41..c1e6c85d7 100644 --- a/app/Models/StandaloneMariadb.php +++ b/app/Models/StandaloneMariadb.php @@ -75,6 +75,11 @@ class StandaloneMariadb extends BaseModel } } + public function isRunning() + { + return (bool) str($this->status)->contains('running'); + } + public function isExited() { return (bool) str($this->status)->startsWith('exited'); diff --git a/app/Models/StandaloneMongodb.php b/app/Models/StandaloneMongodb.php index f09e932bf..e5ed0a5f4 100644 --- a/app/Models/StandaloneMongodb.php +++ b/app/Models/StandaloneMongodb.php @@ -79,6 +79,11 @@ class StandaloneMongodb extends BaseModel } } + public function isRunning() + { + return (bool) str($this->status)->contains('running'); + } + public function isExited() { return (bool) str($this->status)->startsWith('exited'); diff --git a/app/Models/StandaloneMysql.php b/app/Models/StandaloneMysql.php index f4e56fab2..bd4a7abb7 100644 --- a/app/Models/StandaloneMysql.php +++ b/app/Models/StandaloneMysql.php @@ -76,6 +76,11 @@ class StandaloneMysql extends BaseModel } } + public function isRunning() + { + return (bool) str($this->status)->contains('running'); + } + public function isExited() { return (bool) str($this->status)->startsWith('exited'); diff --git a/app/Models/StandalonePostgresql.php b/app/Models/StandalonePostgresql.php index 311c09c36..db771c7cd 100644 --- a/app/Models/StandalonePostgresql.php +++ b/app/Models/StandalonePostgresql.php @@ -102,6 +102,11 @@ class StandalonePostgresql extends BaseModel } } + public function isRunning() + { + return (bool) str($this->status)->contains('running'); + } + public function isExited() { return (bool) str($this->status)->startsWith('exited'); diff --git a/app/Models/StandaloneRedis.php b/app/Models/StandaloneRedis.php index 8a202ea9e..c524d4d03 100644 --- a/app/Models/StandaloneRedis.php +++ b/app/Models/StandaloneRedis.php @@ -71,6 +71,11 @@ class StandaloneRedis extends BaseModel } } + public function isRunning() + { + return (bool) str($this->status)->contains('running'); + } + public function isExited() { return (bool) str($this->status)->startsWith('exited'); diff --git a/app/Notifications/Server/DockerCleanup.php b/app/Notifications/Server/DockerCleanup.php index f8195ec1d..682ed7a1a 100644 --- a/app/Notifications/Server/DockerCleanup.php +++ b/app/Notifications/Server/DockerCleanup.php @@ -44,7 +44,7 @@ class DockerCleanup extends Notification implements ShouldQueue // $mail->view('emails.high-disk-usage', [ // 'name' => $this->server->name, // 'disk_usage' => $this->disk_usage, - // 'threshold' => $this->cleanup_after_percentage, + // 'threshold' => $this->docker_cleanup_threshold, // ]); // return $mail; // } diff --git a/app/Notifications/Server/HighDiskUsage.php b/app/Notifications/Server/HighDiskUsage.php index 3c68afe7b..34cb22091 100644 --- a/app/Notifications/Server/HighDiskUsage.php +++ b/app/Notifications/Server/HighDiskUsage.php @@ -17,7 +17,7 @@ class HighDiskUsage extends Notification implements ShouldQueue public $tries = 1; - public function __construct(public Server $server, public int $disk_usage, public int $cleanup_after_percentage) {} + public function __construct(public Server $server, public int $disk_usage, public int $docker_cleanup_threshold) {} public function via(object $notifiable): array { @@ -46,7 +46,7 @@ class HighDiskUsage extends Notification implements ShouldQueue $mail->view('emails.high-disk-usage', [ 'name' => $this->server->name, 'disk_usage' => $this->disk_usage, - 'threshold' => $this->cleanup_after_percentage, + 'threshold' => $this->docker_cleanup_threshold, ]); return $mail; @@ -54,7 +54,7 @@ class HighDiskUsage extends Notification implements ShouldQueue public function toDiscord(): string { - $message = "Coolify: Server '{$this->server->name}' high disk usage detected!\nDisk usage: {$this->disk_usage}%. Threshold: {$this->cleanup_after_percentage}%.\nPlease cleanup your disk to prevent data-loss.\nHere are some tips: https://coolify.io/docs/knowledge-base/server/automated-cleanup."; + $message = "Coolify: Server '{$this->server->name}' high disk usage detected!\nDisk usage: {$this->disk_usage}%. Threshold: {$this->docker_cleanup_threshold}%.\nPlease cleanup your disk to prevent data-loss.\nHere are some tips: https://coolify.io/docs/knowledge-base/server/automated-cleanup."; return $message; } @@ -62,7 +62,7 @@ class HighDiskUsage extends Notification implements ShouldQueue public function toTelegram(): array { return [ - 'message' => "Coolify: Server '{$this->server->name}' high disk usage detected!\nDisk usage: {$this->disk_usage}%. Threshold: {$this->cleanup_after_percentage}%.\nPlease cleanup your disk to prevent data-loss.\nHere are some tips: https://coolify.io/docs/knowledge-base/server/automated-cleanup.", + 'message' => "Coolify: Server '{$this->server->name}' high disk usage detected!\nDisk usage: {$this->disk_usage}%. Threshold: {$this->docker_cleanup_threshold}%.\nPlease cleanup your disk to prevent data-loss.\nHere are some tips: https://coolify.io/docs/knowledge-base/server/automated-cleanup.", ]; } } diff --git a/app/Providers/TelescopeServiceProvider.php b/app/Providers/TelescopeServiceProvider.php new file mode 100644 index 000000000..b7a336631 --- /dev/null +++ b/app/Providers/TelescopeServiceProvider.php @@ -0,0 +1,67 @@ +hideSensitiveRequestDetails(); + + $isLocal = $this->app->environment('local'); + + Telescope::filter(function (IncomingEntry $entry) use ($isLocal) { + return $isLocal || + $entry->isReportableException() || + $entry->isFailedRequest() || + $entry->isFailedJob() || + $entry->isScheduledTask() || + $entry->hasMonitoredTag(); + }); + } + + /** + * Prevent sensitive request details from being logged by Telescope. + */ + protected function hideSensitiveRequestDetails(): void + { + if ($this->app->environment('local')) { + return; + } + + Telescope::hideRequestParameters(['_token']); + + Telescope::hideRequestHeaders([ + 'cookie', + 'x-csrf-token', + 'x-xsrf-token', + ]); + } + + /** + * Register the Telescope gate. + * + * This gate determines who can access Telescope in non-local environments. + */ + protected function gate(): void + { + Gate::define('viewTelescope', function ($user) { + $root_user = User::find(0); + + return in_array($user->email, [ + $root_user->email, + ]); + }); + } +} diff --git a/bootstrap/helpers/applications.php b/bootstrap/helpers/applications.php index 1a08a46eb..b3e8011b9 100644 --- a/bootstrap/helpers/applications.php +++ b/bootstrap/helpers/applications.php @@ -91,7 +91,7 @@ function next_queuable(string $server_id, string $application_id): bool $server = Server::find($server_id); $concurrent_builds = $server->settings->concurrent_builds; - ray("serverId:{$server->id}", "concurrentBuilds:{$concurrent_builds}", "deployments:{$deployments->count()}", "sameApplicationDeployments:{$same_application_deployments->count()}"); + ray("serverId:{$server->id}", "concurrentBuilds:{$concurrent_builds}", "deployments:{$deployments->count()}", "sameApplicationDeployments:{$same_application_deployments->count()}")->green(); if ($deployments->count() > $concurrent_builds) { return false; diff --git a/bootstrap/helpers/constants.php b/bootstrap/helpers/constants.php index f94c9bc20..1eeec8f94 100644 --- a/bootstrap/helpers/constants.php +++ b/bootstrap/helpers/constants.php @@ -46,6 +46,7 @@ const SUPPORTED_OS = [ 'centos fedora rhel ol rocky amzn almalinux', 'sles opensuse-leap opensuse-tumbleweed', 'arch', + 'alpine', ]; const SHARED_VARIABLE_TYPES = ['team', 'project', 'environment']; diff --git a/bootstrap/helpers/databases.php b/bootstrap/helpers/databases.php index b8dcc1f3c..950eb67b6 100644 --- a/bootstrap/helpers/databases.php +++ b/bootstrap/helpers/databases.php @@ -19,7 +19,7 @@ function generate_database_name(string $type): string return $type.'-database-'.$cuid; } -function create_standalone_postgresql($environmentId, $destinationUuid, ?array $otherData = null): StandalonePostgresql +function create_standalone_postgresql($environmentId, $destinationUuid, ?array $otherData = null, string $databaseImage = 'postgres:16-alpine'): StandalonePostgresql { $destination = StandaloneDocker::where('uuid', $destinationUuid)->first(); if (! $destination) { @@ -27,6 +27,7 @@ function create_standalone_postgresql($environmentId, $destinationUuid, ?array $ } $database = new StandalonePostgresql; $database->name = generate_database_name('postgresql'); + $database->image = $databaseImage; $database->postgres_password = \Illuminate\Support\Str::password(length: 64, symbols: false); $database->environment_id = $environmentId; $database->destination_id = $destination->id; diff --git a/bootstrap/helpers/docker.php b/bootstrap/helpers/docker.php index a534dc5ff..8dce52f15 100644 --- a/bootstrap/helpers/docker.php +++ b/bootstrap/helpers/docker.php @@ -40,6 +40,20 @@ function getCurrentApplicationContainerStatus(Server $server, int $id, ?int $pul return $containers; } +function getCurrentServiceContainerStatus(Server $server, int $id): Collection +{ + $containers = collect([]); + if (! $server->isSwarm()) { + $containers = instant_remote_process(["docker ps -a --filter='label=coolify.serviceId={$id}' --format '{{json .}}' "], $server); + $containers = format_docker_command_output_to_json($containers); + $containers = $containers->filter(); + + return $containers; + } + + return $containers; +} + function format_docker_command_output_to_json($rawOutput): Collection { $outputLines = explode(PHP_EOL, $rawOutput); @@ -140,6 +154,8 @@ function getContainerStatus(Server $server, string $container_id, bool $all_data function generateApplicationContainerName(Application $application, $pull_request_id = 0) { + // TODO: refactor generateApplicationContainerName, we do not need $application and $pull_request_id + $consistent_container_name = $application->settings->is_consistent_container_name_enabled; $now = now()->format('Hisu'); if ($pull_request_id !== 0 && $pull_request_id !== null) { @@ -213,12 +229,12 @@ function generateServiceSpecificFqdns(ServiceApplication|Application $resource) } if (is_null($MINIO_BROWSER_REDIRECT_URL?->value)) { $MINIO_BROWSER_REDIRECT_URL?->update([ - 'value' => generateFqdn($server, 'console-'.$uuid), + 'value' => generateFqdn($server, 'console-'.$uuid, true), ]); } if (is_null($MINIO_SERVER_URL?->value)) { $MINIO_SERVER_URL?->update([ - 'value' => generateFqdn($server, 'minio-'.$uuid), + 'value' => generateFqdn($server, 'minio-'.$uuid, true), ]); } $payload = collect([ @@ -251,7 +267,7 @@ function generateServiceSpecificFqdns(ServiceApplication|Application $resource) return $payload; } -function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains, bool $is_force_https_enabled = false, $onlyPort = null, ?Collection $serviceLabels = null, ?bool $is_gzip_enabled = true, ?bool $is_stripprefix_enabled = true, ?string $service_name = null, ?string $image = null, string $redirect_direction = 'both') +function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains, bool $is_force_https_enabled = false, $onlyPort = null, ?Collection $serviceLabels = null, ?bool $is_gzip_enabled = true, ?bool $is_stripprefix_enabled = true, ?string $service_name = null, ?string $image = null, string $redirect_direction = 'both', ?string $predefinedPort = null) { $labels = collect([]); if ($serviceLabels) { @@ -270,6 +286,9 @@ function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains, if (is_null($port) && ! is_null($onlyPort)) { $port = $onlyPort; } + if (is_null($port) && $predefinedPort) { + $port = $predefinedPort; + } $labels->push("caddy_{$loop}={$schema}://{$host}"); $labels->push("caddy_{$loop}.header=-Server"); $labels->push("caddy_{$loop}.try_files={path} /index.html /index.php"); @@ -677,18 +696,19 @@ function convert_docker_run_to_compose(?string $custom_docker_run_options = null '--sysctl', '--ulimit', '--device', + '--shm-size', ]); $mapping = collect([ '--cap-add' => 'cap_add', '--cap-drop' => 'cap_drop', '--security-opt' => 'security_opt', '--sysctl' => 'sysctls', - '--ulimit' => 'ulimits', '--device' => 'devices', '--init' => 'init', '--ulimit' => 'ulimits', '--privileged' => 'privileged', '--ip' => 'ip', + '--shm-size' => 'shm_size', ]); foreach ($matches as $match) { $option = $match[1]; @@ -704,6 +724,7 @@ function convert_docker_run_to_compose(?string $custom_docker_run_options = null $options = collect($options); // Easily get mappings from https://github.com/composerize/composerize/blob/master/packages/composerize/src/mappings.js foreach ($options as $option => $value) { + // ray($option,$value); if (! data_get($mapping, $option)) { continue; } @@ -728,6 +749,10 @@ function convert_docker_run_to_compose(?string $custom_docker_run_options = null } }); $compose_options->put($mapping[$option], $ulimits); + } elseif ($option === '--shm-size') { + if (! is_null($value) && is_array($value) && count($value) > 0) { + $compose_options->put($mapping[$option], $value[0]); + } } else { if ($list_options->contains($option)) { if ($compose_options->has($mapping[$option])) { @@ -749,6 +774,26 @@ function convert_docker_run_to_compose(?string $custom_docker_run_options = null return $compose_options->toArray(); } +function generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $network) +{ + $ipv4 = data_get($docker_run_options, 'ip.0'); + $ipv6 = data_get($docker_run_options, 'ip6.0'); + data_forget($docker_run_options, 'ip'); + data_forget($docker_run_options, 'ip6'); + if ($ipv4 || $ipv6) { + data_forget($docker_compose['services'][$container_name], 'networks'); + } + if ($ipv4) { + $docker_compose['services'][$container_name]['networks'][$network]['ipv4_address'] = $ipv4; + } + if ($ipv6) { + $docker_compose['services'][$container_name]['networks'][$network]['ipv6_address'] = $ipv6; + } + $docker_compose['services'][$container_name] = array_merge_recursive($docker_compose['services'][$container_name], $docker_run_options); + + return $docker_compose; +} + function validateComposeFile(string $compose, int $server_id): string|Throwable { return 'OK'; diff --git a/bootstrap/helpers/proxy.php b/bootstrap/helpers/proxy.php index e50983535..c4c15b8fe 100644 --- a/bootstrap/helpers/proxy.php +++ b/bootstrap/helpers/proxy.php @@ -146,12 +146,11 @@ function generate_default_proxy_configuration(Server $server) 'coolify.managed=true', ]; $config = [ - 'version' => '3.8', 'networks' => $array_of_networks->toArray(), 'services' => [ 'traefik' => [ 'container_name' => 'coolify-proxy', - 'image' => 'traefik:v2.11', + 'image' => 'traefik:v3.1', 'restart' => RESTART_MODE, 'extra_hosts' => [ 'host.docker.internal:host-gateway', diff --git a/bootstrap/helpers/remoteProcess.php b/bootstrap/helpers/remoteProcess.php index 918aa74cc..4ba378e67 100644 --- a/bootstrap/helpers/remoteProcess.php +++ b/bootstrap/helpers/remoteProcess.php @@ -95,8 +95,24 @@ function generateScpCommand(Server $server, string $source, string $dest) $timeout = config('constants.ssh.command_timeout'); $connectionTimeout = config('constants.ssh.connection_timeout'); $serverInterval = config('constants.ssh.server_interval'); + $muxPersistTime = config('constants.ssh.mux_persist_time'); $scp_command = "timeout $timeout scp "; + $muxEnabled = config('constants.ssh.mux_enabled', true) && config('coolify.is_windows_docker_desktop') == false; + // ray('SSH Multiplexing Enabled:', $muxEnabled)->blue(); + + if ($muxEnabled) { + $muxSocket = "/var/www/html/storage/app/ssh/mux/{$server->muxFilename()}"; + $scp_command .= "-o ControlMaster=auto -o ControlPath=$muxSocket -o ControlPersist={$muxPersistTime} "; + ensureMultiplexedConnection($server); + // ray('Using SSH Multiplexing')->green(); + } else { + // ray('Not using SSH Multiplexing')->red(); + } + + if (data_get($server, 'settings.is_cloudflare_tunnel')) { + $scp_command .= '-o ProxyCommand="/usr/local/bin/cloudflared access ssh --hostname %h" '; + } $scp_command .= "-i {$privateKeyLocation} " .'-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ' .'-o PasswordAuthentication=no ' @@ -145,9 +161,18 @@ function generateSshCommand(Server $server, string $command) $ssh_command = "timeout $timeout ssh "; - if (config('coolify.mux_enabled') && config('coolify.is_windows_docker_desktop') == false) { - $ssh_command .= "-o ControlMaster=auto -o ControlPersist={$muxPersistTime} -o ControlPath=/var/www/html/storage/app/ssh/mux/%h_%p_%r "; + $muxEnabled = config('constants.ssh.mux_enabled') && config('coolify.is_windows_docker_desktop') == false; + // ray('SSH Multiplexing Enabled:', $muxEnabled)->blue(); + if ($muxEnabled) { + // Always use multiplexing when enabled + $muxSocket = "/var/www/html/storage/app/ssh/mux/{$server->muxFilename()}"; + $ssh_command .= "-o ControlMaster=auto -o ControlPath=$muxSocket -o ControlPersist={$muxPersistTime} "; + ensureMultiplexedConnection($server); + // ray('Using SSH Multiplexing')->green(); + } else { + // ray('Not using SSH Multiplexing')->red(); } + if (data_get($server, 'settings.is_cloudflare_tunnel')) { $ssh_command .= '-o ProxyCommand="/usr/local/bin/cloudflared access ssh --hostname %h" '; } @@ -167,11 +192,120 @@ function generateSshCommand(Server $server, string $command) .$command.PHP_EOL .$delimiter; - // ray($ssh_command); return $ssh_command; } -function instant_remote_process(Collection|array $command, Server $server, bool $throwError = true, bool $no_sudo = false) + +function ensureMultiplexedConnection(Server $server) { + if (! (config('constants.ssh.mux_enabled') && config('coolify.is_windows_docker_desktop') == false)) { + return; + } + + static $ensuredConnections = []; + + if (isset($ensuredConnections[$server->id])) { + if (! shouldResetMultiplexedConnection($server)) { + // ray('Using Existing Multiplexed Connection')->green(); + + return; + } + } + + $muxSocket = "/var/www/html/storage/app/ssh/mux/{$server->muxFilename()}"; + $checkCommand = "ssh -O check -o ControlPath=$muxSocket "; + if (data_get($server, 'settings.is_cloudflare_tunnel')) { + $checkCommand .= '-o ProxyCommand="/usr/local/bin/cloudflared access ssh --hostname %h" '; + } + $checkCommand .= " {$server->user}@{$server->ip}"; + + $process = Process::run($checkCommand); + + if ($process->exitCode() === 0) { + // ray('Existing Multiplexed Connection is Valid')->green(); + $ensuredConnections[$server->id] = [ + 'timestamp' => now(), + 'muxSocket' => $muxSocket, + ]; + + return; + } + + // ray('Establishing New Multiplexed Connection')->orange(); + + $privateKeyLocation = savePrivateKeyToFs($server); + $connectionTimeout = config('constants.ssh.connection_timeout'); + $serverInterval = config('constants.ssh.server_interval'); + $muxPersistTime = config('constants.ssh.mux_persist_time'); + + $establishCommand = "ssh -fNM -o ControlMaster=auto -o ControlPath=$muxSocket -o ControlPersist={$muxPersistTime} "; + + if (data_get($server, 'settings.is_cloudflare_tunnel')) { + $establishCommand .= '-o ProxyCommand="/usr/local/bin/cloudflared access ssh --hostname %h" '; + } + $establishCommand .= "-i {$privateKeyLocation} " + .'-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ' + .'-o PasswordAuthentication=no ' + ."-o ConnectTimeout=$connectionTimeout " + ."-o ServerAliveInterval=$serverInterval " + .'-o RequestTTY=no ' + .'-o LogLevel=ERROR ' + ."-p {$server->port} " + ."{$server->user}@{$server->ip}"; + + $establishProcess = Process::run($establishCommand); + + if ($establishProcess->exitCode() !== 0) { + throw new \RuntimeException('Failed to establish multiplexed connection: '.$establishProcess->errorOutput()); + } + + $ensuredConnections[$server->id] = [ + 'timestamp' => now(), + 'muxSocket' => $muxSocket, + ]; + + // ray('Established New Multiplexed Connection')->green(); +} + +function shouldResetMultiplexedConnection(Server $server) +{ + if (! (config('constants.ssh.mux_enabled') && config('coolify.is_windows_docker_desktop') == false)) { + return false; + } + + static $ensuredConnections = []; + + if (! isset($ensuredConnections[$server->id])) { + return true; + } + + $lastEnsured = $ensuredConnections[$server->id]['timestamp']; + $muxPersistTime = config('constants.ssh.mux_persist_time'); + $resetInterval = strtotime($muxPersistTime) - time(); + + return $lastEnsured->addSeconds($resetInterval)->isPast(); +} + +function resetMultiplexedConnection(Server $server) +{ + if (! (config('constants.ssh.mux_enabled') && config('coolify.is_windows_docker_desktop') == false)) { + return; + } + + static $ensuredConnections = []; + + if (isset($ensuredConnections[$server->id])) { + $muxSocket = $ensuredConnections[$server->id]['muxSocket']; + $closeCommand = "ssh -O exit -o ControlPath=$muxSocket {$server->user}@{$server->ip}"; + Process::run($closeCommand); + unset($ensuredConnections[$server->id]); + } +} + +function instant_remote_process(Collection|array $command, Server $server, bool $throwError = true, bool $no_sudo = false): ?string +{ + static $processCount = 0; + $processCount++; + $timeout = config('constants.ssh.command_timeout'); if ($command instanceof Collection) { $command = $command->toArray(); @@ -180,10 +314,18 @@ function instant_remote_process(Collection|array $command, Server $server, bool $command = parseCommandsByLineForSudo(collect($command), $server); } $command_string = implode("\n", $command); - $ssh_command = generateSshCommand($server, $command_string, $no_sudo); - $process = Process::timeout($timeout)->run($ssh_command); + + $start_time = microtime(true); + $sshCommand = generateSshCommand($server, $command_string); + $process = Process::timeout($timeout)->run($sshCommand); + $end_time = microtime(true); + + $execution_time = ($end_time - $start_time) * 1000; // Convert to milliseconds + // ray('SSH command execution time:', $execution_time.' ms')->orange(); + $output = trim($process->output()); $exitCode = $process->exitCode(); + if ($exitCode !== 0) { if (! $throwError) { return null; @@ -223,7 +365,6 @@ function decode_remote_command_output(?ApplicationDeploymentQueue $application_d if (is_null($application_deployment_queue)) { return collect([]); } - // ray(data_get($application_deployment_queue, 'logs')); try { $decoded = json_decode( data_get($application_deployment_queue, 'logs'), @@ -233,7 +374,7 @@ function decode_remote_command_output(?ApplicationDeploymentQueue $application_d } catch (\JsonException $exception) { return collect([]); } - // ray($decoded ); + $seenCommands = collect(); $formatted = collect($decoded); if (! $is_debug_enabled) { $formatted = $formatted->filter(fn ($i) => $i['hidden'] === false ?? false); @@ -244,7 +385,42 @@ function decode_remote_command_output(?ApplicationDeploymentQueue $application_d data_set($i, 'timestamp', Carbon::parse(data_get($i, 'timestamp'))->format('Y-M-d H:i:s.u')); return $i; - }); + }) + ->reduce(function ($deploymentLogLines, $logItem) use ($seenCommands) { + $command = data_get($logItem, 'command'); + $isStderr = data_get($logItem, 'type') === 'stderr'; + $isNewCommand = ! is_null($command) && ! $seenCommands->first(function ($seenCommand) use ($logItem) { + return data_get($seenCommand, 'command') === data_get($logItem, 'command') && data_get($seenCommand, 'batch') === data_get($logItem, 'batch'); + }); + + if ($isNewCommand) { + $deploymentLogLines->push([ + 'line' => $command, + 'timestamp' => data_get($logItem, 'timestamp'), + 'stderr' => $isStderr, + 'hidden' => data_get($logItem, 'hidden'), + 'command' => true, + ]); + + $seenCommands->push([ + 'command' => $command, + 'batch' => data_get($logItem, 'batch'), + ]); + } + + $lines = explode(PHP_EOL, data_get($logItem, 'output')); + + foreach ($lines as $line) { + $deploymentLogLines->push([ + 'line' => $line, + 'timestamp' => data_get($logItem, 'timestamp'), + 'stderr' => $isStderr, + 'hidden' => data_get($logItem, 'hidden'), + ]); + } + + return $deploymentLogLines; + }, collect()); return $formatted; } @@ -258,6 +434,10 @@ function remove_mux_and_private_key(Server $server) { $muxFilename = $server->muxFilename(); $privateKeyLocation = savePrivateKeyToFs($server); + + $closeCommand = "ssh -O exit -o ControlPath=/var/www/html/storage/app/ssh/mux/{$muxFilename} {$server->user}@{$server->ip}"; + Process::run($closeCommand); + Storage::disk('ssh-mux')->delete($muxFilename); Storage::disk('ssh-keys')->delete($privateKeyLocation); } @@ -267,7 +447,10 @@ function refresh_server_connection(?PrivateKey $private_key = null) return; } foreach ($private_key->servers as $server) { - Storage::disk('ssh-mux')->delete($server->muxFilename()); + $muxFilename = $server->muxFilename(); + $closeCommand = "ssh -O exit -o ControlPath=/var/www/html/storage/app/ssh/mux/{$muxFilename} {$server->user}@{$server->ip}"; + Process::run($closeCommand); + Storage::disk('ssh-mux')->delete($muxFilename); } } @@ -277,24 +460,17 @@ function checkRequiredCommands(Server $server) foreach ($commands as $command) { $commandFound = instant_remote_process(["docker run --rm --privileged --net=host --pid=host --ipc=host --volume /:/host busybox chroot /host bash -c 'command -v {$command}'"], $server, false); if ($commandFound) { - ray($command.' found'); - continue; } try { instant_remote_process(["docker run --rm --privileged --net=host --pid=host --ipc=host --volume /:/host busybox chroot /host bash -c 'apt update && apt install -y {$command}'"], $server); } catch (\Throwable $e) { - ray('could not install '.$command); - ray($e); break; } $commandFound = instant_remote_process(["docker run --rm --privileged --net=host --pid=host --ipc=host --volume /:/host busybox chroot /host bash -c 'command -v {$command}'"], $server, false); if ($commandFound) { - ray($command.' found'); - continue; } - ray('could not install '.$command); break; } } diff --git a/bootstrap/helpers/services.php b/bootstrap/helpers/services.php index 1cc2ac36d..eba88d000 100644 --- a/bootstrap/helpers/services.php +++ b/bootstrap/helpers/services.php @@ -4,6 +4,7 @@ use App\Models\Application; use App\Models\EnvironmentVariable; use App\Models\ServiceApplication; use App\Models\ServiceDatabase; +use Illuminate\Support\Stringable; use Spatie\Url\Url; use Symfony\Component\Yaml\Yaml; @@ -15,9 +16,9 @@ function collectRegex(string $name) { return "/{$name}\w+/"; } -function replaceVariables($variable) +function replaceVariables(string $variable): Stringable { - return $variable->before('}')->replaceFirst('$', '')->replaceFirst('{', ''); + return str($variable)->before('}')->replaceFirst('$', '')->replaceFirst('{', ''); } function getFilesystemVolumesFromServer(ServiceApplication|ServiceDatabase|Application $oneService, bool $isInit = false) @@ -53,7 +54,9 @@ function getFilesystemVolumesFromServer(ServiceApplication|ServiceDatabase|Appli if ($isFile == 'OK') { // If its a file & exists $filesystemContent = instant_remote_process(["cat $fileLocation"], $server); - $fileVolume->content = $filesystemContent; + if ($fileVolume->is_based_on_git) { + $fileVolume->content = $filesystemContent; + } $fileVolume->is_directory = false; $fileVolume->save(); } elseif ($isDir == 'OK') { diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index aae4fafd4..cd2779466 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -353,6 +353,14 @@ function isCloud(): bool return ! config('coolify.self_hosted'); } +function translate_cron_expression($expression_to_validate): string +{ + if (isset(VALID_CRON_STRINGS[$expression_to_validate])) { + return VALID_CRON_STRINGS[$expression_to_validate]; + } + + return $expression_to_validate; +} function validate_cron_expression($expression_to_validate): bool { $isValid = false; @@ -470,7 +478,7 @@ function data_get_str($data, $key, $default = null): Stringable return str($str); } -function generateFqdn(Server $server, string $random) +function generateFqdn(Server $server, string $random, bool $forceHttps = false): string { $wildcard = data_get($server, 'settings.wildcard_domain'); if (is_null($wildcard) || $wildcard === '') { @@ -480,6 +488,9 @@ function generateFqdn(Server $server, string $random) $host = $url->getHost(); $path = $url->getPath() === '/' ? '' : $url->getPath(); $scheme = $url->getScheme(); + if ($forceHttps) { + $scheme = 'https'; + } $finalFqdn = "$scheme://{$random}.$host$path"; return $finalFqdn; @@ -758,6 +769,670 @@ function getTopLevelNetworks(Service|Application $resource) return $topLevelNetworks->keys(); } } +function sourceIsLocal(Stringable $source) +{ + if ($source->startsWith('./') || $source->startsWith('/') || $source->startsWith('~') || $source->startsWith('..') || $source->startsWith('~/') || $source->startsWith('../')) { + return true; + } + + return false; +} + +function replaceLocalSource(Stringable $source, Stringable $replacedWith) +{ + if ($source->startsWith('.')) { + $source = $source->replaceFirst('.', $replacedWith->value()); + } + if ($source->startsWith('~')) { + $source = $source->replaceFirst('~', $replacedWith->value()); + } + if ($source->startsWith('..')) { + $source = $source->replaceFirst('..', $replacedWith->value()); + } + if ($source->endsWith('/') && $source->value() !== '/') { + $source = $source->replaceLast('/', ''); + } + + return $source; +} + +function convertToArray($collection) +{ + if ($collection instanceof Collection) { + return $collection->map(function ($item) { + return convertToArray($item); + })->toArray(); + } elseif ($collection instanceof Stringable) { + return (string) $collection; + } elseif (is_array($collection)) { + return array_map(function ($item) { + return convertToArray($item); + }, $collection); + } + + return $collection; +} + +function parseEnvVariable(Str|string $value) +{ + $value = str($value); + $count = substr_count($value->value(), '_'); + $command = null; + $forService = null; + $generatedValue = null; + $port = null; + if ($value->startsWith('SERVICE')) { + if ($count === 2) { + if ($value->startsWith('SERVICE_FQDN') || $value->startsWith('SERVICE_URL')) { + // SERVICE_FQDN_UMAMI + $command = $value->after('SERVICE_')->beforeLast('_'); + $forService = $value->afterLast('_'); + } else { + // SERVICE_BASE64_UMAMI + $command = $value->after('SERVICE_')->beforeLast('_'); + } + } + if ($count === 3) { + if ($value->startsWith('SERVICE_FQDN') || $value->startsWith('SERVICE_URL')) { + // SERVICE_FQDN_UMAMI_1000 + $command = $value->after('SERVICE_')->before('_'); + $forService = $value->after('SERVICE_')->after('_')->before('_'); + $port = $value->afterLast('_'); + if (filter_var($port, FILTER_VALIDATE_INT) === false) { + $port = null; + } + } else { + // SERVICE_BASE64_64_UMAMI + $command = $value->after('SERVICE_')->beforeLast('_'); + } + } + } + + return [ + 'command' => $command, + 'forService' => $forService, + 'generatedValue' => $generatedValue, + 'port' => $port, + ]; +} +function generateEnvValue(string $command, Service|Application|null $service = null) +{ + switch ($command) { + case 'PASSWORD': + $generatedValue = Str::password(symbols: false); + break; + case 'PASSWORD_64': + $generatedValue = Str::password(length: 64, symbols: false); + break; + // This is not base64, it's just a random string + case 'BASE64_64': + $generatedValue = Str::random(64); + break; + case 'BASE64_128': + $generatedValue = Str::random(128); + break; + case 'BASE64': + case 'BASE64_32': + $generatedValue = Str::random(32); + break; + // This is base64, + case 'REALBASE64_64': + $generatedValue = base64_encode(Str::random(64)); + break; + case 'REALBASE64_128': + $generatedValue = base64_encode(Str::random(128)); + break; + case 'REALBASE64': + case 'REALBASE64_32': + $generatedValue = base64_encode(Str::random(32)); + break; + case 'USER': + $generatedValue = Str::random(16); + break; + case 'SUPABASEANON': + $signingKey = $service->environment_variables()->where('key', 'SERVICE_PASSWORD_JWT')->first(); + if (is_null($signingKey)) { + return; + } else { + $signingKey = $signingKey->value; + } + $key = InMemory::plainText($signingKey); + $algorithm = new Sha256; + $tokenBuilder = (new Builder(new JoseEncoder, ChainedFormatter::default())); + $now = new DateTimeImmutable; + $now = $now->setTime($now->format('H'), $now->format('i')); + $token = $tokenBuilder + ->issuedBy('supabase') + ->issuedAt($now) + ->expiresAt($now->modify('+100 year')) + ->withClaim('role', 'anon') + ->getToken($algorithm, $key); + $generatedValue = $token->toString(); + break; + case 'SUPABASESERVICE': + $signingKey = $service->environment_variables()->where('key', 'SERVICE_PASSWORD_JWT')->first(); + if (is_null($signingKey)) { + return; + } else { + $signingKey = $signingKey->value; + } + $key = InMemory::plainText($signingKey); + $algorithm = new Sha256; + $tokenBuilder = (new Builder(new JoseEncoder, ChainedFormatter::default())); + $now = new DateTimeImmutable; + $now = $now->setTime($now->format('H'), $now->format('i')); + $token = $tokenBuilder + ->issuedBy('supabase') + ->issuedAt($now) + ->expiresAt($now->modify('+100 year')) + ->withClaim('role', 'service_role') + ->getToken($algorithm, $key); + $generatedValue = $token->toString(); + break; + default: + // $generatedValue = Str::random(16); + $generatedValue = null; + break; + } + + return $generatedValue; +} + +function getRealtime() +{ + $envDefined = env('PUSHER_PORT'); + if (empty($envDefined)) { + $url = Url::fromString(Request::getSchemeAndHttpHost()); + $port = $url->getPort(); + if ($port) { + return '6001'; + } else { + return null; + } + } else { + return $envDefined; + } +} + +function validate_dns_entry(string $fqdn, Server $server) +{ + // https://www.cloudflare.com/ips-v4/# + $cloudflare_ips = collect(['173.245.48.0/20', '103.21.244.0/22', '103.22.200.0/22', '103.31.4.0/22', '141.101.64.0/18', '108.162.192.0/18', '190.93.240.0/20', '188.114.96.0/20', '197.234.240.0/22', '198.41.128.0/17', '162.158.0.0/15', '104.16.0.0/13', '172.64.0.0/13', '131.0.72.0/22']); + + $url = Url::fromString($fqdn); + $host = $url->getHost(); + if (str($host)->contains('sslip.io')) { + return true; + } + $settings = \App\Models\InstanceSettings::get(); + $is_dns_validation_enabled = data_get($settings, 'is_dns_validation_enabled'); + if (! $is_dns_validation_enabled) { + return true; + } + $dns_servers = data_get($settings, 'custom_dns_servers'); + $dns_servers = str($dns_servers)->explode(','); + if ($server->id === 0) { + $ip = data_get($settings, 'public_ipv4', data_get($settings, 'public_ipv6', $server->ip)); + } else { + $ip = $server->ip; + } + $found_matching_ip = false; + $type = \PurplePixie\PhpDns\DNSTypes::NAME_A; + foreach ($dns_servers as $dns_server) { + try { + ray("Checking $host on $dns_server"); + $query = new DNSQuery($dns_server); + $results = $query->query($host, $type); + if ($results === false || $query->hasError()) { + ray('Error: '.$query->getLasterror()); + } else { + foreach ($results as $result) { + if ($result->getType() == $type) { + if (ip_match($result->getData(), $cloudflare_ips->toArray(), $match)) { + ray("Found match in Cloudflare IPs: $match"); + $found_matching_ip = true; + break; + } + if ($result->getData() === $ip) { + ray($host.' has IP address '.$result->getData()); + ray($result->getString()); + $found_matching_ip = true; + break; + } + } + } + } + } catch (\Exception $e) { + } + } + ray("Found match: $found_matching_ip"); + + return $found_matching_ip; +} + +function ip_match($ip, $cidrs, &$match = null) +{ + foreach ((array) $cidrs as $cidr) { + [$subnet, $mask] = explode('/', $cidr); + if (((ip2long($ip) & ($mask = ~((1 << (32 - $mask)) - 1))) == (ip2long($subnet) & $mask))) { + $match = $cidr; + + return true; + } + } + + return false; +} +function checkIfDomainIsAlreadyUsed(Collection|array $domains, ?string $teamId = null, ?string $uuid = null) +{ + if (is_null($teamId)) { + return response()->json(['error' => 'Team ID is required.'], 400); + } + if (is_array($domains)) { + $domains = collect($domains); + } + + $domains = $domains->map(function ($domain) { + if (str($domain)->endsWith('/')) { + $domain = str($domain)->beforeLast('/'); + } + + return str($domain); + }); + $applications = Application::ownedByCurrentTeamAPI($teamId)->get(['fqdn', 'uuid']); + $serviceApplications = ServiceApplication::ownedByCurrentTeamAPI($teamId)->get(['fqdn', 'uuid']); + if ($uuid) { + $applications = $applications->filter(fn ($app) => $app->uuid !== $uuid); + $serviceApplications = $serviceApplications->filter(fn ($app) => $app->uuid !== $uuid); + } + $domainFound = false; + foreach ($applications as $app) { + if (is_null($app->fqdn)) { + continue; + } + $list_of_domains = collect(explode(',', $app->fqdn))->filter(fn ($fqdn) => $fqdn !== ''); + foreach ($list_of_domains as $domain) { + if (str($domain)->endsWith('/')) { + $domain = str($domain)->beforeLast('/'); + } + $naked_domain = str($domain)->value(); + if ($domains->contains($naked_domain)) { + $domainFound = true; + break; + } + } + } + if ($domainFound) { + return true; + } + foreach ($serviceApplications as $app) { + if (str($app->fqdn)->isEmpty()) { + continue; + } + $list_of_domains = collect(explode(',', $app->fqdn))->filter(fn ($fqdn) => $fqdn !== ''); + foreach ($list_of_domains as $domain) { + if (str($domain)->endsWith('/')) { + $domain = str($domain)->beforeLast('/'); + } + $naked_domain = str($domain)->value(); + if ($domains->contains($naked_domain)) { + $domainFound = true; + break; + } + } + } + if ($domainFound) { + return true; + } + $settings = \App\Models\InstanceSettings::get(); + if (data_get($settings, 'fqdn')) { + $domain = data_get($settings, 'fqdn'); + if (str($domain)->endsWith('/')) { + $domain = str($domain)->beforeLast('/'); + } + $naked_domain = str($domain)->value(); + if ($domains->contains($naked_domain)) { + return true; + } + } +} +function check_domain_usage(ServiceApplication|Application|null $resource = null, ?string $domain = null) +{ + if ($resource) { + if ($resource->getMorphClass() === 'App\Models\Application' && $resource->build_pack === 'dockercompose') { + $domains = data_get(json_decode($resource->docker_compose_domains, true), '*.domain'); + $domains = collect($domains); + } else { + $domains = collect($resource->fqdns); + } + } elseif ($domain) { + $domains = collect($domain); + } else { + throw new \RuntimeException('No resource or FQDN provided.'); + } + $domains = $domains->map(function ($domain) { + if (str($domain)->endsWith('/')) { + $domain = str($domain)->beforeLast('/'); + } + + return str($domain); + }); + $apps = Application::all(); + foreach ($apps as $app) { + $list_of_domains = collect(explode(',', $app->fqdn))->filter(fn ($fqdn) => $fqdn !== ''); + foreach ($list_of_domains as $domain) { + if (str($domain)->endsWith('/')) { + $domain = str($domain)->beforeLast('/'); + } + $naked_domain = str($domain)->value(); + if ($domains->contains($naked_domain)) { + if (data_get($resource, 'uuid')) { + if ($resource->uuid !== $app->uuid) { + throw new \RuntimeException("Domain $naked_domain is already in use by another resource called:

{$app->name}."); + } + } elseif ($domain) { + throw new \RuntimeException("Domain $naked_domain is already in use by another resource called:

{$app->name}."); + } + } + } + } + $apps = ServiceApplication::all(); + foreach ($apps as $app) { + $list_of_domains = collect(explode(',', $app->fqdn))->filter(fn ($fqdn) => $fqdn !== ''); + foreach ($list_of_domains as $domain) { + if (str($domain)->endsWith('/')) { + $domain = str($domain)->beforeLast('/'); + } + $naked_domain = str($domain)->value(); + if ($domains->contains($naked_domain)) { + if (data_get($resource, 'uuid')) { + if ($resource->uuid !== $app->uuid) { + throw new \RuntimeException("Domain $naked_domain is already in use by another resource called:

{$app->name}."); + } + } elseif ($domain) { + throw new \RuntimeException("Domain $naked_domain is already in use by another resource called:

{$app->name}."); + } + } + } + } + if ($resource) { + $settings = \App\Models\InstanceSettings::get(); + if (data_get($settings, 'fqdn')) { + $domain = data_get($settings, 'fqdn'); + if (str($domain)->endsWith('/')) { + $domain = str($domain)->beforeLast('/'); + } + $naked_domain = str($domain)->value(); + if ($domains->contains($naked_domain)) { + throw new \RuntimeException("Domain $naked_domain is already in use by this Coolify instance."); + } + } + } +} + +function parseCommandsByLineForSudo(Collection $commands, Server $server): array +{ + $commands = $commands->map(function ($line) { + if (! str($line)->startsWith('cd') && ! str($line)->startsWith('command') && ! str($line)->startsWith('echo') && ! str($line)->startsWith('true')) { + return "sudo $line"; + } + + return $line; + }); + $commands = $commands->map(function ($line) use ($server) { + if (Str::startsWith($line, 'sudo mkdir -p')) { + return "$line && sudo chown -R $server->user:$server->user ".Str::after($line, 'sudo mkdir -p').' && sudo chmod -R o-rwx '.Str::after($line, 'sudo mkdir -p'); + } + + return $line; + }); + $commands = $commands->map(function ($line) { + $line = str($line); + if (str($line)->contains('$(')) { + $line = $line->replace('$(', '$(sudo '); + } + if (str($line)->contains('||')) { + $line = $line->replace('||', '|| sudo'); + } + if (str($line)->contains('&&')) { + $line = $line->replace('&&', '&& sudo'); + } + if (str($line)->contains(' | ')) { + $line = $line->replace(' | ', ' | sudo '); + } + + return $line->value(); + }); + + return $commands->toArray(); +} +function parseLineForSudo(string $command, Server $server): string +{ + if (! str($command)->startSwith('cd') && ! str($command)->startSwith('command')) { + $command = "sudo $command"; + } + if (Str::startsWith($command, 'sudo mkdir -p')) { + $command = "$command && sudo chown -R $server->user:$server->user ".Str::after($command, 'sudo mkdir -p').' && sudo chmod -R o-rwx '.Str::after($command, 'sudo mkdir -p'); + } + if (str($command)->contains('$(') || str($command)->contains('`')) { + $command = str($command)->replace('$(', '$(sudo ')->replace('`', '`sudo ')->value(); + } + if (str($command)->contains('||')) { + $command = str($command)->replace('||', '|| sudo ')->value(); + } + if (str($command)->contains('&&')) { + $command = str($command)->replace('&&', '&& sudo ')->value(); + } + + return $command; +} + +function get_public_ips() +{ + try { + echo "Refreshing public ips!\n"; + $settings = \App\Models\InstanceSettings::get(); + [$first, $second] = Process::concurrently(function (Pool $pool) { + $pool->path(__DIR__)->command('curl -4s https://ifconfig.io'); + $pool->path(__DIR__)->command('curl -6s https://ifconfig.io'); + }); + $ipv4 = $first->output(); + if ($ipv4) { + $ipv4 = trim($ipv4); + $validate_ipv4 = filter_var($ipv4, FILTER_VALIDATE_IP); + if ($validate_ipv4 == false) { + echo "Invalid ipv4: $ipv4\n"; + + return; + } + $settings->update(['public_ipv4' => $ipv4]); + } + $ipv6 = $second->output(); + if ($ipv6) { + $ipv6 = trim($ipv6); + $validate_ipv6 = filter_var($ipv6, FILTER_VALIDATE_IP); + if ($validate_ipv6 == false) { + echo "Invalid ipv6: $ipv6\n"; + + return; + } + $settings->update(['public_ipv6' => $ipv6]); + } + } catch (\Throwable $e) { + echo "Error: {$e->getMessage()}\n"; + } +} + +function isAnyDeploymentInprogress() +{ + // Only use it in the deployment script + $count = ApplicationDeploymentQueue::whereIn('status', [ApplicationDeploymentStatus::IN_PROGRESS, ApplicationDeploymentStatus::QUEUED])->count(); + if ($count > 0) { + echo "There are $count deployments in progress. Exiting...\n"; + exit(1); + } + echo "No deployments in progress.\n"; + exit(0); +} + +function generateSentinelToken() +{ + $token = Str::random(64); + + return $token; +} + +function isBase64Encoded($strValue) +{ + return base64_encode(base64_decode($strValue, true)) === $strValue; +} +function customApiValidator(Collection|array $item, array $rules) +{ + if (is_array($item)) { + $item = collect($item); + } + + return Validator::make($item->toArray(), $rules, [ + 'required' => 'This field is required.', + ]); +} + +function parseServiceVolumes($serviceVolumes, $resource, $topLevelVolumes, $pull_request_id = 0) +{ + $serviceVolumes = $serviceVolumes->map(function ($volume) use ($resource, $topLevelVolumes, $pull_request_id) { + $type = null; + $source = null; + $target = null; + $content = null; + $isDirectory = false; + if (is_string($volume)) { + $source = str($volume)->before(':'); + $target = str($volume)->after(':')->beforeLast(':'); + $foundConfig = $resource->fileStorages()->whereMountPath($target)->first(); + if ($source->startsWith('./') || $source->startsWith('/') || $source->startsWith('~')) { + $type = str('bind'); + if ($foundConfig) { + $contentNotNull = data_get($foundConfig, 'content'); + if ($contentNotNull) { + $content = $contentNotNull; + } + $isDirectory = data_get($foundConfig, 'is_directory'); + } else { + // By default, we cannot determine if the bind is a directory or not, so we set it to directory + $isDirectory = true; + } + } else { + $type = str('volume'); + } + } elseif (is_array($volume)) { + $type = data_get_str($volume, 'type'); + $source = data_get_str($volume, 'source'); + $target = data_get_str($volume, 'target'); + $content = data_get($volume, 'content'); + $isDirectory = (bool) data_get($volume, 'isDirectory', null) || (bool) data_get($volume, 'is_directory', null); + $foundConfig = $resource->fileStorages()->whereMountPath($target)->first(); + if ($foundConfig) { + $contentNotNull = data_get($foundConfig, 'content'); + if ($contentNotNull) { + $content = $contentNotNull; + } + $isDirectory = data_get($foundConfig, 'is_directory'); + } else { + $isDirectory = (bool) data_get($volume, 'isDirectory', null) || (bool) data_get($volume, 'is_directory', null); + if ((is_null($isDirectory) || ! $isDirectory) && is_null($content)) { + // if isDirectory is not set (or false) & content is also not set, we assume it is a directory + ray('setting isDirectory to true'); + $isDirectory = true; + } + } + } + if ($type?->value() === 'bind') { + if ($source->value() === '/var/run/docker.sock') { + return $volume; + } + if ($source->value() === '/tmp' || $source->value() === '/tmp/') { + return $volume; + } + if (get_class($resource) === "App\Models\Application") { + $dir = base_configuration_dir().'/applications/'.$resource->uuid; + } else { + $dir = base_configuration_dir().'/services/'.$resource->service->uuid; + } + + if ($source->startsWith('.')) { + $source = $source->replaceFirst('.', $dir); + } + if ($source->startsWith('~')) { + $source = $source->replaceFirst('~', $dir); + } + if ($pull_request_id !== 0) { + $source = $source."-pr-$pull_request_id"; + } + if (! $resource?->settings?->is_preserve_repository_enabled || $foundConfig?->is_based_on_git) { + LocalFileVolume::updateOrCreate( + [ + 'mount_path' => $target, + 'resource_id' => $resource->id, + 'resource_type' => get_class($resource), + ], + [ + 'fs_path' => $source, + 'mount_path' => $target, + 'content' => $content, + 'is_directory' => $isDirectory, + 'resource_id' => $resource->id, + 'resource_type' => get_class($resource), + ] + ); + } + } elseif ($type->value() === 'volume') { + if ($topLevelVolumes->has($source->value())) { + $v = $topLevelVolumes->get($source->value()); + if (data_get($v, 'driver_opts.type') === 'cifs') { + return $volume; + } + } + $slugWithoutUuid = Str::slug($source, '-'); + if (get_class($resource) === "App\Models\Application") { + $name = "{$resource->uuid}_{$slugWithoutUuid}"; + } else { + $name = "{$resource->service->uuid}_{$slugWithoutUuid}"; + } + if (is_string($volume)) { + $source = str($volume)->before(':'); + $target = str($volume)->after(':')->beforeLast(':'); + $source = $name; + $volume = "$source:$target"; + } elseif (is_array($volume)) { + data_set($volume, 'source', $name); + } + $topLevelVolumes->put($name, [ + 'name' => $name, + ]); + LocalPersistentVolume::updateOrCreate( + [ + 'mount_path' => $target, + 'resource_id' => $resource->id, + 'resource_type' => get_class($resource), + ], + [ + 'name' => $name, + 'mount_path' => $target, + 'resource_id' => $resource->id, + 'resource_type' => get_class($resource), + ] + ); + } + dispatch(new ServerFilesFromServerJob($resource)); + + return $volume; + }); + + return [ + 'serviceVolumes' => $serviceVolumes, + 'topLevelVolumes' => $topLevelVolumes, + ]; +} function parseDockerComposeFile(Service|Application $resource, bool $isNew = false, int $pull_request_id = 0, ?int $preview_id = null) { @@ -1062,6 +1737,23 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal data_set($service, 'volumes', $serviceVolumes->toArray()); } + // convert - SESSION_SECRET: 123 to - SESSION_SECRET=123 + $convertedServiceVariables = collect([]); + foreach ($serviceVariables as $variableName => $variable) { + if (is_numeric($variableName)) { + if (is_array($variable)) { + $key = str(collect($variable)->keys()->first()); + $value = str(collect($variable)->values()->first()); + $variable = "$key=$value"; + $convertedServiceVariables->put($variableName, $variable); + } elseif (is_string($variable)) { + $convertedServiceVariables->put($variableName, $variable); + } + } elseif (is_string($variableName)) { + $convertedServiceVariables->put($variableName, $variable); + } + } + $serviceVariables = $convertedServiceVariables; // Get variables from the service foreach ($serviceVariables as $variableName => $variable) { if (is_numeric($variableName)) { @@ -1177,7 +1869,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal 'key' => $key, 'service_id' => $resource->id, ])->first(); - $value = str(replaceVariables($value)); + $value = replaceVariables($value); $key = $value; if ($value->startsWith('SERVICE_')) { $foundEnv = EnvironmentVariable::where([ @@ -1360,14 +2052,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal } } if ($resource->server->isLogDrainEnabled() && $savedService->isLogDrainEnabled()) { - data_set($service, 'logging', [ - 'driver' => 'fluentd', - 'options' => [ - 'fluentd-address' => 'tcp://127.0.0.1:24224', - 'fluentd-async' => 'true', - 'fluentd-sub-second-precision' => 'true', - ], - ]); + data_set($service, 'logging', generate_fluentd_configuration()); } if ($serviceLabels->count() > 0) { if ($resource->is_container_label_escape_enabled) { @@ -1393,7 +2078,6 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal updateCompose($savedService); return $service; - }); $envs_from_coolify = $resource->environment_variables()->get(); @@ -1416,6 +2100,21 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal } } $parsedServiceVariables->put('COOLIFY_CONTAINER_NAME', "$serviceName-{$resource->uuid}"); + + // TODO: move this in a shared function + if (! $parsedServiceVariables->has('COOLIFY_APP_NAME')) { + $parsedServiceVariables->put('COOLIFY_APP_NAME', "\"{$resource->name}\""); + } + if (! $parsedServiceVariables->has('COOLIFY_SERVER_IP')) { + $parsedServiceVariables->put('COOLIFY_SERVER_IP', "\"{$resource->destination->server->ip}\""); + } + if (! $parsedServiceVariables->has('COOLIFY_ENVIRONMENT_NAME')) { + $parsedServiceVariables->put('COOLIFY_ENVIRONMENT_NAME', "\"{$resource->environment->name}\""); + } + if (! $parsedServiceVariables->has('COOLIFY_PROJECT_NAME')) { + $parsedServiceVariables->put('COOLIFY_PROJECT_NAME', "\"{$resource->project()->name}\""); + } + $parsedServiceVariables = $parsedServiceVariables->map(function ($value, $key) use ($envs_from_coolify) { if (! str($value)->startsWith('$')) { $found_env = $envs_from_coolify->where('key', $key)->first(); @@ -1441,6 +2140,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal $yaml = data_forget($yaml, 'services.*.volumes.*.content'); $resource->docker_compose_raw = Yaml::dump($yaml, 10, 2); $resource->docker_compose = Yaml::dump($finalServices, 10, 2); + $resource->save(); $resource->saveComposeConfigs(); @@ -1449,10 +2149,6 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal return collect([]); } } elseif ($resource->getMorphClass() === 'App\Models\Application') { - $isSameDockerComposeFile = false; - if ($resource->dockerComposePrLocation() === $resource->dockerComposeLocation()) { - $isSameDockerComposeFile = true; - } try { $yaml = Yaml::parse($resource->docker_compose_raw); } catch (\Exception $e) { @@ -1819,7 +2515,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal } } if ($collectedPorts->count() > 0) { - // ray($collectedPorts->implode(',')); + ray($collectedPorts->implode(',')); } $definedNetworkExists = $topLevelNetworks->contains(function ($value, $_) use ($definedNetwork) { return $value == $definedNetwork; @@ -1934,7 +2630,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal 'application_id' => $resource->id, 'is_preview' => false, ])->first(); - $value = str(replaceVariables($value)); + $value = replaceVariables($value); $key = $value; if ($value->startsWith('SERVICE_')) { $foundEnv = EnvironmentVariable::where([ @@ -2126,15 +2822,10 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal $defaultLabels = defaultLabels($resource->id, $containerName, $pull_request_id, type: 'application'); $serviceLabels = $serviceLabels->merge($defaultLabels); - if ($server->isLogDrainEnabled() && $resource->isLogDrainEnabled()) { - data_set($service, 'logging', [ - 'driver' => 'fluentd', - 'options' => [ - 'fluentd-address' => 'tcp://127.0.0.1:24224', - 'fluentd-async' => 'true', - 'fluentd-sub-second-precision' => 'true', - ], - ]); + if ($server->isLogDrainEnabled()) { + if ($resource instanceof Application && $resource->isLogDrainEnabled()) { + data_set($service, 'logging', generate_fluentd_configuration()); + } } if ($serviceLabels->count() > 0) { if ($resource->settings->is_container_label_escape_enabled) { @@ -2167,498 +2858,921 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal 'configs' => $topLevelConfigs->toArray(), 'secrets' => $topLevelSecrets->toArray(), ]; - if ($isSameDockerComposeFile) { - $resource->docker_compose_raw = Yaml::dump($yaml, 10, 2); - $resource->docker_compose = Yaml::dump($finalServices, 10, 2); - } else { - $resource->docker_compose_raw = Yaml::dump($yaml, 10, 2); - $resource->docker_compose = Yaml::dump($finalServices, 10, 2); - } + $resource->docker_compose_raw = Yaml::dump($yaml, 10, 2); + $resource->docker_compose = Yaml::dump($finalServices, 10, 2); + data_forget($resource, 'environment_variables'); + data_forget($resource, 'environment_variables_preview'); $resource->save(); return collect($finalServices); } } -function parseEnvVariable(Str|string $value) +function newParser(Application|Service $resource, int $pull_request_id = 0, ?int $preview_id = null): Collection { - $value = str($value); - $count = substr_count($value->value(), '_'); - $command = null; - $forService = null; - $generatedValue = null; - $port = null; - if ($value->startsWith('SERVICE')) { - if ($count === 2) { - if ($value->startsWith('SERVICE_FQDN') || $value->startsWith('SERVICE_URL')) { - // SERVICE_FQDN_UMAMI - $command = $value->after('SERVICE_')->beforeLast('_'); - $forService = $value->afterLast('_'); - } else { - // SERVICE_BASE64_UMAMI - $command = $value->after('SERVICE_')->beforeLast('_'); + $isApplication = $resource instanceof Application; + $isService = $resource instanceof Service; + + $uuid = data_get($resource, 'uuid'); + $compose = data_get($resource, 'docker_compose_raw'); + if (! $compose) { + return collect([]); + } + + if ($isApplication) { + $nameOfId = 'application_id'; + $pullRequestId = $pull_request_id; + $isPullRequest = $pullRequestId == 0 ? false : true; + $server = data_get($resource, 'destination.server'); + $fileStorages = $resource->fileStorages(); + } elseif ($isService) { + $nameOfId = 'service_id'; + $server = data_get($resource, 'server'); + $allServices = get_service_templates(); + } else { + return collect([]); + } + + try { + $yaml = Yaml::parse($compose); + } catch (\Exception $e) { + return collect([]); + } + + $services = data_get($yaml, 'services', collect([])); + $topLevel = collect([ + 'volumes' => collect(data_get($yaml, 'volumes', [])), + 'networks' => collect(data_get($yaml, 'networks', [])), + 'configs' => collect(data_get($yaml, 'configs', [])), + 'secrets' => collect(data_get($yaml, 'secrets', [])), + ]); + // If there are predefined volumes, make sure they are not null + if ($topLevel->get('volumes')->count() > 0) { + $temp = collect([]); + foreach ($topLevel['volumes'] as $volumeName => $volume) { + if (is_null($volume)) { + continue; } + $temp->put($volumeName, $volume); } - if ($count === 3) { - if ($value->startsWith('SERVICE_FQDN') || $value->startsWith('SERVICE_URL')) { - // SERVICE_FQDN_UMAMI_1000 - $command = $value->after('SERVICE_')->before('_'); - $forService = $value->after('SERVICE_')->after('_')->before('_'); - $port = $value->afterLast('_'); - if (filter_var($port, FILTER_VALIDATE_INT) === false) { - $port = null; - } + $topLevel['volumes'] = $temp; + } + // Get the base docker network + $baseNetwork = collect([$uuid]); + if ($isApplication && $isPullRequest) { + $baseNetwork = collect(["{$uuid}-{$pullRequestId}"]); + } + + $parsedServices = collect([]); + ray()->clearAll(); + + $allMagicEnvironments = collect([]); + foreach ($services as $serviceName => $service) { + $magicEnvironments = collect([]); + $image = data_get_str($service, 'image'); + $environment = collect(data_get($service, 'environment', [])); + $buildArgs = collect(data_get($service, 'build.args', [])); + $environment = $environment->merge($buildArgs); + $isDatabase = isDatabaseImage(data_get_str($service, 'image')); + + if ($isService) { + if ($isDatabase) { + $savedService = ServiceDatabase::firstOrCreate([ + 'name' => $serviceName, + 'image' => $image, + 'service_id' => $resource->id, + ]); } else { - // SERVICE_BASE64_64_UMAMI - $command = $value->after('SERVICE_')->beforeLast('_'); + $savedService = ServiceApplication::firstOrCreate([ + 'name' => $serviceName, + 'image' => $image, + 'service_id' => $resource->id, + ]); + } + $environment = collect(data_get($service, 'environment', [])); + $buildArgs = collect(data_get($service, 'build.args', [])); + $environment = $environment->merge($buildArgs); + + // convert environment variables to one format + $environment = convertComposeEnvironmentToArray($environment); + + // Add Coolify defined environments + $allEnvironments = $resource->environment_variables()->get(['key', 'value']); + + $allEnvironments = $allEnvironments->mapWithKeys(function ($item) { + return [$item['key'] => $item['value']]; + }); + // filter and add magic environments + foreach ($environment as $key => $value) { + // Get all SERVICE_ variables from keys and values + $key = str($key); + $value = str($value); + + $regex = '/\$(\{?([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)\}?)/'; + preg_match_all($regex, $value, $valueMatches); + if (count($valueMatches[1]) > 0) { + foreach ($valueMatches[1] as $match) { + $match = replaceVariables($match); + if ($match->startsWith('SERVICE_')) { + if ($magicEnvironments->has($match->value())) { + continue; + } + $magicEnvironments->put($match->value(), ''); + } + } + } + + // Get magic environments where we need to preset the FQDN + if ($key->startsWith('SERVICE_FQDN_')) { + // SERVICE_FQDN_APP or SERVICE_FQDN_APP_3000 + if (substr_count(str($key)->value(), '_') === 3) { + $fqdnFor = $key->after('SERVICE_FQDN_')->beforeLast('_')->lower()->value(); + } else { + $fqdnFor = $key->after('SERVICE_FQDN_')->lower()->value(); + } + if ($isApplication) { + $fqdn = generateFqdn($server, "{$resource->name}-$uuid"); + } elseif ($isService) { + if ($fqdnFor) { + $fqdn = generateFqdn($server, "$fqdnFor-$uuid"); + } else { + $fqdn = generateFqdn($server, "{$savedService->name}-$uuid"); + } + } + if ($value && get_class($value) === 'Illuminate\Support\Stringable' && $value->startsWith('/')) { + $path = $value->value(); + if ($path !== '/') { + $fqdn = "$fqdn$path"; + } + } + if ($isApplication && is_null($resource->fqdn)) { + data_forget($resource, 'environment_variables'); + data_forget($resource, 'environment_variables_preview'); + $resource->fqdn = $fqdn; + $resource->save(); + } elseif ($isService && is_null($savedService->fqdn)) { + $savedService->fqdn = $fqdn; + $savedService->save(); + } + + if (substr_count(str($key)->value(), '_') === 2) { + $resource->environment_variables()->where('key', $key->value())->where($nameOfId, $resource->id)->firstOrCreate([ + 'key' => $key->value(), + $nameOfId => $resource->id, + ], [ + 'value' => $fqdn, + 'is_build_time' => false, + 'is_preview' => false, + ]); + } + if (substr_count(str($key)->value(), '_') === 3) { + $newKey = str($key)->beforeLast('_'); + $resource->environment_variables()->where('key', $newKey->value())->where($nameOfId, $resource->id)->firstOrCreate([ + 'key' => $newKey->value(), + $nameOfId => $resource->id, + ], [ + 'value' => $fqdn, + 'is_build_time' => false, + 'is_preview' => false, + ]); + } + } + } + + $allMagicEnvironments = $allMagicEnvironments->merge($magicEnvironments); + + if ($magicEnvironments->count() > 0) { + foreach ($magicEnvironments as $key => $value) { + $key = str($key); + $value = replaceVariables($value); + $command = $key->after('SERVICE_')->before('_'); + $found = $resource->environment_variables()->where('key', $key->value())->where($nameOfId, $resource->id)->first(); + if ($found) { + continue; + } + if ($command->value() === 'FQDN') { + $fqdnFor = $key->after('SERVICE_FQDN_')->lower()->value(); + if (str($fqdnFor)->contains('_')) { + $fqdnFor = str($fqdnFor)->before('_'); + } + if ($isApplication) { + $fqdn = generateFqdn($server, "{$resource->name}-$uuid"); + } elseif ($isService) { + $fqdn = generateFqdn($server, "$fqdnFor-$uuid"); + } + $resource->environment_variables()->where('key', $key->value())->where($nameOfId, $resource->id)->firstOrCreate([ + 'key' => $key->value(), + $nameOfId => $resource->id, + ], [ + 'value' => $fqdn, + 'is_build_time' => false, + 'is_preview' => false, + ]); + } elseif ($command->value() === 'URL') { + $fqdnFor = $key->after('SERVICE_URL_')->lower()->value(); + if (str($fqdnFor)->contains('_')) { + $fqdnFor = str($fqdnFor)->before('_'); + } + if ($isApplication) { + $fqdn = generateFqdn($server, "{$resource->name}-$uuid"); + } elseif ($isService) { + $fqdn = generateFqdn($server, "$fqdnFor-$uuid"); + } + $resource->environment_variables()->where('key', $key->value())->where($nameOfId, $resource->id)->firstOrCreate([ + 'key' => $key->value(), + $nameOfId => $resource->id, + ], [ + 'value' => $fqdn, + 'is_build_time' => false, + 'is_preview' => false, + ]); + + } else { + $value = generateEnvValue($command, $resource); + $resource->environment_variables()->where('key', $key->value())->where($nameOfId, $resource->id)->firstOrCreate([ + 'key' => $key->value(), + $nameOfId => $resource->id, + ], [ + 'value' => $value, + 'is_build_time' => false, + 'is_preview' => false, + ]); + } + } } } } + // Parse the rest of the services + foreach ($services as $serviceName => $service) { + $image = data_get_str($service, 'image'); + $restart = data_get_str($service, 'restart', RESTART_MODE); + $logging = data_get($service, 'logging'); + + if ($server->isLogDrainEnabled()) { + if ($resource instanceof Application && $resource->isLogDrainEnabled()) { + $logging = generate_fluentd_configuration(); + } + } + $volumes = collect(data_get($service, 'volumes', [])); + $networks = collect(data_get($service, 'networks', [])); + $use_network_mode = data_get($service, 'network_mode') !== null; + $depends_on = collect(data_get($service, 'depends_on', [])); + $labels = collect(data_get($service, 'labels', [])); + $environment = collect(data_get($service, 'environment', [])); + $ports = collect(data_get($service, 'ports', [])); + $buildArgs = collect(data_get($service, 'build.args', [])); + $environment = $environment->merge($buildArgs); + + $environment = convertComposeEnvironmentToArray($environment); + $coolifyEnvironments = collect([]); + + $isDatabase = isDatabaseImage(data_get_str($service, 'image')); + $volumesParsed = collect([]); + + if ($isApplication) { + $baseName = generateApplicationContainerName( + application: $resource, + pull_request_id: $pullRequestId + ); + $containerName = "$serviceName-$baseName"; + $predefinedPort = null; + } elseif ($isService) { + $containerName = "$serviceName-{$resource->uuid}"; + + if ($serviceName === 'registry') { + $tempServiceName = 'docker-registry'; + } else { + $tempServiceName = $serviceName; + } + if (str(data_get($service, 'image'))->contains('glitchtip')) { + $tempServiceName = 'glitchtip'; + } + if ($serviceName === 'supabase-kong') { + $tempServiceName = 'supabase'; + } + $serviceDefinition = data_get($allServices, $tempServiceName); + $predefinedPort = data_get($serviceDefinition, 'port'); + if ($serviceName === 'plausible') { + $predefinedPort = '8000'; + } + if ($isDatabase) { + $savedService = ServiceDatabase::firstOrCreate([ + 'name' => $serviceName, + 'image' => $image, + 'service_id' => $resource->id, + ]); + } else { + $savedService = ServiceApplication::firstOrCreate([ + 'name' => $serviceName, + 'image' => $image, + 'service_id' => $resource->id, + ]); + } + $fileStorages = $savedService->fileStorages(); + if ($savedService->image !== $image) { + $savedService->image = $image; + $savedService->save(); + } + } + + $originalResource = $isApplication ? $resource : $savedService; + + if ($volumes->count() > 0) { + foreach ($volumes as $index => $volume) { + $type = null; + $source = null; + $target = null; + $content = null; + $isDirectory = false; + if (is_string($volume)) { + $source = str($volume)->before(':'); + $target = str($volume)->after(':')->beforeLast(':'); + $foundConfig = $fileStorages->whereMountPath($target)->first(); + if (sourceIsLocal($source)) { + $type = str('bind'); + if ($foundConfig) { + $contentNotNull_temp = data_get($foundConfig, 'content'); + if ($contentNotNull_temp) { + $content = $contentNotNull_temp; + } + $isDirectory = data_get($foundConfig, 'is_directory'); + } else { + // By default, we cannot determine if the bind is a directory or not, so we set it to directory + $isDirectory = true; + } + } else { + $type = str('volume'); + } + } elseif (is_array($volume)) { + $type = data_get_str($volume, 'type'); + $source = data_get_str($volume, 'source'); + $target = data_get_str($volume, 'target'); + $content = data_get($volume, 'content'); + $isDirectory = (bool) data_get($volume, 'isDirectory', null) || (bool) data_get($volume, 'is_directory', null); + + $foundConfig = $fileStorages->whereMountPath($target)->first(); + if ($foundConfig) { + $contentNotNull_temp = data_get($foundConfig, 'content'); + if ($contentNotNull_temp) { + $content = $contentNotNull_temp; + } + $isDirectory = data_get($foundConfig, 'is_directory'); + } else { + // if isDirectory is not set (or false) & content is also not set, we assume it is a directory + if ((is_null($isDirectory) || ! $isDirectory) && is_null($content)) { + $isDirectory = true; + } + } + } + if ($type->value() === 'bind') { + if ($source->value() === '/var/run/docker.sock') { + $volume = $source->value().':'.$target->value(); + } elseif ($source->value() === '/tmp' || $source->value() === '/tmp/') { + $volume = $source->value().':'.$target->value(); + } else { + $mainDirectory = str(base_configuration_dir().'/applications/'.$uuid); + $source = replaceLocalSource($source, $mainDirectory); + if ($isApplication && $isPullRequest) { + $source = $source."-pr-$pullRequestId"; + } + LocalFileVolume::updateOrCreate( + [ + 'mount_path' => $target, + 'resource_id' => $originalResource->id, + 'resource_type' => get_class($originalResource), + ], + [ + 'fs_path' => $source, + 'mount_path' => $target, + 'content' => $content, + 'is_directory' => $isDirectory, + 'resource_id' => $originalResource->id, + 'resource_type' => get_class($originalResource), + ] + ); + $volume = "$source:$target"; + } + } elseif ($type->value() === 'volume') { + if ($topLevel->get('volumes')->has($source->value())) { + $temp = $topLevel->get('volumes')->get($source->value()); + if (data_get($temp, 'driver_opts.type') === 'cifs') { + continue; + } + if (data_get($temp, 'driver_opts.type') === 'nfs') { + continue; + } + } + $slugWithoutUuid = Str::slug($source, '-'); + $name = "{$uuid}_{$slugWithoutUuid}"; + + if ($isApplication && $isPullRequest) { + $name = "{$name}-pr-$pullRequestId"; + } + if (is_string($volume)) { + $source = str($volume)->before(':'); + $target = str($volume)->after(':')->beforeLast(':'); + $source = $name; + $volume = "$source:$target"; + } elseif (is_array($volume)) { + data_set($volume, 'source', $name); + } + $topLevel->get('volumes')->put($name, [ + 'name' => $name, + ]); + LocalPersistentVolume::updateOrCreate( + [ + 'name' => $name, + 'resource_id' => $originalResource->id, + 'resource_type' => get_class($originalResource), + ], + [ + 'name' => $name, + 'mount_path' => $target, + 'resource_id' => $originalResource->id, + 'resource_type' => get_class($originalResource), + ] + ); + } + dispatch(new ServerFilesFromServerJob($originalResource)); + $volumesParsed->put($index, $volume); + } + } + + if ($depends_on?->count() > 0) { + if ($isApplication && $isPullRequest) { + $newDependsOn = collect([]); + $depends_on->each(function ($dependency, $condition) use ($pullRequestId, $newDependsOn) { + if (is_numeric($condition)) { + $dependency = "$dependency-pr-$pullRequestId"; + + $newDependsOn->put($condition, $dependency); + } else { + $condition = "$condition-pr-$pullRequestId"; + $newDependsOn->put($condition, $dependency); + } + }); + $depends_on = $newDependsOn; + } + } + if (! $use_network_mode) { + if ($topLevel->get('networks')?->count() > 0) { + foreach ($topLevel->get('networks') as $networkName => $network) { + if ($networkName === 'default') { + continue; + } + // ignore aliases + if ($network['aliases'] ?? false) { + continue; + } + $networkExists = $networks->contains(function ($value, $key) use ($networkName) { + return $value == $networkName || $key == $networkName; + }); + if (! $networkExists) { + $networks->put($networkName, null); + } + } + } + $baseNetworkExists = $networks->contains(function ($value, $_) use ($baseNetwork) { + return $value == $baseNetwork; + }); + if (! $baseNetworkExists) { + foreach ($baseNetwork as $network) { + $topLevel->get('networks')->put($network, [ + 'name' => $network, + 'external' => true, + ]); + } + } + } + + // Collect/create/update ports + $collectedPorts = collect([]); + if ($ports->count() > 0) { + foreach ($ports as $sport) { + if (is_string($sport) || is_numeric($sport)) { + $collectedPorts->push($sport); + } + if (is_array($sport)) { + $target = data_get($sport, 'target'); + $published = data_get($sport, 'published'); + $protocol = data_get($sport, 'protocol'); + $collectedPorts->push("$target:$published/$protocol"); + } + } + } + if ($isService) { + $originalResource->ports = $collectedPorts->implode(','); + $originalResource->save(); + } + + $networks_temp = collect(); + + if (! $use_network_mode) { + foreach ($networks as $key => $network) { + if (gettype($network) === 'string') { + // networks: + // - appwrite + $networks_temp->put($network, null); + } elseif (gettype($network) === 'array') { + // networks: + // default: + // ipv4_address: 192.168.203.254 + $networks_temp->put($key, $network); + } + } + foreach ($baseNetwork as $key => $network) { + $networks_temp->put($network, null); + } + + if ($isApplication) { + if (data_get($resource, 'settings.connect_to_docker_network')) { + $network = $resource->destination->network; + $networks_temp->put($network, null); + $topLevel->get('networks')->put($network, [ + 'name' => $network, + 'external' => true, + ]); + } + } + } + + $normalEnvironments = $environment->diffKeys($allMagicEnvironments); + $normalEnvironments = $normalEnvironments->filter(function ($value, $key) { + return ! str($value)->startsWith('SERVICE_'); + }); + + foreach ($normalEnvironments as $key => $value) { + $key = str($key); + $value = str($value); + $originalValue = $value; + $parsedValue = replaceVariables($value); + if ($value->startsWith('$SERVICE_')) { + $resource->environment_variables()->where('key', $key)->where($nameOfId, $resource->id)->firstOrCreate([ + 'key' => $key, + $nameOfId => $resource->id, + ], [ + 'value' => $value, + 'is_build_time' => false, + 'is_preview' => false, + ]); + + continue; + } + if (! $value->startsWith('$')) { + continue; + } + if ($key->value() === $parsedValue->value()) { + $value = null; + $resource->environment_variables()->where('key', $key)->where($nameOfId, $resource->id)->firstOrCreate([ + 'key' => $key, + $nameOfId => $resource->id, + ], [ + 'value' => $value, + 'is_build_time' => false, + 'is_preview' => false, + ]); + } else { + if ($value->startsWith('$')) { + if ($value->contains(':-')) { + $value = replaceVariables($value); + $key = $value->before(':'); + $value = $value->after(':-'); + } elseif ($value->contains('-')) { + $value = replaceVariables($value); + + $key = $value->before('-'); + $value = $value->after('-'); + } elseif ($value->contains(':?')) { + $value = replaceVariables($value); + + $key = $value->before(':'); + $value = $value->after(':?'); + } elseif ($value->contains('?')) { + $value = replaceVariables($value); + + $key = $value->before('?'); + $value = $value->after('?'); + } + if ($originalValue->value() === $value->value()) { + continue; + } + $resource->environment_variables()->where('key', $key)->where($nameOfId, $resource->id)->firstOrCreate([ + 'key' => $key, + $nameOfId => $resource->id, + ], [ + 'value' => $value, + 'is_build_time' => false, + 'is_preview' => false, + ]); + } + + } + } + if ($isApplication) { + $branch = $originalResource->git_branch; + if ($pullRequestId !== 0) { + $branch = "pull/{$pullRequestId}/head"; + } + if ($originalResource->environment_variables->where('key', 'COOLIFY_BRANCH')->isEmpty()) { + $coolifyEnvironments->put('COOLIFY_BRANCH', "\"{$branch}\""); + } + } + + // Add COOLIFY_CONTAINER_NAME to environment + if ($resource->environment_variables->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) { + $coolifyEnvironments->put('COOLIFY_CONTAINER_NAME', "\"{$containerName}\""); + } + + if ($isApplication) { + $domains = collect(json_decode($resource->docker_compose_domains)) ?? collect([]); + $fqdns = data_get($domains, "$serviceName.domain"); + if ($fqdns) { + $fqdns = str($fqdns)->explode(','); + if ($isPullRequest) { + $preview = $resource->previews()->find($preview_id); + $docker_compose_domains = collect(json_decode(data_get($preview, 'docker_compose_domains'))); + if ($docker_compose_domains->count() > 0) { + $found_fqdn = data_get($docker_compose_domains, "$serviceName.domain"); + if ($found_fqdn) { + $fqdns = collect($found_fqdn); + } else { + $fqdns = collect([]); + } + } else { + $fqdns = $fqdns->map(function ($fqdn) use ($pullRequestId, $resource) { + $preview = ApplicationPreview::findPreviewByApplicationAndPullId($resource->id, $pullRequestId); + $url = Url::fromString($fqdn); + $template = $resource->preview_url_template; + $host = $url->getHost(); + $schema = $url->getScheme(); + $random = new Cuid2; + $preview_fqdn = str_replace('{{random}}', $random, $template); + $preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn); + $preview_fqdn = str_replace('{{pr_id}}', $pullRequestId, $preview_fqdn); + $preview_fqdn = "$schema://$preview_fqdn"; + $preview->fqdn = $preview_fqdn; + $preview->save(); + + return $preview_fqdn; + }); + } + } + } + $defaultLabels = defaultLabels( + id: $resource->id, + name: $containerName, + pull_request_id: $pullRequestId, + type: 'application' + ); + } elseif ($isService) { + if ($savedService->serviceType()) { + $fqdns = generateServiceSpecificFqdns($savedService); + } else { + $fqdns = collect(data_get($savedService, 'fqdns'))->filter(); + } + $defaultLabels = defaultLabels($resource->id, $containerName, type: 'service', subType: $isDatabase ? 'database' : 'application', subId: $savedService->id); + } + // Add COOLIFY_FQDN & COOLIFY_URL to environment + if (! $isDatabase && $fqdns instanceof Collection && $fqdns->count() > 0) { + $coolifyEnvironments->put('COOLIFY_URL', $fqdns->implode(',')); + + $urls = $fqdns->map(function ($fqdn) { + return str($fqdn)->replace('http://', '')->replace('https://', ''); + }); + $coolifyEnvironments->put('COOLIFY_FQDN', $urls->implode(',')); + } + add_coolify_default_environment_variables($resource, $coolifyEnvironments, $resource->environment_variables); + + if ($environment->count() > 0) { + $environment = $environment->filter(function ($value, $key) { + return ! str($key)->startsWith('SERVICE_FQDN_'); + }); + } + $serviceLabels = $labels->merge($defaultLabels); + if (! $isDatabase && $fqdns instanceof Collection && $fqdns->count() > 0) { + if ($isApplication) { + $shouldGenerateLabelsExactly = $resource->destination->server->settings->generate_exact_labels; + $uuid = $resource->uuid; + $network = data_get($resource, 'destination.network'); + if ($isPullRequest) { + $uuid = "{$resource->uuid}-{$pullRequestId}"; + } + if ($isPullRequest) { + $network = "{$resource->destination->network}-{$pullRequestId}"; + } + } else { + $shouldGenerateLabelsExactly = $resource->server->settings->generate_exact_labels; + $uuid = $resource->uuid; + $network = data_get($resource, 'destination.network'); + } + if ($shouldGenerateLabelsExactly) { + switch ($server->proxyType()) { + case ProxyTypes::TRAEFIK->value: + $serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik( + uuid: $uuid, + domains: $fqdns, + is_force_https_enabled: true, + serviceLabels: $serviceLabels, + is_gzip_enabled: $originalResource->isGzipEnabled(), + is_stripprefix_enabled: $originalResource->isStripprefixEnabled(), + service_name: $serviceName, + image: $image + )); + break; + case ProxyTypes::CADDY->value: + $serviceLabels = $serviceLabels->merge(fqdnLabelsForCaddy( + network: $network, + uuid: $uuid, + domains: $fqdns, + is_force_https_enabled: true, + serviceLabels: $serviceLabels, + is_gzip_enabled: $originalResource->isGzipEnabled(), + is_stripprefix_enabled: $originalResource->isStripprefixEnabled(), + service_name: $serviceName, + image: $image, + predefinedPort: $predefinedPort + )); + break; + } + } else { + $serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik( + uuid: $uuid, + domains: $fqdns, + is_force_https_enabled: true, + serviceLabels: $serviceLabels, + is_gzip_enabled: $originalResource->isGzipEnabled(), + is_stripprefix_enabled: $originalResource->isStripprefixEnabled(), + service_name: $serviceName, + image: $image + )); + $serviceLabels = $serviceLabels->merge(fqdnLabelsForCaddy( + network: $network, + uuid: $uuid, + domains: $fqdns, + is_force_https_enabled: true, + serviceLabels: $serviceLabels, + is_gzip_enabled: $originalResource->isGzipEnabled(), + is_stripprefix_enabled: $originalResource->isStripprefixEnabled(), + service_name: $serviceName, + image: $image, + predefinedPort: $predefinedPort + + )); + } + } + if ($isService) { + if (data_get($service, 'restart') === 'no' || data_get($service, 'exclude_from_hc')) { + $savedService->update(['exclude_from_status' => true]); + } + } + data_forget($service, 'volumes.*.content'); + data_forget($service, 'volumes.*.isDirectory'); + data_forget($service, 'volumes.*.is_directory'); + data_forget($service, 'exclude_from_hc'); + + $payload = collect($service)->merge([ + 'container_name' => $containerName, + 'restart' => $restart->value(), + 'labels' => $serviceLabels, + ]); + if (! $use_network_mode) { + $payload['networks'] = $networks_temp; + } + if ($ports->count() > 0) { + $payload['ports'] = $ports; + } + if ($volumesParsed->count() > 0) { + $payload['volumes'] = $volumesParsed; + } + if ($environment->count() > 0 || $coolifyEnvironments->count() > 0) { + $payload['environment'] = $environment->merge($coolifyEnvironments); + } + if ($logging) { + $payload['logging'] = $logging; + } + if ($depends_on->count() > 0) { + $payload['depends_on'] = $depends_on; + } + if ($isApplication && $isPullRequest) { + $serviceName = "{$serviceName}-pr-{$pullRequestId}"; + } + + $parsedServices->put($serviceName, $payload); + } + $topLevel->put('services', $parsedServices); + $customOrder = ['services', 'volumes', 'networks', 'configs', 'secrets']; + + $topLevel = $topLevel->sortBy(function ($value, $key) use ($customOrder) { + return array_search($key, $customOrder); + }); + + $resource->docker_compose = Yaml::dump(convertToArray($topLevel), 10, 2); + data_forget($resource, 'environment_variables'); + data_forget($resource, 'environment_variables_preview'); + $resource->save(); + + return $topLevel; +} + +function generate_fluentd_configuration(): array +{ return [ - 'command' => $command, - 'forService' => $forService, - 'generatedValue' => $generatedValue, - 'port' => $port, + 'driver' => 'fluentd', + 'options' => [ + 'fluentd-address' => 'tcp://127.0.0.1:24224', + 'fluentd-async' => 'true', + 'fluentd-sub-second-precision' => 'true', + // env vars are used in the LogDrain configurations + 'env' => 'COOLIFY_APP_NAME,COOLIFY_PROJECT_NAME,COOLIFY_SERVER_IP,COOLIFY_ENVIRONMENT_NAME', + ], ]; } -function generateEnvValue(string $command, ?Service $service = null) + +function isAssociativeArray($array) { - switch ($command) { - case 'PASSWORD': - $generatedValue = Str::password(symbols: false); - break; - case 'PASSWORD_64': - $generatedValue = Str::password(length: 64, symbols: false); - break; - // This is not base64, it's just a random string - case 'BASE64_64': - $generatedValue = Str::random(64); - break; - case 'BASE64_128': - $generatedValue = Str::random(128); - break; - case 'BASE64': - case 'BASE64_32': - $generatedValue = Str::random(32); - break; - // This is base64, - case 'REALBASE64_64': - $generatedValue = base64_encode(Str::random(64)); - break; - case 'REALBASE64_128': - $generatedValue = base64_encode(Str::random(128)); - break; - case 'REALBASE64': - case 'REALBASE64_32': - $generatedValue = base64_encode(Str::random(32)); - break; - case 'USER': - $generatedValue = Str::random(16); - break; - case 'SUPABASEANON': - $signingKey = $service->environment_variables()->where('key', 'SERVICE_PASSWORD_JWT')->first(); - if (is_null($signingKey)) { - return; - } else { - $signingKey = $signingKey->value; - } - $key = InMemory::plainText($signingKey); - $algorithm = new Sha256; - $tokenBuilder = (new Builder(new JoseEncoder, ChainedFormatter::default())); - $now = new DateTimeImmutable; - $now = $now->setTime($now->format('H'), $now->format('i')); - $token = $tokenBuilder - ->issuedBy('supabase') - ->issuedAt($now) - ->expiresAt($now->modify('+100 year')) - ->withClaim('role', 'anon') - ->getToken($algorithm, $key); - $generatedValue = $token->toString(); - break; - case 'SUPABASESERVICE': - $signingKey = $service->environment_variables()->where('key', 'SERVICE_PASSWORD_JWT')->first(); - if (is_null($signingKey)) { - return; - } else { - $signingKey = $signingKey->value; - } - $key = InMemory::plainText($signingKey); - $algorithm = new Sha256; - $tokenBuilder = (new Builder(new JoseEncoder, ChainedFormatter::default())); - $now = new DateTimeImmutable; - $now = $now->setTime($now->format('H'), $now->format('i')); - $token = $tokenBuilder - ->issuedBy('supabase') - ->issuedAt($now) - ->expiresAt($now->modify('+100 year')) - ->withClaim('role', 'service_role') - ->getToken($algorithm, $key); - $generatedValue = $token->toString(); - break; - default: - $generatedValue = Str::random(16); - break; + if ($array instanceof Collection) { + $array = $array->toArray(); } - return $generatedValue; + if (! is_array($array)) { + throw new \InvalidArgumentException('Input must be an array or a Collection.'); + } + + if ($array === []) { + return false; + } + + return array_keys($array) !== range(0, count($array) - 1); } -function getRealtime() +/** + * This method adds the default environment variables to the resource. + * - COOLIFY_APP_NAME + * - COOLIFY_PROJECT_NAME + * - COOLIFY_SERVER_IP + * - COOLIFY_ENVIRONMENT_NAME + * + * Theses variables are added in place to the $where_to_add array. + */ +function add_coolify_default_environment_variables(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse|Application|Service $resource, Collection &$where_to_add, ?Collection $where_to_check = null) { - $envDefined = env('PUSHER_PORT'); - if (empty($envDefined)) { - $url = Url::fromString(Request::getSchemeAndHttpHost()); - $port = $url->getPort(); - if ($port) { - return '6001'; + if ($resource instanceof Service) { + $ip = $resource->server->ip; + } else { + $ip = $resource->destination->server->ip; + } + if (isAssociativeArray($where_to_add)) { + $isAssociativeArray = true; + } else { + $isAssociativeArray = false; + } + if ($where_to_check != null && $where_to_check->where('key', 'COOLIFY_APP_NAME')->isEmpty()) { + if ($isAssociativeArray) { + $where_to_add->put('COOLIFY_APP_NAME', "\"{$resource->name}\""); } else { - return null; - } - } else { - return $envDefined; - } -} - -function validate_dns_entry(string $fqdn, Server $server) -{ - // https://www.cloudflare.com/ips-v4/# - $cloudflare_ips = collect(['173.245.48.0/20', '103.21.244.0/22', '103.22.200.0/22', '103.31.4.0/22', '141.101.64.0/18', '108.162.192.0/18', '190.93.240.0/20', '188.114.96.0/20', '197.234.240.0/22', '198.41.128.0/17', '162.158.0.0/15', '104.16.0.0/13', '172.64.0.0/13', '131.0.72.0/22']); - - $url = Url::fromString($fqdn); - $host = $url->getHost(); - if (str($host)->contains('sslip.io')) { - return true; - } - $settings = \App\Models\InstanceSettings::get(); - $is_dns_validation_enabled = data_get($settings, 'is_dns_validation_enabled'); - if (! $is_dns_validation_enabled) { - return true; - } - $dns_servers = data_get($settings, 'custom_dns_servers'); - $dns_servers = str($dns_servers)->explode(','); - if ($server->id === 0) { - $ip = data_get($settings, 'public_ipv4', data_get($settings, 'public_ipv6', $server->ip)); - } else { - $ip = $server->ip; - } - $found_matching_ip = false; - $type = \PurplePixie\PhpDns\DNSTypes::NAME_A; - foreach ($dns_servers as $dns_server) { - try { - ray("Checking $host on $dns_server"); - $query = new DNSQuery($dns_server); - $results = $query->query($host, $type); - if ($results === false || $query->hasError()) { - ray('Error: '.$query->getLasterror()); - } else { - foreach ($results as $result) { - if ($result->getType() == $type) { - if (ip_match($result->getData(), $cloudflare_ips->toArray(), $match)) { - ray("Found match in Cloudflare IPs: $match"); - $found_matching_ip = true; - break; - } - if ($result->getData() === $ip) { - ray($host.' has IP address '.$result->getData()); - ray($result->getString()); - $found_matching_ip = true; - break; - } - } - } - } - } catch (\Exception $e) { + $where_to_add->push("COOLIFY_APP_NAME=\"{$resource->name}\""); } } - ray("Found match: $found_matching_ip"); - - return $found_matching_ip; -} - -function ip_match($ip, $cidrs, &$match = null) -{ - foreach ((array) $cidrs as $cidr) { - [$subnet, $mask] = explode('/', $cidr); - if (((ip2long($ip) & ($mask = ~((1 << (32 - $mask)) - 1))) == (ip2long($subnet) & $mask))) { - $match = $cidr; - - return true; - } - } - - return false; -} -function checkIfDomainIsAlreadyUsed(Collection|array $domains, ?string $teamId = null, ?string $uuid = null) -{ - if (is_null($teamId)) { - return response()->json(['error' => 'Team ID is required.'], 400); - } - if (is_array($domains)) { - $domains = collect($domains); - } - - $domains = $domains->map(function ($domain) { - if (str($domain)->endsWith('/')) { - $domain = str($domain)->beforeLast('/'); - } - - return str($domain); - }); - $applications = Application::ownedByCurrentTeamAPI($teamId)->get(['fqdn', 'uuid']); - $serviceApplications = ServiceApplication::ownedByCurrentTeamAPI($teamId)->get(['fqdn', 'uuid']); - if ($uuid) { - $applications = $applications->filter(fn ($app) => $app->uuid !== $uuid); - $serviceApplications = $serviceApplications->filter(fn ($app) => $app->uuid !== $uuid); - } - $domainFound = false; - foreach ($applications as $app) { - if (is_null($app->fqdn)) { - continue; - } - $list_of_domains = collect(explode(',', $app->fqdn))->filter(fn ($fqdn) => $fqdn !== ''); - foreach ($list_of_domains as $domain) { - if (str($domain)->endsWith('/')) { - $domain = str($domain)->beforeLast('/'); - } - $naked_domain = str($domain)->value(); - if ($domains->contains($naked_domain)) { - $domainFound = true; - break; - } - } - } - if ($domainFound) { - return true; - } - foreach ($serviceApplications as $app) { - if (str($app->fqdn)->isEmpty()) { - continue; - } - $list_of_domains = collect(explode(',', $app->fqdn))->filter(fn ($fqdn) => $fqdn !== ''); - foreach ($list_of_domains as $domain) { - if (str($domain)->endsWith('/')) { - $domain = str($domain)->beforeLast('/'); - } - $naked_domain = str($domain)->value(); - if ($domains->contains($naked_domain)) { - $domainFound = true; - break; - } - } - } - if ($domainFound) { - return true; - } - $settings = \App\Models\InstanceSettings::get(); - if (data_get($settings, 'fqdn')) { - $domain = data_get($settings, 'fqdn'); - if (str($domain)->endsWith('/')) { - $domain = str($domain)->beforeLast('/'); - } - $naked_domain = str($domain)->value(); - if ($domains->contains($naked_domain)) { - return true; - } - } -} -function check_domain_usage(ServiceApplication|Application|null $resource = null, ?string $domain = null) -{ - if ($resource) { - if ($resource->getMorphClass() === 'App\Models\Application' && $resource->build_pack === 'dockercompose') { - $domains = data_get(json_decode($resource->docker_compose_domains, true), '*.domain'); - $domains = collect($domains); + if ($where_to_check != null && $where_to_check->where('key', 'COOLIFY_SERVER_IP')->isEmpty()) { + if ($isAssociativeArray) { + $where_to_add->put('COOLIFY_SERVER_IP', "\"{$ip}\""); } else { - $domains = collect($resource->fqdns); + $where_to_add->push("COOLIFY_SERVER_IP=\"{$ip}\""); } - } elseif ($domain) { - $domains = collect($domain); + } + if ($where_to_check != null && $where_to_check->where('key', 'COOLIFY_ENVIRONMENT_NAME')->isEmpty()) { + if ($isAssociativeArray) { + $where_to_add->put('COOLIFY_ENVIRONMENT_NAME', "\"{$resource->environment->name}\""); + } else { + $where_to_add->push("COOLIFY_ENVIRONMENT_NAME=\"{$resource->environment->name}\""); + } + } + if ($where_to_check != null && $where_to_check->where('key', 'COOLIFY_PROJECT_NAME')->isEmpty()) { + if ($isAssociativeArray) { + $where_to_add->put('COOLIFY_PROJECT_NAME', "\"{$resource->project()->name}\""); + } else { + $where_to_add->push("COOLIFY_PROJECT_NAME=\"{$resource->project()->name}\""); + } + } +} + +function convertComposeEnvironmentToArray($environment) +{ + $convertedServiceVariables = collect([]); + if (isAssociativeArray($environment)) { + $convertedServiceVariables = $environment; } else { - throw new \RuntimeException('No resource or FQDN provided.'); - } - $domains = $domains->map(function ($domain) { - if (str($domain)->endsWith('/')) { - $domain = str($domain)->beforeLast('/'); - } - - return str($domain); - }); - $apps = Application::all(); - foreach ($apps as $app) { - $list_of_domains = collect(explode(',', $app->fqdn))->filter(fn ($fqdn) => $fqdn !== ''); - foreach ($list_of_domains as $domain) { - if (str($domain)->endsWith('/')) { - $domain = str($domain)->beforeLast('/'); - } - $naked_domain = str($domain)->value(); - if ($domains->contains($naked_domain)) { - if (data_get($resource, 'uuid')) { - if ($resource->uuid !== $app->uuid) { - throw new \RuntimeException("Domain $naked_domain is already in use by another resource called:

{$app->name}."); - } - } elseif ($domain) { - throw new \RuntimeException("Domain $naked_domain is already in use by another resource called:

{$app->name}."); - } - } - } - } - $apps = ServiceApplication::all(); - foreach ($apps as $app) { - $list_of_domains = collect(explode(',', $app->fqdn))->filter(fn ($fqdn) => $fqdn !== ''); - foreach ($list_of_domains as $domain) { - if (str($domain)->endsWith('/')) { - $domain = str($domain)->beforeLast('/'); - } - $naked_domain = str($domain)->value(); - if ($domains->contains($naked_domain)) { - if (data_get($resource, 'uuid')) { - if ($resource->uuid !== $app->uuid) { - throw new \RuntimeException("Domain $naked_domain is already in use by another resource called:

{$app->name}."); - } - } elseif ($domain) { - throw new \RuntimeException("Domain $naked_domain is already in use by another resource called:

{$app->name}."); - } - } - } - } - if ($resource) { - $settings = \App\Models\InstanceSettings::get(); - if (data_get($settings, 'fqdn')) { - $domain = data_get($settings, 'fqdn'); - if (str($domain)->endsWith('/')) { - $domain = str($domain)->beforeLast('/'); - } - $naked_domain = str($domain)->value(); - if ($domains->contains($naked_domain)) { - throw new \RuntimeException("Domain $naked_domain is already in use by this Coolify instance."); + foreach ($environment as $value) { + $parts = explode('=', $value, 2); + $key = $parts[0]; + $realValue = $parts[1] ?? ''; + if ($key) { + $convertedServiceVariables->put($key, $realValue); } } } -} - -function parseCommandsByLineForSudo(Collection $commands, Server $server): array -{ - $commands = $commands->map(function ($line) { - if (! str($line)->startsWith('cd') && ! str($line)->startsWith('command') && ! str($line)->startsWith('echo') && ! str($line)->startsWith('true')) { - return "sudo $line"; - } - - return $line; - }); - $commands = $commands->map(function ($line) use ($server) { - if (Str::startsWith($line, 'sudo mkdir -p')) { - return "$line && sudo chown -R $server->user:$server->user ".Str::after($line, 'sudo mkdir -p').' && sudo chmod -R o-rwx '.Str::after($line, 'sudo mkdir -p'); - } - - return $line; - }); - $commands = $commands->map(function ($line) { - $line = str($line); - if (str($line)->contains('$(')) { - $line = $line->replace('$(', '$(sudo '); - } - if (str($line)->contains('||')) { - $line = $line->replace('||', '|| sudo'); - } - if (str($line)->contains('&&')) { - $line = $line->replace('&&', '&& sudo'); - } - if (str($line)->contains(' | ')) { - $line = $line->replace(' | ', ' | sudo '); - } - - return $line->value(); - }); - - return $commands->toArray(); -} -function parseLineForSudo(string $command, Server $server): string -{ - if (! str($command)->startSwith('cd') && ! str($command)->startSwith('command')) { - $command = "sudo $command"; - } - if (Str::startsWith($command, 'sudo mkdir -p')) { - $command = "$command && sudo chown -R $server->user:$server->user ".Str::after($command, 'sudo mkdir -p').' && sudo chmod -R o-rwx '.Str::after($command, 'sudo mkdir -p'); - } - if (str($command)->contains('$(') || str($command)->contains('`')) { - $command = str($command)->replace('$(', '$(sudo ')->replace('`', '`sudo ')->value(); - } - if (str($command)->contains('||')) { - $command = str($command)->replace('||', '|| sudo ')->value(); - } - if (str($command)->contains('&&')) { - $command = str($command)->replace('&&', '&& sudo ')->value(); - } - - return $command; -} - -function get_public_ips() -{ - try { - echo "Refreshing public ips!\n"; - $settings = \App\Models\InstanceSettings::get(); - [$first, $second] = Process::concurrently(function (Pool $pool) { - $pool->path(__DIR__)->command('curl -4s https://ifconfig.io'); - $pool->path(__DIR__)->command('curl -6s https://ifconfig.io'); - }); - $ipv4 = $first->output(); - if ($ipv4) { - $ipv4 = trim($ipv4); - $validate_ipv4 = filter_var($ipv4, FILTER_VALIDATE_IP); - if ($validate_ipv4 == false) { - echo "Invalid ipv4: $ipv4\n"; - - return; - } - $settings->update(['public_ipv4' => $ipv4]); - } - $ipv6 = $second->output(); - if ($ipv6) { - $ipv6 = trim($ipv6); - $validate_ipv6 = filter_var($ipv6, FILTER_VALIDATE_IP); - if ($validate_ipv6 == false) { - echo "Invalid ipv6: $ipv6\n"; - - return; - } - $settings->update(['public_ipv6' => $ipv6]); - } - } catch (\Throwable $e) { - echo "Error: {$e->getMessage()}\n"; - } -} - -function isAnyDeploymentInprogress() -{ - // Only use it in the deployment script - $count = ApplicationDeploymentQueue::whereIn('status', [ApplicationDeploymentStatus::IN_PROGRESS, ApplicationDeploymentStatus::QUEUED])->count(); - if ($count > 0) { - echo "There are $count deployments in progress. Exiting...\n"; - exit(1); - } - echo "No deployments in progress.\n"; - exit(0); -} - -function generateSentinelToken() -{ - $token = Str::random(64); - - return $token; -} - -function isBase64Encoded($strValue) -{ - return base64_encode(base64_decode($strValue, true)) === $strValue; -} -function customApiValidator(Collection|array $item, array $rules) -{ - if (is_array($item)) { - $item = collect($item); - } - - return Validator::make($item->toArray(), $rules, [ - 'required' => 'This field is required.', - ]); + + return $convertedServiceVariables; + } diff --git a/composer.json b/composer.json index a3189d341..e8b46105d 100644 --- a/composer.json +++ b/composer.json @@ -14,10 +14,11 @@ "guzzlehttp/guzzle": "^7.5.0", "laravel/fortify": "^v1.16.0", "laravel/framework": "^v11", - "laravel/horizon": "^5.23.1", + "laravel/horizon": "^5.27.1", "laravel/prompts": "^0.1.6", "laravel/sanctum": "^v4.0", "laravel/socialite": "^v5.14.0", + "laravel/telescope": "^5.2", "laravel/tinker": "^v2.8.1", "laravel/ui": "^4.2", "lcobucci/jwt": "^5.0.0", @@ -93,7 +94,9 @@ }, "extra": { "laravel": { - "dont-discover": [] + "dont-discover": [ + "laravel/telescope" + ] } }, "config": { diff --git a/composer.lock b/composer.lock index 367dda58d..fffb320d3 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "cb17445966de6094aef5a92ee59d1d77", + "content-hash": "96f8146407d0e6e897ff097c5eccd3a4", "packages": [ { "name": "amphp/amp", @@ -740,16 +740,16 @@ }, { "name": "amphp/sync", - "version": "v2.2.0", + "version": "v2.3.0", "source": { "type": "git", "url": "https://github.com/amphp/sync.git", - "reference": "375ef5b54a0d12c38e12728dde05a55e30f2fbec" + "reference": "217097b785130d77cfcc58ff583cf26cd1770bf1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/sync/zipball/375ef5b54a0d12c38e12728dde05a55e30f2fbec", - "reference": "375ef5b54a0d12c38e12728dde05a55e30f2fbec", + "url": "https://api.github.com/repos/amphp/sync/zipball/217097b785130d77cfcc58ff583cf26cd1770bf1", + "reference": "217097b785130d77cfcc58ff583cf26cd1770bf1", "shasum": "" }, "require": { @@ -803,7 +803,7 @@ ], "support": { "issues": "https://github.com/amphp/sync/issues", - "source": "https://github.com/amphp/sync/tree/v2.2.0" + "source": "https://github.com/amphp/sync/tree/v2.3.0" }, "funding": [ { @@ -811,7 +811,7 @@ "type": "github" } ], - "time": "2024-03-12T01:00:01+00:00" + "time": "2024-08-03T19:31:26+00:00" }, { "name": "amphp/windows-registry", @@ -921,16 +921,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.316.1", + "version": "3.321.9", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "888cee2adf890a5b749cc22c0f05051b53619d33" + "reference": "5de5099cfe0e17cb3eb2fe51de0101c99bc9442a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/888cee2adf890a5b749cc22c0f05051b53619d33", - "reference": "888cee2adf890a5b749cc22c0f05051b53619d33", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/5de5099cfe0e17cb3eb2fe51de0101c99bc9442a", + "reference": "5de5099cfe0e17cb3eb2fe51de0101c99bc9442a", "shasum": "" }, "require": { @@ -983,7 +983,10 @@ ], "psr-4": { "Aws\\": "src/" - } + }, + "exclude-from-classmap": [ + "src/data/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1010,9 +1013,9 @@ "support": { "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.316.1" + "source": "https://github.com/aws/aws-sdk-php/tree/3.321.9" }, - "time": "2024-07-09T18:09:27+00:00" + "time": "2024-09-11T18:15:49+00:00" }, { "name": "bacon/bacon-qr-code", @@ -1253,23 +1256,23 @@ }, { "name": "dasprid/enum", - "version": "1.0.5", + "version": "1.0.6", "source": { "type": "git", "url": "https://github.com/DASPRiD/Enum.git", - "reference": "6faf451159fb8ba4126b925ed2d78acfce0dc016" + "reference": "8dfd07c6d2cf31c8da90c53b83c026c7696dda90" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/DASPRiD/Enum/zipball/6faf451159fb8ba4126b925ed2d78acfce0dc016", - "reference": "6faf451159fb8ba4126b925ed2d78acfce0dc016", + "url": "https://api.github.com/repos/DASPRiD/Enum/zipball/8dfd07c6d2cf31c8da90c53b83c026c7696dda90", + "reference": "8dfd07c6d2cf31c8da90c53b83c026c7696dda90", "shasum": "" }, "require": { "php": ">=7.1 <9.0" }, "require-dev": { - "phpunit/phpunit": "^7 | ^8 | ^9", + "phpunit/phpunit": "^7 || ^8 || ^9 || ^10 || ^11", "squizlabs/php_codesniffer": "*" }, "type": "library", @@ -1297,9 +1300,9 @@ ], "support": { "issues": "https://github.com/DASPRiD/Enum/issues", - "source": "https://github.com/DASPRiD/Enum/tree/1.0.5" + "source": "https://github.com/DASPRiD/Enum/tree/1.0.6" }, - "time": "2023-08-25T16:18:39+00:00" + "time": "2024-08-09T14:30:48+00:00" }, { "name": "daverandom/libdns", @@ -1515,16 +1518,16 @@ }, { "name": "doctrine/dbal", - "version": "3.8.6", + "version": "3.9.1", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "b7411825cf7efb7e51f9791dea19d86e43b399a1" + "reference": "d7dc08f98cba352b2bab5d32c5e58f7e745c11a7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/b7411825cf7efb7e51f9791dea19d86e43b399a1", - "reference": "b7411825cf7efb7e51f9791dea19d86e43b399a1", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/d7dc08f98cba352b2bab5d32c5e58f7e745c11a7", + "reference": "d7dc08f98cba352b2bab5d32c5e58f7e745c11a7", "shasum": "" }, "require": { @@ -1540,12 +1543,12 @@ "doctrine/coding-standard": "12.0.0", "fig/log-test": "^1", "jetbrains/phpstorm-stubs": "2023.1", - "phpstan/phpstan": "1.11.5", + "phpstan/phpstan": "1.12.0", "phpstan/phpstan-strict-rules": "^1.6", - "phpunit/phpunit": "9.6.19", + "phpunit/phpunit": "9.6.20", "psalm/plugin-phpunit": "0.18.4", "slevomat/coding-standard": "8.13.1", - "squizlabs/php_codesniffer": "3.10.1", + "squizlabs/php_codesniffer": "3.10.2", "symfony/cache": "^5.4|^6.0|^7.0", "symfony/console": "^4.4|^5.4|^6.0|^7.0", "vimeo/psalm": "4.30.0" @@ -1608,7 +1611,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/3.8.6" + "source": "https://github.com/doctrine/dbal/tree/3.9.1" }, "funding": [ { @@ -1624,7 +1627,7 @@ "type": "tidelift" } ], - "time": "2024-06-19T10:38:17+00:00" + "time": "2024-09-01T13:49:23+00:00" }, { "name": "doctrine/deprecations", @@ -2196,24 +2199,24 @@ }, { "name": "graham-campbell/result-type", - "version": "v1.1.2", + "version": "v1.1.3", "source": { "type": "git", "url": "https://github.com/GrahamCampbell/Result-Type.git", - "reference": "fbd48bce38f73f8a4ec8583362e732e4095e5862" + "reference": "3ba905c11371512af9d9bdd27d99b782216b6945" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/fbd48bce38f73f8a4ec8583362e732e4095e5862", - "reference": "fbd48bce38f73f8a4ec8583362e732e4095e5862", + "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/3ba905c11371512af9d9bdd27d99b782216b6945", + "reference": "3ba905c11371512af9d9bdd27d99b782216b6945", "shasum": "" }, "require": { "php": "^7.2.5 || ^8.0", - "phpoption/phpoption": "^1.9.2" + "phpoption/phpoption": "^1.9.3" }, "require-dev": { - "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2" + "phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28" }, "type": "library", "autoload": { @@ -2242,7 +2245,7 @@ ], "support": { "issues": "https://github.com/GrahamCampbell/Result-Type/issues", - "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.2" + "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.3" }, "funding": [ { @@ -2254,26 +2257,26 @@ "type": "tidelift" } ], - "time": "2023-11-12T22:16:48+00:00" + "time": "2024-07-20T21:45:45+00:00" }, { "name": "guzzlehttp/guzzle", - "version": "7.8.1", + "version": "7.9.2", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "41042bc7ab002487b876a0683fc8dce04ddce104" + "reference": "d281ed313b989f213357e3be1a179f02196ac99b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/41042bc7ab002487b876a0683fc8dce04ddce104", - "reference": "41042bc7ab002487b876a0683fc8dce04ddce104", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/d281ed313b989f213357e3be1a179f02196ac99b", + "reference": "d281ed313b989f213357e3be1a179f02196ac99b", "shasum": "" }, "require": { "ext-json": "*", - "guzzlehttp/promises": "^1.5.3 || ^2.0.1", - "guzzlehttp/psr7": "^1.9.1 || ^2.5.1", + "guzzlehttp/promises": "^1.5.3 || ^2.0.3", + "guzzlehttp/psr7": "^2.7.0", "php": "^7.2.5 || ^8.0", "psr/http-client": "^1.0", "symfony/deprecation-contracts": "^2.2 || ^3.0" @@ -2284,9 +2287,9 @@ "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", "ext-curl": "*", - "php-http/client-integration-tests": "dev-master#2c025848417c1135031fdf9c728ee53d0a7ceaee as 3.0.999", + "guzzle/client-integration-tests": "3.0.2", "php-http/message-factory": "^1.1", - "phpunit/phpunit": "^8.5.36 || ^9.6.15", + "phpunit/phpunit": "^8.5.39 || ^9.6.20", "psr/log": "^1.1 || ^2.0 || ^3.0" }, "suggest": { @@ -2364,7 +2367,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.8.1" + "source": "https://github.com/guzzle/guzzle/tree/7.9.2" }, "funding": [ { @@ -2380,20 +2383,20 @@ "type": "tidelift" } ], - "time": "2023-12-03T20:35:24+00:00" + "time": "2024-07-24T11:22:20+00:00" }, { "name": "guzzlehttp/promises", - "version": "2.0.2", + "version": "2.0.3", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "bbff78d96034045e58e13dedd6ad91b5d1253223" + "reference": "6ea8dd08867a2a42619d65c3deb2c0fcbf81c8f8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/bbff78d96034045e58e13dedd6ad91b5d1253223", - "reference": "bbff78d96034045e58e13dedd6ad91b5d1253223", + "url": "https://api.github.com/repos/guzzle/promises/zipball/6ea8dd08867a2a42619d65c3deb2c0fcbf81c8f8", + "reference": "6ea8dd08867a2a42619d65c3deb2c0fcbf81c8f8", "shasum": "" }, "require": { @@ -2401,7 +2404,7 @@ }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "phpunit/phpunit": "^8.5.36 || ^9.6.15" + "phpunit/phpunit": "^8.5.39 || ^9.6.20" }, "type": "library", "extra": { @@ -2447,7 +2450,7 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/2.0.2" + "source": "https://github.com/guzzle/promises/tree/2.0.3" }, "funding": [ { @@ -2463,20 +2466,20 @@ "type": "tidelift" } ], - "time": "2023-12-03T20:19:20+00:00" + "time": "2024-07-18T10:29:17+00:00" }, { "name": "guzzlehttp/psr7", - "version": "2.6.2", + "version": "2.7.0", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221" + "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/45b30f99ac27b5ca93cb4831afe16285f57b8221", - "reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/a70f5c95fb43bc83f07c9c948baa0dc1829bf201", + "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201", "shasum": "" }, "require": { @@ -2491,8 +2494,8 @@ }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "http-interop/http-factory-tests": "^0.9", - "phpunit/phpunit": "^8.5.36 || ^9.6.15" + "http-interop/http-factory-tests": "0.9.0", + "phpunit/phpunit": "^8.5.39 || ^9.6.20" }, "suggest": { "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" @@ -2563,7 +2566,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.6.2" + "source": "https://github.com/guzzle/psr7/tree/2.7.0" }, "funding": [ { @@ -2579,7 +2582,7 @@ "type": "tidelift" } ], - "time": "2023-12-03T20:05:35+00:00" + "time": "2024-07-18T11:15:46+00:00" }, { "name": "guzzlehttp/uri-template", @@ -2786,16 +2789,16 @@ }, { "name": "laravel/fortify", - "version": "v1.21.5", + "version": "v1.24.1", "source": { "type": "git", "url": "https://github.com/laravel/fortify.git", - "reference": "3eaf01ec826c4f653628202640a4450784f78b15" + "reference": "8158ba0960bb5f4aae509d01d74a95e16e30de20" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/fortify/zipball/3eaf01ec826c4f653628202640a4450784f78b15", - "reference": "3eaf01ec826c4f653628202640a4450784f78b15", + "url": "https://api.github.com/repos/laravel/fortify/zipball/8158ba0960bb5f4aae509d01d74a95e16e30de20", + "reference": "8158ba0960bb5f4aae509d01d74a95e16e30de20", "shasum": "" }, "require": { @@ -2847,20 +2850,20 @@ "issues": "https://github.com/laravel/fortify/issues", "source": "https://github.com/laravel/fortify" }, - "time": "2024-07-04T14:36:27+00:00" + "time": "2024-09-03T10:02:14+00:00" }, { "name": "laravel/framework", - "version": "v11.15.0", + "version": "v11.23.2", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "ba85f1c019bed59b3c736c9c4502805efd0ba84b" + "reference": "d38bf0fd3a8936e1cb9ca8eb8d7304a564f790f3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/ba85f1c019bed59b3c736c9c4502805efd0ba84b", - "reference": "ba85f1c019bed59b3c736c9c4502805efd0ba84b", + "url": "https://api.github.com/repos/laravel/framework/zipball/d38bf0fd3a8936e1cb9ca8eb8d7304a564f790f3", + "reference": "d38bf0fd3a8936e1cb9ca8eb8d7304a564f790f3", "shasum": "" }, "require": { @@ -2922,6 +2925,7 @@ "illuminate/bus": "self.version", "illuminate/cache": "self.version", "illuminate/collections": "self.version", + "illuminate/concurrency": "self.version", "illuminate/conditionable": "self.version", "illuminate/config": "self.version", "illuminate/console": "self.version", @@ -2964,7 +2968,7 @@ "league/flysystem-sftp-v3": "^3.0", "mockery/mockery": "^1.6", "nyholm/psr7": "^1.2", - "orchestra/testbench-core": "^9.1.5", + "orchestra/testbench-core": "^9.4.0", "pda/pheanstalk": "^5.0", "phpstan/phpstan": "^1.11.5", "phpunit/phpunit": "^10.5|^11.0", @@ -3022,6 +3026,7 @@ "src/Illuminate/Events/functions.php", "src/Illuminate/Filesystem/functions.php", "src/Illuminate/Foundation/helpers.php", + "src/Illuminate/Log/functions.php", "src/Illuminate/Support/helpers.php" ], "psr-4": { @@ -3053,20 +3058,20 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2024-07-09T15:38:12+00:00" + "time": "2024-09-11T21:59:23+00:00" }, { "name": "laravel/horizon", - "version": "v5.25.0", + "version": "v5.28.1", "source": { "type": "git", "url": "https://github.com/laravel/horizon.git", - "reference": "81e62cee5b3feaf169d683b8890e33bf454698ab" + "reference": "9d2c4eaeb11408384401f8a7d1b0ea4c76554f3f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/horizon/zipball/81e62cee5b3feaf169d683b8890e33bf454698ab", - "reference": "81e62cee5b3feaf169d683b8890e33bf454698ab", + "url": "https://api.github.com/repos/laravel/horizon/zipball/9d2c4eaeb11408384401f8a7d1b0ea4c76554f3f", + "reference": "9d2c4eaeb11408384401f8a7d1b0ea4c76554f3f", "shasum": "" }, "require": { @@ -3130,22 +3135,22 @@ ], "support": { "issues": "https://github.com/laravel/horizon/issues", - "source": "https://github.com/laravel/horizon/tree/v5.25.0" + "source": "https://github.com/laravel/horizon/tree/v5.28.1" }, - "time": "2024-07-05T16:46:31+00:00" + "time": "2024-09-04T14:06:50+00:00" }, { "name": "laravel/prompts", - "version": "v0.1.24", + "version": "v0.1.25", "source": { "type": "git", "url": "https://github.com/laravel/prompts.git", - "reference": "409b0b4305273472f3754826e68f4edbd0150149" + "reference": "7b4029a84c37cb2725fc7f011586e2997040bc95" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/prompts/zipball/409b0b4305273472f3754826e68f4edbd0150149", - "reference": "409b0b4305273472f3754826e68f4edbd0150149", + "url": "https://api.github.com/repos/laravel/prompts/zipball/7b4029a84c37cb2725fc7f011586e2997040bc95", + "reference": "7b4029a84c37cb2725fc7f011586e2997040bc95", "shasum": "" }, "require": { @@ -3188,9 +3193,9 @@ "description": "Add beautiful and user-friendly forms to your command-line applications.", "support": { "issues": "https://github.com/laravel/prompts/issues", - "source": "https://github.com/laravel/prompts/tree/v0.1.24" + "source": "https://github.com/laravel/prompts/tree/v0.1.25" }, - "time": "2024-06-17T13:58:22+00:00" + "time": "2024-08-12T22:06:33+00:00" }, { "name": "laravel/sanctum", @@ -3258,26 +3263,27 @@ }, { "name": "laravel/serializable-closure", - "version": "v1.3.3", + "version": "v1.3.4", "source": { "type": "git", "url": "https://github.com/laravel/serializable-closure.git", - "reference": "3dbf8a8e914634c48d389c1234552666b3d43754" + "reference": "61b87392d986dc49ad5ef64e75b1ff5fee24ef81" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/3dbf8a8e914634c48d389c1234552666b3d43754", - "reference": "3dbf8a8e914634c48d389c1234552666b3d43754", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/61b87392d986dc49ad5ef64e75b1ff5fee24ef81", + "reference": "61b87392d986dc49ad5ef64e75b1ff5fee24ef81", "shasum": "" }, "require": { "php": "^7.3|^8.0" }, "require-dev": { - "nesbot/carbon": "^2.61", + "illuminate/support": "^8.0|^9.0|^10.0|^11.0", + "nesbot/carbon": "^2.61|^3.0", "pestphp/pest": "^1.21.3", "phpstan/phpstan": "^1.8.2", - "symfony/var-dumper": "^5.4.11" + "symfony/var-dumper": "^5.4.11|^6.2.0|^7.0.0" }, "type": "library", "extra": { @@ -3314,20 +3320,20 @@ "issues": "https://github.com/laravel/serializable-closure/issues", "source": "https://github.com/laravel/serializable-closure" }, - "time": "2023-11-08T14:08:06+00:00" + "time": "2024-08-02T07:48:17+00:00" }, { "name": "laravel/socialite", - "version": "v5.15.1", + "version": "v5.16.0", "source": { "type": "git", "url": "https://github.com/laravel/socialite.git", - "reference": "cc02625f0bd1f95dc3688eb041cce0f1e709d029" + "reference": "40a2dc98c53d9dc6d55eadb0d490d3d72b73f1bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/socialite/zipball/cc02625f0bd1f95dc3688eb041cce0f1e709d029", - "reference": "cc02625f0bd1f95dc3688eb041cce0f1e709d029", + "url": "https://api.github.com/repos/laravel/socialite/zipball/40a2dc98c53d9dc6d55eadb0d490d3d72b73f1bf", + "reference": "40a2dc98c53d9dc6d55eadb0d490d3d72b73f1bf", "shasum": "" }, "require": { @@ -3386,7 +3392,76 @@ "issues": "https://github.com/laravel/socialite/issues", "source": "https://github.com/laravel/socialite" }, - "time": "2024-06-28T20:09:34+00:00" + "time": "2024-09-03T09:46:57+00:00" + }, + { + "name": "laravel/telescope", + "version": "v5.2.2", + "source": { + "type": "git", + "url": "https://github.com/laravel/telescope.git", + "reference": "daaf95dee9fab2dd80f59b5f6611c6c0eff44878" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/telescope/zipball/daaf95dee9fab2dd80f59b5f6611c6c0eff44878", + "reference": "daaf95dee9fab2dd80f59b5f6611c6c0eff44878", + "shasum": "" + }, + "require": { + "ext-json": "*", + "laravel/framework": "^8.37|^9.0|^10.0|^11.0", + "php": "^8.0", + "symfony/console": "^5.3|^6.0|^7.0", + "symfony/var-dumper": "^5.0|^6.0|^7.0" + }, + "require-dev": { + "ext-gd": "*", + "guzzlehttp/guzzle": "^6.0|^7.0", + "laravel/octane": "^1.4|^2.0|dev-develop", + "orchestra/testbench": "^6.40|^7.37|^8.17|^9.0", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^9.0|^10.5" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Laravel\\Telescope\\TelescopeServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Laravel\\Telescope\\": "src/", + "Laravel\\Telescope\\Database\\Factories\\": "database/factories/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + }, + { + "name": "Mohamed Said", + "email": "mohamed@laravel.com" + } + ], + "description": "An elegant debug assistant for the Laravel framework.", + "keywords": [ + "debugging", + "laravel", + "monitoring" + ], + "support": { + "issues": "https://github.com/laravel/telescope/issues", + "source": "https://github.com/laravel/telescope/tree/v5.2.2" + }, + "time": "2024-08-26T12:40:52+00:00" }, { "name": "laravel/tinker", @@ -3592,16 +3667,16 @@ }, { "name": "league/commonmark", - "version": "2.4.2", + "version": "2.5.3", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "91c24291965bd6d7c46c46a12ba7492f83b1cadf" + "reference": "b650144166dfa7703e62a22e493b853b58d874b0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/91c24291965bd6d7c46c46a12ba7492f83b1cadf", - "reference": "91c24291965bd6d7c46c46a12ba7492f83b1cadf", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/b650144166dfa7703e62a22e493b853b58d874b0", + "reference": "b650144166dfa7703e62a22e493b853b58d874b0", "shasum": "" }, "require": { @@ -3614,8 +3689,8 @@ }, "require-dev": { "cebe/markdown": "^1.0", - "commonmark/cmark": "0.30.3", - "commonmark/commonmark.js": "0.30.0", + "commonmark/cmark": "0.31.1", + "commonmark/commonmark.js": "0.31.1", "composer/package-versions-deprecated": "^1.8", "embed/embed": "^4.4", "erusev/parsedown": "^1.0", @@ -3637,7 +3712,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "2.5-dev" + "dev-main": "2.6-dev" } }, "autoload": { @@ -3694,7 +3769,7 @@ "type": "tidelift" } ], - "time": "2024-02-02T11:59:32+00:00" + "time": "2024-08-16T11:46:16+00:00" }, { "name": "league/config", @@ -4459,16 +4534,16 @@ }, { "name": "lorisleiva/laravel-actions", - "version": "v2.8.0", + "version": "v2.8.4", "source": { "type": "git", "url": "https://github.com/lorisleiva/laravel-actions.git", - "reference": "d5c2ca544f40d85f877b38eb6d23e9c967ecb69f" + "reference": "5a168bfdd3b75dd6ff259019d4aeef784bbd5403" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/lorisleiva/laravel-actions/zipball/d5c2ca544f40d85f877b38eb6d23e9c967ecb69f", - "reference": "d5c2ca544f40d85f877b38eb6d23e9c967ecb69f", + "url": "https://api.github.com/repos/lorisleiva/laravel-actions/zipball/5a168bfdd3b75dd6ff259019d4aeef784bbd5403", + "reference": "5a168bfdd3b75dd6ff259019d4aeef784bbd5403", "shasum": "" }, "require": { @@ -4477,7 +4552,7 @@ "php": "^8.1" }, "require-dev": { - "orchestra/testbench": "^9.0", + "orchestra/testbench": "^8.0|^9.0", "pestphp/pest": "^1.23|^2.34", "phpunit/phpunit": "^9.6|^10.0" }, @@ -4518,11 +4593,12 @@ "controller", "job", "laravel", + "listener", "object" ], "support": { "issues": "https://github.com/lorisleiva/laravel-actions/issues", - "source": "https://github.com/lorisleiva/laravel-actions/tree/v2.8.0" + "source": "https://github.com/lorisleiva/laravel-actions/tree/v2.8.4" }, "funding": [ { @@ -4530,7 +4606,7 @@ "type": "github" } ], - "time": "2024-03-13T12:47:32+00:00" + "time": "2024-09-10T09:57:29+00:00" }, { "name": "lorisleiva/lody", @@ -4707,16 +4783,16 @@ }, { "name": "mtdowling/jmespath.php", - "version": "2.7.0", + "version": "2.8.0", "source": { "type": "git", "url": "https://github.com/jmespath/jmespath.php.git", - "reference": "bbb69a935c2cbb0c03d7f481a238027430f6440b" + "reference": "a2a865e05d5f420b50cc2f85bb78d565db12a6bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/bbb69a935c2cbb0c03d7f481a238027430f6440b", - "reference": "bbb69a935c2cbb0c03d7f481a238027430f6440b", + "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/a2a865e05d5f420b50cc2f85bb78d565db12a6bc", + "reference": "a2a865e05d5f420b50cc2f85bb78d565db12a6bc", "shasum": "" }, "require": { @@ -4733,7 +4809,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.7-dev" + "dev-master": "2.8-dev" } }, "autoload": { @@ -4767,22 +4843,22 @@ ], "support": { "issues": "https://github.com/jmespath/jmespath.php/issues", - "source": "https://github.com/jmespath/jmespath.php/tree/2.7.0" + "source": "https://github.com/jmespath/jmespath.php/tree/2.8.0" }, - "time": "2023-08-25T10:54:48+00:00" + "time": "2024-09-04T18:46:31+00:00" }, { "name": "nesbot/carbon", - "version": "3.6.0", + "version": "3.8.0", "source": { "type": "git", "url": "https://github.com/briannesbitt/Carbon.git", - "reference": "39c8ef752db6865717cc3fba63970c16f057982c" + "reference": "bbd3eef89af8ba66a3aa7952b5439168fbcc529f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/39c8ef752db6865717cc3fba63970c16f057982c", - "reference": "39c8ef752db6865717cc3fba63970c16f057982c", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/bbd3eef89af8ba66a3aa7952b5439168fbcc529f", + "reference": "bbd3eef89af8ba66a3aa7952b5439168fbcc529f", "shasum": "" }, "require": { @@ -4875,7 +4951,7 @@ "type": "tidelift" } ], - "time": "2024-06-20T15:52:59+00:00" + "time": "2024-08-19T06:22:39+00:00" }, { "name": "nette/schema", @@ -4941,20 +5017,20 @@ }, { "name": "nette/utils", - "version": "v4.0.4", + "version": "v4.0.5", "source": { "type": "git", "url": "https://github.com/nette/utils.git", - "reference": "d3ad0aa3b9f934602cb3e3902ebccf10be34d218" + "reference": "736c567e257dbe0fcf6ce81b4d6dbe05c6899f96" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/d3ad0aa3b9f934602cb3e3902ebccf10be34d218", - "reference": "d3ad0aa3b9f934602cb3e3902ebccf10be34d218", + "url": "https://api.github.com/repos/nette/utils/zipball/736c567e257dbe0fcf6ce81b4d6dbe05c6899f96", + "reference": "736c567e257dbe0fcf6ce81b4d6dbe05c6899f96", "shasum": "" }, "require": { - "php": ">=8.0 <8.4" + "php": "8.0 - 8.4" }, "conflict": { "nette/finder": "<3", @@ -5021,9 +5097,9 @@ ], "support": { "issues": "https://github.com/nette/utils/issues", - "source": "https://github.com/nette/utils/tree/v4.0.4" + "source": "https://github.com/nette/utils/tree/v4.0.5" }, - "time": "2024-01-17T16:50:36+00:00" + "time": "2024-08-07T15:39:19+00:00" }, { "name": "nikic/php-parser", @@ -5138,16 +5214,16 @@ }, { "name": "nunomaduro/termwind", - "version": "v2.0.1", + "version": "v2.1.0", "source": { "type": "git", "url": "https://github.com/nunomaduro/termwind.git", - "reference": "58c4c58cf23df7f498daeb97092e34f5259feb6a" + "reference": "e5f21eade88689536c0cdad4c3cd75f3ed26e01a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/58c4c58cf23df7f498daeb97092e34f5259feb6a", - "reference": "58c4c58cf23df7f498daeb97092e34f5259feb6a", + "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/e5f21eade88689536c0cdad4c3cd75f3ed26e01a", + "reference": "e5f21eade88689536c0cdad4c3cd75f3ed26e01a", "shasum": "" }, "require": { @@ -5157,11 +5233,11 @@ }, "require-dev": { "ergebnis/phpstan-rules": "^2.2.0", - "illuminate/console": "^11.0.0", - "laravel/pint": "^1.14.0", - "mockery/mockery": "^1.6.7", - "pestphp/pest": "^2.34.1", - "phpstan/phpstan": "^1.10.59", + "illuminate/console": "^11.1.1", + "laravel/pint": "^1.15.0", + "mockery/mockery": "^1.6.11", + "pestphp/pest": "^2.34.6", + "phpstan/phpstan": "^1.10.66", "phpstan/phpstan-strict-rules": "^1.5.2", "symfony/var-dumper": "^7.0.4", "thecodingmachine/phpstan-strict-rules": "^1.0.0" @@ -5206,7 +5282,7 @@ ], "support": { "issues": "https://github.com/nunomaduro/termwind/issues", - "source": "https://github.com/nunomaduro/termwind/tree/v2.0.1" + "source": "https://github.com/nunomaduro/termwind/tree/v2.1.0" }, "funding": [ { @@ -5222,20 +5298,20 @@ "type": "github" } ], - "time": "2024-03-06T16:17:14+00:00" + "time": "2024-09-05T15:25:50+00:00" }, { "name": "nyholm/psr7", - "version": "1.8.1", + "version": "1.8.2", "source": { "type": "git", "url": "https://github.com/Nyholm/psr7.git", - "reference": "aa5fc277a4f5508013d571341ade0c3886d4d00e" + "reference": "a71f2b11690f4b24d099d6b16690a90ae14fc6f3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Nyholm/psr7/zipball/aa5fc277a4f5508013d571341ade0c3886d4d00e", - "reference": "aa5fc277a4f5508013d571341ade0c3886d4d00e", + "url": "https://api.github.com/repos/Nyholm/psr7/zipball/a71f2b11690f4b24d099d6b16690a90ae14fc6f3", + "reference": "a71f2b11690f4b24d099d6b16690a90ae14fc6f3", "shasum": "" }, "require": { @@ -5288,7 +5364,7 @@ ], "support": { "issues": "https://github.com/Nyholm/psr7/issues", - "source": "https://github.com/Nyholm/psr7/tree/1.8.1" + "source": "https://github.com/Nyholm/psr7/tree/1.8.2" }, "funding": [ { @@ -5300,28 +5376,28 @@ "type": "github" } ], - "time": "2023-11-13T09:31:12+00:00" + "time": "2024-09-09T07:06:30+00:00" }, { "name": "paragonie/constant_time_encoding", - "version": "v2.7.0", + "version": "v3.0.0", "source": { "type": "git", "url": "https://github.com/paragonie/constant_time_encoding.git", - "reference": "52a0d99e69f56b9ec27ace92ba56897fe6993105" + "reference": "df1e7fde177501eee2037dd159cf04f5f301a512" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/52a0d99e69f56b9ec27ace92ba56897fe6993105", - "reference": "52a0d99e69f56b9ec27ace92ba56897fe6993105", + "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/df1e7fde177501eee2037dd159cf04f5f301a512", + "reference": "df1e7fde177501eee2037dd159cf04f5f301a512", "shasum": "" }, "require": { - "php": "^7|^8" + "php": "^8" }, "require-dev": { - "phpunit/phpunit": "^6|^7|^8|^9", - "vimeo/psalm": "^1|^2|^3|^4" + "phpunit/phpunit": "^9", + "vimeo/psalm": "^4|^5" }, "type": "library", "autoload": { @@ -5367,7 +5443,7 @@ "issues": "https://github.com/paragonie/constant_time_encoding/issues", "source": "https://github.com/paragonie/constant_time_encoding" }, - "time": "2024-05-08T12:18:48+00:00" + "time": "2024-05-08T12:36:18+00:00" }, { "name": "paragonie/random_compat", @@ -5562,16 +5638,16 @@ }, { "name": "php-di/php-di", - "version": "7.0.6", + "version": "7.0.7", "source": { "type": "git", "url": "https://github.com/PHP-DI/PHP-DI.git", - "reference": "8097948a89f6ec782839b3e958432f427cac37fd" + "reference": "e87435e3c0e8f22977adc5af0d5cdcc467e15cf1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/8097948a89f6ec782839b3e958432f427cac37fd", - "reference": "8097948a89f6ec782839b3e958432f427cac37fd", + "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/e87435e3c0e8f22977adc5af0d5cdcc467e15cf1", + "reference": "e87435e3c0e8f22977adc5af0d5cdcc467e15cf1", "shasum": "" }, "require": { @@ -5619,7 +5695,7 @@ ], "support": { "issues": "https://github.com/PHP-DI/PHP-DI/issues", - "source": "https://github.com/PHP-DI/PHP-DI/tree/7.0.6" + "source": "https://github.com/PHP-DI/PHP-DI/tree/7.0.7" }, "funding": [ { @@ -5631,7 +5707,7 @@ "type": "tidelift" } ], - "time": "2023-11-02T10:04:50+00:00" + "time": "2024-07-21T15:55:45+00:00" }, { "name": "phpdocumentor/reflection-common", @@ -5746,16 +5822,16 @@ }, { "name": "phpoption/phpoption", - "version": "1.9.2", + "version": "1.9.3", "source": { "type": "git", "url": "https://github.com/schmittjoh/php-option.git", - "reference": "80735db690fe4fc5c76dfa7f9b770634285fa820" + "reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/80735db690fe4fc5c76dfa7f9b770634285fa820", - "reference": "80735db690fe4fc5c76dfa7f9b770634285fa820", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/e3fac8b24f56113f7cb96af14958c0dd16330f54", + "reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54", "shasum": "" }, "require": { @@ -5763,13 +5839,13 @@ }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2" + "phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28" }, "type": "library", "extra": { "bamarni-bin": { "bin-links": true, - "forward-command": true + "forward-command": false }, "branch-alias": { "dev-master": "1.9-dev" @@ -5805,7 +5881,7 @@ ], "support": { "issues": "https://github.com/schmittjoh/php-option/issues", - "source": "https://github.com/schmittjoh/php-option/tree/1.9.2" + "source": "https://github.com/schmittjoh/php-option/tree/1.9.3" }, "funding": [ { @@ -5817,20 +5893,20 @@ "type": "tidelift" } ], - "time": "2023-11-12T21:59:55+00:00" + "time": "2024-07-20T21:41:07+00:00" }, { "name": "phpseclib/phpseclib", - "version": "3.0.39", + "version": "3.0.41", "source": { "type": "git", "url": "https://github.com/phpseclib/phpseclib.git", - "reference": "211ebc399c6e73c225a018435fe5ae209d1d1485" + "reference": "621c73f7dcb310b61de34d1da4c4204e8ace6ceb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/211ebc399c6e73c225a018435fe5ae209d1d1485", - "reference": "211ebc399c6e73c225a018435fe5ae209d1d1485", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/621c73f7dcb310b61de34d1da4c4204e8ace6ceb", + "reference": "621c73f7dcb310b61de34d1da4c4204e8ace6ceb", "shasum": "" }, "require": { @@ -5911,7 +5987,7 @@ ], "support": { "issues": "https://github.com/phpseclib/phpseclib/issues", - "source": "https://github.com/phpseclib/phpseclib/tree/3.0.39" + "source": "https://github.com/phpseclib/phpseclib/tree/3.0.41" }, "funding": [ { @@ -5927,20 +6003,20 @@ "type": "tidelift" } ], - "time": "2024-06-24T06:27:33+00:00" + "time": "2024-08-12T00:13:54+00:00" }, { "name": "phpstan/phpdoc-parser", - "version": "1.29.1", + "version": "1.30.1", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "fcaefacf2d5c417e928405b71b400d4ce10daaf4" + "reference": "51b95ec8670af41009e2b2b56873bad96682413e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/fcaefacf2d5c417e928405b71b400d4ce10daaf4", - "reference": "fcaefacf2d5c417e928405b71b400d4ce10daaf4", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/51b95ec8670af41009e2b2b56873bad96682413e", + "reference": "51b95ec8670af41009e2b2b56873bad96682413e", "shasum": "" }, "require": { @@ -5972,22 +6048,22 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.29.1" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.30.1" }, - "time": "2024-05-31T08:52:43+00:00" + "time": "2024-09-07T20:13:05+00:00" }, { "name": "phpstan/phpstan", - "version": "1.11.7", + "version": "1.12.3", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "52d2bbfdcae7f895915629e4694e9497d0f8e28d" + "reference": "0fcbf194ab63d8159bb70d9aa3e1350051632009" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/52d2bbfdcae7f895915629e4694e9497d0f8e28d", - "reference": "52d2bbfdcae7f895915629e4694e9497d0f8e28d", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/0fcbf194ab63d8159bb70d9aa3e1350051632009", + "reference": "0fcbf194ab63d8159bb70d9aa3e1350051632009", "shasum": "" }, "require": { @@ -6032,7 +6108,7 @@ "type": "github" } ], - "time": "2024-07-06T11:17:41+00:00" + "time": "2024-09-09T08:10:35+00:00" }, { "name": "pion/laravel-chunk-upload", @@ -6146,24 +6222,24 @@ }, { "name": "pragmarx/google2fa", - "version": "v8.0.1", + "version": "v8.0.3", "source": { "type": "git", "url": "https://github.com/antonioribeiro/google2fa.git", - "reference": "80c3d801b31fe165f8fe99ea085e0a37834e1be3" + "reference": "6f8d87ebd5afbf7790bde1ffc7579c7c705e0fad" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/antonioribeiro/google2fa/zipball/80c3d801b31fe165f8fe99ea085e0a37834e1be3", - "reference": "80c3d801b31fe165f8fe99ea085e0a37834e1be3", + "url": "https://api.github.com/repos/antonioribeiro/google2fa/zipball/6f8d87ebd5afbf7790bde1ffc7579c7c705e0fad", + "reference": "6f8d87ebd5afbf7790bde1ffc7579c7c705e0fad", "shasum": "" }, "require": { - "paragonie/constant_time_encoding": "^1.0|^2.0", + "paragonie/constant_time_encoding": "^1.0|^2.0|^3.0", "php": "^7.1|^8.0" }, "require-dev": { - "phpstan/phpstan": "^0.12.18", + "phpstan/phpstan": "^1.9", "phpunit/phpunit": "^7.5.15|^8.5|^9.0" }, "type": "library", @@ -6192,9 +6268,9 @@ ], "support": { "issues": "https://github.com/antonioribeiro/google2fa/issues", - "source": "https://github.com/antonioribeiro/google2fa/tree/v8.0.1" + "source": "https://github.com/antonioribeiro/google2fa/tree/v8.0.3" }, - "time": "2022-06-13T21:57:56+00:00" + "time": "2024-09-05T11:56:40+00:00" }, { "name": "psr/cache", @@ -6558,16 +6634,16 @@ }, { "name": "psr/log", - "version": "3.0.0", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", "shasum": "" }, "require": { @@ -6602,9 +6678,9 @@ "psr-3" ], "support": { - "source": "https://github.com/php-fig/log/tree/3.0.0" + "source": "https://github.com/php-fig/log/tree/3.0.2" }, - "time": "2021-07-14T16:46:02+00:00" + "time": "2024-09-11T13:17:53+00:00" }, { "name": "psr/simple-cache", @@ -7072,21 +7148,21 @@ }, { "name": "rector/rector", - "version": "1.2.0", + "version": "1.2.5", "source": { "type": "git", "url": "https://github.com/rectorphp/rector.git", - "reference": "2fa387553db22b6f9bcccf5ff16f2c2c18a52a65" + "reference": "e98aa793ca3fcd17e893cfaf9103ac049775d339" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/rectorphp/rector/zipball/2fa387553db22b6f9bcccf5ff16f2c2c18a52a65", - "reference": "2fa387553db22b6f9bcccf5ff16f2c2c18a52a65", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/e98aa793ca3fcd17e893cfaf9103ac049775d339", + "reference": "e98aa793ca3fcd17e893cfaf9103ac049775d339", "shasum": "" }, "require": { "php": "^7.2|^8.0", - "phpstan/phpstan": "^1.11" + "phpstan/phpstan": "^1.12.2" }, "conflict": { "rector/rector-doctrine": "*", @@ -7119,7 +7195,7 @@ ], "support": { "issues": "https://github.com/rectorphp/rector/issues", - "source": "https://github.com/rectorphp/rector/tree/1.2.0" + "source": "https://github.com/rectorphp/rector/tree/1.2.5" }, "funding": [ { @@ -7127,7 +7203,7 @@ "type": "github" } ], - "time": "2024-07-01T14:24:45+00:00" + "time": "2024-09-08T17:43:24+00:00" }, { "name": "resend/resend-laravel", @@ -7329,16 +7405,16 @@ }, { "name": "sentry/sentry", - "version": "4.8.0", + "version": "4.9.0", "source": { "type": "git", "url": "https://github.com/getsentry/sentry-php.git", - "reference": "3cf5778ff425a23f2d22ed41b423691d36f47163" + "reference": "788ec170f51ebb22f2809a1e3f78b19ccd39b70d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/getsentry/sentry-php/zipball/3cf5778ff425a23f2d22ed41b423691d36f47163", - "reference": "3cf5778ff425a23f2d22ed41b423691d36f47163", + "url": "https://api.github.com/repos/getsentry/sentry-php/zipball/788ec170f51ebb22f2809a1e3f78b19ccd39b70d", + "reference": "788ec170f51ebb22f2809a1e3f78b19ccd39b70d", "shasum": "" }, "require": { @@ -7402,7 +7478,7 @@ ], "support": { "issues": "https://github.com/getsentry/sentry-php/issues", - "source": "https://github.com/getsentry/sentry-php/tree/4.8.0" + "source": "https://github.com/getsentry/sentry-php/tree/4.9.0" }, "funding": [ { @@ -7414,27 +7490,27 @@ "type": "custom" } ], - "time": "2024-06-05T13:18:43+00:00" + "time": "2024-08-08T14:40:50+00:00" }, { "name": "sentry/sentry-laravel", - "version": "4.6.1", + "version": "4.8.0", "source": { "type": "git", "url": "https://github.com/getsentry/sentry-laravel.git", - "reference": "7f5fd9f362e440c4c0c492f386b93095321f9101" + "reference": "2bbcb7e81097993cf64d5b296eaa6d396cddd5a7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/getsentry/sentry-laravel/zipball/7f5fd9f362e440c4c0c492f386b93095321f9101", - "reference": "7f5fd9f362e440c4c0c492f386b93095321f9101", + "url": "https://api.github.com/repos/getsentry/sentry-laravel/zipball/2bbcb7e81097993cf64d5b296eaa6d396cddd5a7", + "reference": "2bbcb7e81097993cf64d5b296eaa6d396cddd5a7", "shasum": "" }, "require": { "illuminate/support": "^6.0 | ^7.0 | ^8.0 | ^9.0 | ^10.0 | ^11.0", "nyholm/psr7": "^1.0", "php": "^7.2 | ^8.0", - "sentry/sentry": "^4.7", + "sentry/sentry": "^4.9", "symfony/psr-http-message-bridge": "^1.0 | ^2.0 | ^6.0 | ^7.0" }, "require-dev": { @@ -7491,7 +7567,7 @@ ], "support": { "issues": "https://github.com/getsentry/sentry-laravel/issues", - "source": "https://github.com/getsentry/sentry-laravel/tree/4.6.1" + "source": "https://github.com/getsentry/sentry-laravel/tree/4.8.0" }, "funding": [ { @@ -7503,7 +7579,7 @@ "type": "custom" } ], - "time": "2024-06-18T15:06:09+00:00" + "time": "2024-08-15T19:03:01+00:00" }, { "name": "socialiteproviders/manager", @@ -7632,16 +7708,16 @@ }, { "name": "spatie/backtrace", - "version": "1.6.1", + "version": "1.6.2", "source": { "type": "git", "url": "https://github.com/spatie/backtrace.git", - "reference": "8373b9d51638292e3bfd736a9c19a654111b4a23" + "reference": "1a9a145b044677ae3424693f7b06479fc8c137a9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/backtrace/zipball/8373b9d51638292e3bfd736a9c19a654111b4a23", - "reference": "8373b9d51638292e3bfd736a9c19a654111b4a23", + "url": "https://api.github.com/repos/spatie/backtrace/zipball/1a9a145b044677ae3424693f7b06479fc8c137a9", + "reference": "1a9a145b044677ae3424693f7b06479fc8c137a9", "shasum": "" }, "require": { @@ -7679,7 +7755,7 @@ "spatie" ], "support": { - "source": "https://github.com/spatie/backtrace/tree/1.6.1" + "source": "https://github.com/spatie/backtrace/tree/1.6.2" }, "funding": [ { @@ -7691,7 +7767,7 @@ "type": "other" } ], - "time": "2024-04-24T13:22:11+00:00" + "time": "2024-07-22T08:21:24+00:00" }, { "name": "spatie/laravel-activitylog", @@ -7871,16 +7947,16 @@ }, { "name": "spatie/laravel-package-tools", - "version": "1.16.4", + "version": "1.16.5", "source": { "type": "git", "url": "https://github.com/spatie/laravel-package-tools.git", - "reference": "ddf678e78d7f8b17e5cdd99c0c3413a4a6592e53" + "reference": "c7413972cf22ffdff97b68499c22baa04eddb6a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/ddf678e78d7f8b17e5cdd99c0c3413a4a6592e53", - "reference": "ddf678e78d7f8b17e5cdd99c0c3413a4a6592e53", + "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/c7413972cf22ffdff97b68499c22baa04eddb6a2", + "reference": "c7413972cf22ffdff97b68499c22baa04eddb6a2", "shasum": "" }, "require": { @@ -7919,7 +7995,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-package-tools/issues", - "source": "https://github.com/spatie/laravel-package-tools/tree/1.16.4" + "source": "https://github.com/spatie/laravel-package-tools/tree/1.16.5" }, "funding": [ { @@ -7927,20 +8003,20 @@ "type": "github" } ], - "time": "2024-03-20T07:29:11+00:00" + "time": "2024-08-27T18:56:10+00:00" }, { "name": "spatie/laravel-ray", - "version": "1.37.0", + "version": "1.37.1", "source": { "type": "git", "url": "https://github.com/spatie/laravel-ray.git", - "reference": "f57b294a3815be37effa9d13f54f2fbe5a2fff37" + "reference": "c2bedfd1172648df2c80aaceb2541d70f1d9a5b9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-ray/zipball/f57b294a3815be37effa9d13f54f2fbe5a2fff37", - "reference": "f57b294a3815be37effa9d13f54f2fbe5a2fff37", + "url": "https://api.github.com/repos/spatie/laravel-ray/zipball/c2bedfd1172648df2c80aaceb2541d70f1d9a5b9", + "reference": "c2bedfd1172648df2c80aaceb2541d70f1d9a5b9", "shasum": "" }, "require": { @@ -8002,7 +8078,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-ray/issues", - "source": "https://github.com/spatie/laravel-ray/tree/1.37.0" + "source": "https://github.com/spatie/laravel-ray/tree/1.37.1" }, "funding": [ { @@ -8014,7 +8090,7 @@ "type": "other" } ], - "time": "2024-07-03T08:48:44+00:00" + "time": "2024-07-12T12:35:17+00:00" }, { "name": "spatie/laravel-schemaless-attributes", @@ -8144,16 +8220,16 @@ }, { "name": "spatie/php-structure-discoverer", - "version": "2.1.1", + "version": "2.2.0", "source": { "type": "git", "url": "https://github.com/spatie/php-structure-discoverer.git", - "reference": "24f5221641560ec0f7dce23dd814e7d555b0098b" + "reference": "271542206169d95dd2ffe346ddf11f37672553a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/php-structure-discoverer/zipball/24f5221641560ec0f7dce23dd814e7d555b0098b", - "reference": "24f5221641560ec0f7dce23dd814e7d555b0098b", + "url": "https://api.github.com/repos/spatie/php-structure-discoverer/zipball/271542206169d95dd2ffe346ddf11f37672553a2", + "reference": "271542206169d95dd2ffe346ddf11f37672553a2", "shasum": "" }, "require": { @@ -8212,7 +8288,7 @@ ], "support": { "issues": "https://github.com/spatie/php-structure-discoverer/issues", - "source": "https://github.com/spatie/php-structure-discoverer/tree/2.1.1" + "source": "https://github.com/spatie/php-structure-discoverer/tree/2.2.0" }, "funding": [ { @@ -8220,7 +8296,7 @@ "type": "github" } ], - "time": "2024-03-13T16:08:30+00:00" + "time": "2024-08-29T10:43:45+00:00" }, { "name": "spatie/ray", @@ -8504,16 +8580,16 @@ }, { "name": "symfony/console", - "version": "v7.1.2", + "version": "v7.1.4", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "0aa29ca177f432ab68533432db0de059f39c92ae" + "reference": "1eed7af6961d763e7832e874d7f9b21c3ea9c111" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/0aa29ca177f432ab68533432db0de059f39c92ae", - "reference": "0aa29ca177f432ab68533432db0de059f39c92ae", + "url": "https://api.github.com/repos/symfony/console/zipball/1eed7af6961d763e7832e874d7f9b21c3ea9c111", + "reference": "1eed7af6961d763e7832e874d7f9b21c3ea9c111", "shasum": "" }, "require": { @@ -8577,7 +8653,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.1.2" + "source": "https://github.com/symfony/console/tree/v7.1.4" }, "funding": [ { @@ -8593,7 +8669,7 @@ "type": "tidelift" } ], - "time": "2024-06-28T10:03:55+00:00" + "time": "2024-08-15T22:48:53+00:00" }, { "name": "symfony/css-selector", @@ -8729,16 +8805,16 @@ }, { "name": "symfony/error-handler", - "version": "v7.1.2", + "version": "v7.1.3", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "2412d3dddb5c9ea51a39cfbff1c565fc9844ca32" + "reference": "432bb369952795c61ca1def65e078c4a80dad13c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/2412d3dddb5c9ea51a39cfbff1c565fc9844ca32", - "reference": "2412d3dddb5c9ea51a39cfbff1c565fc9844ca32", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/432bb369952795c61ca1def65e078c4a80dad13c", + "reference": "432bb369952795c61ca1def65e078c4a80dad13c", "shasum": "" }, "require": { @@ -8784,7 +8860,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v7.1.2" + "source": "https://github.com/symfony/error-handler/tree/v7.1.3" }, "funding": [ { @@ -8800,7 +8876,7 @@ "type": "tidelift" } ], - "time": "2024-06-25T19:55:06+00:00" + "time": "2024-07-26T13:02:51+00:00" }, { "name": "symfony/event-dispatcher", @@ -8960,16 +9036,16 @@ }, { "name": "symfony/finder", - "version": "v7.1.1", + "version": "v7.1.4", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "fbb0ba67688b780efbc886c1a0a0948dcf7205d6" + "reference": "d95bbf319f7d052082fb7af147e0f835a695e823" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/fbb0ba67688b780efbc886c1a0a0948dcf7205d6", - "reference": "fbb0ba67688b780efbc886c1a0a0948dcf7205d6", + "url": "https://api.github.com/repos/symfony/finder/zipball/d95bbf319f7d052082fb7af147e0f835a695e823", + "reference": "d95bbf319f7d052082fb7af147e0f835a695e823", "shasum": "" }, "require": { @@ -9004,7 +9080,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.1.1" + "source": "https://github.com/symfony/finder/tree/v7.1.4" }, "funding": [ { @@ -9020,20 +9096,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:57:53+00:00" + "time": "2024-08-13T14:28:19+00:00" }, { "name": "symfony/http-foundation", - "version": "v7.1.1", + "version": "v7.1.3", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "74d171d5b6a1d9e4bfee09a41937c17a7536acfa" + "reference": "f602d5c17d1fa02f8019ace2687d9d136b7f4a1a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/74d171d5b6a1d9e4bfee09a41937c17a7536acfa", - "reference": "74d171d5b6a1d9e4bfee09a41937c17a7536acfa", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/f602d5c17d1fa02f8019ace2687d9d136b7f4a1a", + "reference": "f602d5c17d1fa02f8019ace2687d9d136b7f4a1a", "shasum": "" }, "require": { @@ -9081,7 +9157,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v7.1.1" + "source": "https://github.com/symfony/http-foundation/tree/v7.1.3" }, "funding": [ { @@ -9097,20 +9173,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:57:53+00:00" + "time": "2024-07-26T12:41:01+00:00" }, { "name": "symfony/http-kernel", - "version": "v7.1.2", + "version": "v7.1.4", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "ae3fa717db4d41a55d14c2bd92399e37cf5bc0f6" + "reference": "6efcbd1b3f444f631c386504fc83eeca25963747" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/ae3fa717db4d41a55d14c2bd92399e37cf5bc0f6", - "reference": "ae3fa717db4d41a55d14c2bd92399e37cf5bc0f6", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/6efcbd1b3f444f631c386504fc83eeca25963747", + "reference": "6efcbd1b3f444f631c386504fc83eeca25963747", "shasum": "" }, "require": { @@ -9195,7 +9271,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v7.1.2" + "source": "https://github.com/symfony/http-kernel/tree/v7.1.4" }, "funding": [ { @@ -9211,7 +9287,7 @@ "type": "tidelift" } ], - "time": "2024-06-28T13:13:31+00:00" + "time": "2024-08-30T17:02:28+00:00" }, { "name": "symfony/mailer", @@ -9295,16 +9371,16 @@ }, { "name": "symfony/mime", - "version": "v7.1.2", + "version": "v7.1.4", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "26a00b85477e69a4bab63b66c5dce64f18b0cbfc" + "reference": "ccaa6c2503db867f472a587291e764d6a1e58758" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/26a00b85477e69a4bab63b66c5dce64f18b0cbfc", - "reference": "26a00b85477e69a4bab63b66c5dce64f18b0cbfc", + "url": "https://api.github.com/repos/symfony/mime/zipball/ccaa6c2503db867f472a587291e764d6a1e58758", + "reference": "ccaa6c2503db867f472a587291e764d6a1e58758", "shasum": "" }, "require": { @@ -9359,7 +9435,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v7.1.2" + "source": "https://github.com/symfony/mime/tree/v7.1.4" }, "funding": [ { @@ -9375,7 +9451,7 @@ "type": "tidelift" } ], - "time": "2024-06-28T10:03:55+00:00" + "time": "2024-08-13T14:28:19+00:00" }, { "name": "symfony/options-resolver", @@ -9446,20 +9522,20 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "0424dff1c58f028c451efff2045f5d92410bd540" + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/0424dff1c58f028c451efff2045f5d92410bd540", - "reference": "0424dff1c58f028c451efff2045f5d92410bd540", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "provide": { "ext-ctype": "*" @@ -9505,7 +9581,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0" }, "funding": [ { @@ -9521,24 +9597,24 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-iconv", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-iconv.git", - "reference": "c027e6a3c6aee334663ec21f5852e89738abc805" + "reference": "48becf00c920479ca2e910c22a5a39e5d47ca956" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/c027e6a3c6aee334663ec21f5852e89738abc805", - "reference": "c027e6a3c6aee334663ec21f5852e89738abc805", + "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/48becf00c920479ca2e910c22a5a39e5d47ca956", + "reference": "48becf00c920479ca2e910c22a5a39e5d47ca956", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "provide": { "ext-iconv": "*" @@ -9585,7 +9661,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-iconv/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-iconv/tree/v1.31.0" }, "funding": [ { @@ -9601,24 +9677,24 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a" + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/64647a7c30b2283f5d49b874d84a18fc22054b7a", - "reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "suggest": { "ext-intl": "For best performance" @@ -9663,7 +9739,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.31.0" }, "funding": [ { @@ -9679,26 +9755,25 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "a6e83bdeb3c84391d1dfe16f42e40727ce524a5c" + "reference": "c36586dcf89a12315939e00ec9b4474adcb1d773" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/a6e83bdeb3c84391d1dfe16f42e40727ce524a5c", - "reference": "a6e83bdeb3c84391d1dfe16f42e40727ce524a5c", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/c36586dcf89a12315939e00ec9b4474adcb1d773", + "reference": "c36586dcf89a12315939e00ec9b4474adcb1d773", "shasum": "" }, "require": { - "php": ">=7.1", - "symfony/polyfill-intl-normalizer": "^1.10", - "symfony/polyfill-php72": "^1.10" + "php": ">=7.2", + "symfony/polyfill-intl-normalizer": "^1.10" }, "suggest": { "ext-intl": "For best performance" @@ -9747,7 +9822,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.31.0" }, "funding": [ { @@ -9763,24 +9838,24 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb" + "reference": "3833d7255cc303546435cb650316bff708a1c75c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/a95281b0be0d9ab48050ebd988b967875cdb9fdb", - "reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "suggest": { "ext-intl": "For best performance" @@ -9828,7 +9903,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.31.0" }, "funding": [ { @@ -9844,24 +9919,24 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c" + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fd22ab50000ef01661e2a31d850ebaa297f8e03c", - "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341", + "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "provide": { "ext-mbstring": "*" @@ -9908,7 +9983,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0" }, "funding": [ { @@ -9924,97 +9999,24 @@ "type": "tidelift" } ], - "time": "2024-06-19T12:30:46+00:00" - }, - { - "name": "symfony/polyfill-php72", - "version": "v1.30.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "10112722600777e02d2745716b70c5db4ca70442" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/10112722600777e02d2745716b70c5db4ca70442", - "reference": "10112722600777e02d2745716b70c5db4ca70442", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php72\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php72/tree/v1.30.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-06-19T12:30:46+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "77fa7995ac1b21ab60769b7323d600a991a90433" + "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/77fa7995ac1b21ab60769b7323d600a991a90433", - "reference": "77fa7995ac1b21ab60769b7323d600a991a90433", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", + "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "type": "library", "extra": { @@ -10061,7 +10063,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.31.0" }, "funding": [ { @@ -10077,24 +10079,24 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-php83", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php83.git", - "reference": "dbdcdf1a4dcc2743591f1079d0c35ab1e2dcbbc9" + "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/dbdcdf1a4dcc2743591f1079d0c35ab1e2dcbbc9", - "reference": "dbdcdf1a4dcc2743591f1079d0c35ab1e2dcbbc9", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/2fb86d65e2d424369ad2905e83b236a8805ba491", + "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "type": "library", "extra": { @@ -10137,7 +10139,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php83/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-php83/tree/v1.31.0" }, "funding": [ { @@ -10153,24 +10155,24 @@ "type": "tidelift" } ], - "time": "2024-06-19T12:35:24+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-uuid", - "version": "v1.30.0", + "version": "v1.31.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-uuid.git", - "reference": "2ba1f33797470debcda07fe9dce20a0003df18e9" + "reference": "21533be36c24be3f4b1669c4725c7d1d2bab4ae2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/2ba1f33797470debcda07fe9dce20a0003df18e9", - "reference": "2ba1f33797470debcda07fe9dce20a0003df18e9", + "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/21533be36c24be3f4b1669c4725c7d1d2bab4ae2", + "reference": "21533be36c24be3f4b1669c4725c7d1d2bab4ae2", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "provide": { "ext-uuid": "*" @@ -10216,7 +10218,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/polyfill-uuid/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-uuid/tree/v1.31.0" }, "funding": [ { @@ -10232,20 +10234,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/process", - "version": "v7.1.1", + "version": "v7.1.3", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "febf90124323a093c7ee06fdb30e765ca3c20028" + "reference": "7f2f542c668ad6c313dc4a5e9c3321f733197eca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/febf90124323a093c7ee06fdb30e765ca3c20028", - "reference": "febf90124323a093c7ee06fdb30e765ca3c20028", + "url": "https://api.github.com/repos/symfony/process/zipball/7f2f542c668ad6c313dc4a5e9c3321f733197eca", + "reference": "7f2f542c668ad6c313dc4a5e9c3321f733197eca", "shasum": "" }, "require": { @@ -10277,7 +10279,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.1.1" + "source": "https://github.com/symfony/process/tree/v7.1.3" }, "funding": [ { @@ -10293,20 +10295,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:57:53+00:00" + "time": "2024-07-26T12:44:47+00:00" }, { "name": "symfony/psr-http-message-bridge", - "version": "v7.1.1", + "version": "v7.1.4", "source": { "type": "git", "url": "https://github.com/symfony/psr-http-message-bridge.git", - "reference": "9a5dbb606da711f5d40a7596ad577856f9402140" + "reference": "405a7bcd872f1563966f64be19f1362d94ce71ab" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/9a5dbb606da711f5d40a7596ad577856f9402140", - "reference": "9a5dbb606da711f5d40a7596ad577856f9402140", + "url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/405a7bcd872f1563966f64be19f1362d94ce71ab", + "reference": "405a7bcd872f1563966f64be19f1362d94ce71ab", "shasum": "" }, "require": { @@ -10360,7 +10362,7 @@ "psr-7" ], "support": { - "source": "https://github.com/symfony/psr-http-message-bridge/tree/v7.1.1" + "source": "https://github.com/symfony/psr-http-message-bridge/tree/v7.1.4" }, "funding": [ { @@ -10376,20 +10378,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:57:53+00:00" + "time": "2024-08-15T22:48:53+00:00" }, { "name": "symfony/routing", - "version": "v7.1.1", + "version": "v7.1.4", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "60c31bab5c45af7f13091b87deb708830f3c96c0" + "reference": "1500aee0094a3ce1c92626ed8cf3c2037e86f5a7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/60c31bab5c45af7f13091b87deb708830f3c96c0", - "reference": "60c31bab5c45af7f13091b87deb708830f3c96c0", + "url": "https://api.github.com/repos/symfony/routing/zipball/1500aee0094a3ce1c92626ed8cf3c2037e86f5a7", + "reference": "1500aee0094a3ce1c92626ed8cf3c2037e86f5a7", "shasum": "" }, "require": { @@ -10441,7 +10443,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v7.1.1" + "source": "https://github.com/symfony/routing/tree/v7.1.4" }, "funding": [ { @@ -10457,7 +10459,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:57:53+00:00" + "time": "2024-08-29T08:16:25+00:00" }, { "name": "symfony/service-contracts", @@ -10606,16 +10608,16 @@ }, { "name": "symfony/string", - "version": "v7.1.2", + "version": "v7.1.4", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "14221089ac66cf82e3cf3d1c1da65de305587ff8" + "reference": "6cd670a6d968eaeb1c77c2e76091c45c56bc367b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/14221089ac66cf82e3cf3d1c1da65de305587ff8", - "reference": "14221089ac66cf82e3cf3d1c1da65de305587ff8", + "url": "https://api.github.com/repos/symfony/string/zipball/6cd670a6d968eaeb1c77c2e76091c45c56bc367b", + "reference": "6cd670a6d968eaeb1c77c2e76091c45c56bc367b", "shasum": "" }, "require": { @@ -10673,7 +10675,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.1.2" + "source": "https://github.com/symfony/string/tree/v7.1.4" }, "funding": [ { @@ -10689,20 +10691,20 @@ "type": "tidelift" } ], - "time": "2024-06-28T09:27:18+00:00" + "time": "2024-08-12T09:59:40+00:00" }, { "name": "symfony/translation", - "version": "v7.1.1", + "version": "v7.1.3", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "cf5ae136e124fc7681b34ce9fac9d5b9ae8ceee3" + "reference": "8d5e50c813ba2859a6dfc99a0765c550507934a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/cf5ae136e124fc7681b34ce9fac9d5b9ae8ceee3", - "reference": "cf5ae136e124fc7681b34ce9fac9d5b9ae8ceee3", + "url": "https://api.github.com/repos/symfony/translation/zipball/8d5e50c813ba2859a6dfc99a0765c550507934a1", + "reference": "8d5e50c813ba2859a6dfc99a0765c550507934a1", "shasum": "" }, "require": { @@ -10767,7 +10769,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v7.1.1" + "source": "https://github.com/symfony/translation/tree/v7.1.3" }, "funding": [ { @@ -10783,7 +10785,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:57:53+00:00" + "time": "2024-07-26T12:41:01+00:00" }, { "name": "symfony/translation-contracts", @@ -10865,16 +10867,16 @@ }, { "name": "symfony/uid", - "version": "v7.1.1", + "version": "v7.1.4", "source": { "type": "git", "url": "https://github.com/symfony/uid.git", - "reference": "bb59febeecc81528ff672fad5dab7f06db8c8277" + "reference": "82177535395109075cdb45a70533aa3d7a521cdf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/uid/zipball/bb59febeecc81528ff672fad5dab7f06db8c8277", - "reference": "bb59febeecc81528ff672fad5dab7f06db8c8277", + "url": "https://api.github.com/repos/symfony/uid/zipball/82177535395109075cdb45a70533aa3d7a521cdf", + "reference": "82177535395109075cdb45a70533aa3d7a521cdf", "shasum": "" }, "require": { @@ -10919,7 +10921,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/uid/tree/v7.1.1" + "source": "https://github.com/symfony/uid/tree/v7.1.4" }, "funding": [ { @@ -10935,20 +10937,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:57:53+00:00" + "time": "2024-08-12T09:59:40+00:00" }, { "name": "symfony/var-dumper", - "version": "v7.1.2", + "version": "v7.1.4", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "5857c57c6b4b86524c08cf4f4bc95327270a816d" + "reference": "a5fa7481b199090964d6fd5dab6294d5a870c7aa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/5857c57c6b4b86524c08cf4f4bc95327270a816d", - "reference": "5857c57c6b4b86524c08cf4f4bc95327270a816d", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/a5fa7481b199090964d6fd5dab6294d5a870c7aa", + "reference": "a5fa7481b199090964d6fd5dab6294d5a870c7aa", "shasum": "" }, "require": { @@ -11002,7 +11004,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v7.1.2" + "source": "https://github.com/symfony/var-dumper/tree/v7.1.4" }, "funding": [ { @@ -11018,20 +11020,20 @@ "type": "tidelift" } ], - "time": "2024-06-28T08:00:31+00:00" + "time": "2024-08-30T16:12:47+00:00" }, { "name": "symfony/yaml", - "version": "v6.4.8", + "version": "v6.4.11", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "52903de178d542850f6f341ba92995d3d63e60c9" + "reference": "be37e7f13195e05ab84ca5269365591edd240335" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/52903de178d542850f6f341ba92995d3d63e60c9", - "reference": "52903de178d542850f6f341ba92995d3d63e60c9", + "url": "https://api.github.com/repos/symfony/yaml/zipball/be37e7f13195e05ab84ca5269365591edd240335", + "reference": "be37e7f13195e05ab84ca5269365591edd240335", "shasum": "" }, "require": { @@ -11074,7 +11076,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v6.4.8" + "source": "https://github.com/symfony/yaml/tree/v6.4.11" }, "funding": [ { @@ -11090,7 +11092,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-08-12T09:55:28+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", @@ -11206,23 +11208,23 @@ }, { "name": "vlucas/phpdotenv", - "version": "v5.6.0", + "version": "v5.6.1", "source": { "type": "git", "url": "https://github.com/vlucas/phpdotenv.git", - "reference": "2cf9fb6054c2bb1d59d1f3817706ecdb9d2934c4" + "reference": "a59a13791077fe3d44f90e7133eb68e7d22eaff2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/2cf9fb6054c2bb1d59d1f3817706ecdb9d2934c4", - "reference": "2cf9fb6054c2bb1d59d1f3817706ecdb9d2934c4", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/a59a13791077fe3d44f90e7133eb68e7d22eaff2", + "reference": "a59a13791077fe3d44f90e7133eb68e7d22eaff2", "shasum": "" }, "require": { "ext-pcre": "*", - "graham-campbell/result-type": "^1.1.2", + "graham-campbell/result-type": "^1.1.3", "php": "^7.2.5 || ^8.0", - "phpoption/phpoption": "^1.9.2", + "phpoption/phpoption": "^1.9.3", "symfony/polyfill-ctype": "^1.24", "symfony/polyfill-mbstring": "^1.24", "symfony/polyfill-php80": "^1.24" @@ -11239,7 +11241,7 @@ "extra": { "bamarni-bin": { "bin-links": true, - "forward-command": true + "forward-command": false }, "branch-alias": { "dev-master": "5.6-dev" @@ -11274,7 +11276,7 @@ ], "support": { "issues": "https://github.com/vlucas/phpdotenv/issues", - "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.0" + "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.1" }, "funding": [ { @@ -11286,7 +11288,7 @@ "type": "tidelift" } ], - "time": "2023-11-12T22:43:29+00:00" + "time": "2024-07-20T21:52:34+00:00" }, { "name": "voku/portable-ascii", @@ -11532,16 +11534,16 @@ }, { "name": "zbateson/mail-mime-parser", - "version": "3.0.1", + "version": "3.0.3", "source": { "type": "git", "url": "https://github.com/zbateson/mail-mime-parser.git", - "reference": "6ade63b0a43047935791d7977e22717a68cc388b" + "reference": "e0d4423fe27850c9dd301190767dbc421acc2f19" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zbateson/mail-mime-parser/zipball/6ade63b0a43047935791d7977e22717a68cc388b", - "reference": "6ade63b0a43047935791d7977e22717a68cc388b", + "url": "https://api.github.com/repos/zbateson/mail-mime-parser/zipball/e0d4423fe27850c9dd301190767dbc421acc2f19", + "reference": "e0d4423fe27850c9dd301190767dbc421acc2f19", "shasum": "" }, "require": { @@ -11604,7 +11606,7 @@ "type": "github" } ], - "time": "2024-04-29T21:53:01+00:00" + "time": "2024-08-10T18:44:09+00:00" }, { "name": "zbateson/mb-wrapper", @@ -11740,16 +11742,16 @@ }, { "name": "zircote/swagger-php", - "version": "4.10.3", + "version": "4.10.6", "source": { "type": "git", "url": "https://github.com/zircote/swagger-php.git", - "reference": "ad3f913d39b2a4dfb6e59ee4babb35a6b4a2b998" + "reference": "e462ff5269ea0ec91070edd5d51dc7215bdea3b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zircote/swagger-php/zipball/ad3f913d39b2a4dfb6e59ee4babb35a6b4a2b998", - "reference": "ad3f913d39b2a4dfb6e59ee4babb35a6b4a2b998", + "url": "https://api.github.com/repos/zircote/swagger-php/zipball/e462ff5269ea0ec91070edd5d51dc7215bdea3b6", + "reference": "e462ff5269ea0ec91070edd5d51dc7215bdea3b6", "shasum": "" }, "require": { @@ -11815,9 +11817,9 @@ ], "support": { "issues": "https://github.com/zircote/swagger-php/issues", - "source": "https://github.com/zircote/swagger-php/tree/4.10.3" + "source": "https://github.com/zircote/swagger-php/tree/4.10.6" }, - "time": "2024-07-04T07:53:11+00:00" + "time": "2024-07-26T03:04:43+00:00" } ], "packages-dev": [ @@ -11980,16 +11982,16 @@ }, { "name": "fidry/cpu-core-counter", - "version": "1.1.0", + "version": "1.2.0", "source": { "type": "git", "url": "https://github.com/theofidry/cpu-core-counter.git", - "reference": "f92996c4d5c1a696a6a970e20f7c4216200fcc42" + "reference": "8520451a140d3f46ac33042715115e290cf5785f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/f92996c4d5c1a696a6a970e20f7c4216200fcc42", - "reference": "f92996c4d5c1a696a6a970e20f7c4216200fcc42", + "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/8520451a140d3f46ac33042715115e290cf5785f", + "reference": "8520451a140d3f46ac33042715115e290cf5785f", "shasum": "" }, "require": { @@ -12029,7 +12031,7 @@ ], "support": { "issues": "https://github.com/theofidry/cpu-core-counter/issues", - "source": "https://github.com/theofidry/cpu-core-counter/tree/1.1.0" + "source": "https://github.com/theofidry/cpu-core-counter/tree/1.2.0" }, "funding": [ { @@ -12037,7 +12039,7 @@ "type": "github" } ], - "time": "2024-02-07T09:43:46+00:00" + "time": "2024-08-06T10:04:20+00:00" }, { "name": "filp/whoops", @@ -12163,16 +12165,16 @@ }, { "name": "laravel/dusk", - "version": "v8.2.1", + "version": "v8.2.5", "source": { "type": "git", "url": "https://github.com/laravel/dusk.git", - "reference": "f2c0957aa4fbb4a78394e77b8caf969903f28050" + "reference": "e641800393ce4ad39f0a47133f51aae67ceb01ad" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/dusk/zipball/f2c0957aa4fbb4a78394e77b8caf969903f28050", - "reference": "f2c0957aa4fbb4a78394e77b8caf969903f28050", + "url": "https://api.github.com/repos/laravel/dusk/zipball/e641800393ce4ad39f0a47133f51aae67ceb01ad", + "reference": "e641800393ce4ad39f0a47133f51aae67ceb01ad", "shasum": "" }, "require": { @@ -12229,22 +12231,22 @@ ], "support": { "issues": "https://github.com/laravel/dusk/issues", - "source": "https://github.com/laravel/dusk/tree/v8.2.1" + "source": "https://github.com/laravel/dusk/tree/v8.2.5" }, - "time": "2024-07-08T06:42:12+00:00" + "time": "2024-08-26T12:34:33+00:00" }, { "name": "laravel/pint", - "version": "v1.16.2", + "version": "v1.17.3", "source": { "type": "git", "url": "https://github.com/laravel/pint.git", - "reference": "51f1ba679a6afe0315621ad143d788bd7ded0eca" + "reference": "9d77be916e145864f10788bb94531d03e1f7b482" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pint/zipball/51f1ba679a6afe0315621ad143d788bd7ded0eca", - "reference": "51f1ba679a6afe0315621ad143d788bd7ded0eca", + "url": "https://api.github.com/repos/laravel/pint/zipball/9d77be916e145864f10788bb94531d03e1f7b482", + "reference": "9d77be916e145864f10788bb94531d03e1f7b482", "shasum": "" }, "require": { @@ -12255,13 +12257,13 @@ "php": "^8.1.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.59.3", - "illuminate/view": "^10.48.12", - "larastan/larastan": "^2.9.7", + "friendsofphp/php-cs-fixer": "^3.64.0", + "illuminate/view": "^10.48.20", + "larastan/larastan": "^2.9.8", "laravel-zero/framework": "^10.4.0", "mockery/mockery": "^1.6.12", "nunomaduro/termwind": "^1.15.1", - "pestphp/pest": "^2.34.8" + "pestphp/pest": "^2.35.1" }, "bin": [ "builds/pint" @@ -12297,7 +12299,7 @@ "issues": "https://github.com/laravel/pint/issues", "source": "https://github.com/laravel/pint" }, - "time": "2024-07-09T15:58:08+00:00" + "time": "2024-09-03T15:00:28+00:00" }, { "name": "mockery/mockery", @@ -12444,38 +12446,38 @@ }, { "name": "nunomaduro/collision", - "version": "v8.1.1", + "version": "v8.4.0", "source": { "type": "git", "url": "https://github.com/nunomaduro/collision.git", - "reference": "13e5d538b95a744d85f447a321ce10adb28e9af9" + "reference": "e7d1aa8ed753f63fa816932bbc89678238843b4a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/collision/zipball/13e5d538b95a744d85f447a321ce10adb28e9af9", - "reference": "13e5d538b95a744d85f447a321ce10adb28e9af9", + "url": "https://api.github.com/repos/nunomaduro/collision/zipball/e7d1aa8ed753f63fa816932bbc89678238843b4a", + "reference": "e7d1aa8ed753f63fa816932bbc89678238843b4a", "shasum": "" }, "require": { "filp/whoops": "^2.15.4", "nunomaduro/termwind": "^2.0.1", "php": "^8.2.0", - "symfony/console": "^7.0.4" + "symfony/console": "^7.1.3" }, "conflict": { "laravel/framework": "<11.0.0 || >=12.0.0", "phpunit/phpunit": "<10.5.1 || >=12.0.0" }, "require-dev": { - "larastan/larastan": "^2.9.2", - "laravel/framework": "^11.0.0", - "laravel/pint": "^1.14.0", - "laravel/sail": "^1.28.2", - "laravel/sanctum": "^4.0.0", + "larastan/larastan": "^2.9.8", + "laravel/framework": "^11.19.0", + "laravel/pint": "^1.17.1", + "laravel/sail": "^1.31.0", + "laravel/sanctum": "^4.0.2", "laravel/tinker": "^2.9.0", - "orchestra/testbench-core": "^9.0.0", - "pestphp/pest": "^2.34.1 || ^3.0.0", - "sebastian/environment": "^6.0.1 || ^7.0.0" + "orchestra/testbench-core": "^9.2.3", + "pestphp/pest": "^2.35.0 || ^3.0.0", + "sebastian/environment": "^6.1.0 || ^7.0.0" }, "type": "library", "extra": { @@ -12537,25 +12539,25 @@ "type": "patreon" } ], - "time": "2024-03-06T16:20:09+00:00" + "time": "2024-08-03T15:32:23+00:00" }, { "name": "pestphp/pest", - "version": "v2.34.8", + "version": "v2.35.1", "source": { "type": "git", "url": "https://github.com/pestphp/pest.git", - "reference": "e8f122bf47585c06431e0056189ec6bfd6f41f57" + "reference": "b13acb630df52c06123588d321823c31fc685545" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pestphp/pest/zipball/e8f122bf47585c06431e0056189ec6bfd6f41f57", - "reference": "e8f122bf47585c06431e0056189ec6bfd6f41f57", + "url": "https://api.github.com/repos/pestphp/pest/zipball/b13acb630df52c06123588d321823c31fc685545", + "reference": "b13acb630df52c06123588d321823c31fc685545", "shasum": "" }, "require": { "brianium/paratest": "^7.3.1", - "nunomaduro/collision": "^7.10.0|^8.1.1", + "nunomaduro/collision": "^7.10.0|^8.4.0", "nunomaduro/termwind": "^1.15.1|^2.0.1", "pestphp/pest-plugin": "^2.1.1", "pestphp/pest-plugin-arch": "^2.7.0", @@ -12569,8 +12571,8 @@ }, "require-dev": { "pestphp/pest-dev-tools": "^2.16.0", - "pestphp/pest-plugin-type-coverage": "^2.8.3", - "symfony/process": "^6.4.0|^7.1.1" + "pestphp/pest-plugin-type-coverage": "^2.8.5", + "symfony/process": "^6.4.0|^7.1.3" }, "bin": [ "bin/pest" @@ -12633,7 +12635,7 @@ ], "support": { "issues": "https://github.com/pestphp/pest/issues", - "source": "https://github.com/pestphp/pest/tree/v2.34.8" + "source": "https://github.com/pestphp/pest/tree/v2.35.1" }, "funding": [ { @@ -12645,7 +12647,7 @@ "type": "github" } ], - "time": "2024-06-10T22:02:16+00:00" + "time": "2024-08-20T21:41:50+00:00" }, { "name": "pestphp/pest-plugin", @@ -13038,32 +13040,32 @@ }, { "name": "phpunit/php-code-coverage", - "version": "10.1.15", + "version": "10.1.16", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "5da8b1728acd1e6ffdf2ff32ffbdfd04307f26ae" + "reference": "7e308268858ed6baedc8704a304727d20bc07c77" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/5da8b1728acd1e6ffdf2ff32ffbdfd04307f26ae", - "reference": "5da8b1728acd1e6ffdf2ff32ffbdfd04307f26ae", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/7e308268858ed6baedc8704a304727d20bc07c77", + "reference": "7e308268858ed6baedc8704a304727d20bc07c77", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.18 || ^5.0", + "nikic/php-parser": "^4.19.1 || ^5.1.0", "php": ">=8.1", - "phpunit/php-file-iterator": "^4.0", - "phpunit/php-text-template": "^3.0", - "sebastian/code-unit-reverse-lookup": "^3.0", - "sebastian/complexity": "^3.0", - "sebastian/environment": "^6.0", - "sebastian/lines-of-code": "^2.0", - "sebastian/version": "^4.0", - "theseer/tokenizer": "^1.2.0" + "phpunit/php-file-iterator": "^4.1.0", + "phpunit/php-text-template": "^3.0.1", + "sebastian/code-unit-reverse-lookup": "^3.0.0", + "sebastian/complexity": "^3.2.0", + "sebastian/environment": "^6.1.0", + "sebastian/lines-of-code": "^2.0.2", + "sebastian/version": "^4.0.1", + "theseer/tokenizer": "^1.2.3" }, "require-dev": { "phpunit/phpunit": "^10.1" @@ -13075,7 +13077,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "10.1-dev" + "dev-main": "10.1.x-dev" } }, "autoload": { @@ -13104,7 +13106,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.15" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.16" }, "funding": [ { @@ -13112,7 +13114,7 @@ "type": "github" } ], - "time": "2024-06-29T08:25:15+00:00" + "time": "2024-08-22T04:31:57+00:00" }, { "name": "phpunit/php-file-iterator", @@ -13628,16 +13630,16 @@ }, { "name": "sebastian/comparator", - "version": "5.0.1", + "version": "5.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "2db5010a484d53ebf536087a70b4a5423c102372" + "reference": "2d3e04c3b4c1e84a5e7382221ad8883c8fbc4f53" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2db5010a484d53ebf536087a70b4a5423c102372", - "reference": "2db5010a484d53ebf536087a70b4a5423c102372", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2d3e04c3b4c1e84a5e7382221ad8883c8fbc4f53", + "reference": "2d3e04c3b4c1e84a5e7382221ad8883c8fbc4f53", "shasum": "" }, "require": { @@ -13648,7 +13650,7 @@ "sebastian/exporter": "^5.0" }, "require-dev": { - "phpunit/phpunit": "^10.3" + "phpunit/phpunit": "^10.4" }, "type": "library", "extra": { @@ -13693,7 +13695,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", "security": "https://github.com/sebastianbergmann/comparator/security/policy", - "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.1" + "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.2" }, "funding": [ { @@ -13701,7 +13703,7 @@ "type": "github" } ], - "time": "2023-08-14T13:18:12+00:00" + "time": "2024-08-12T06:03:08+00:00" }, { "name": "sebastian/complexity", @@ -14421,16 +14423,16 @@ }, { "name": "spatie/error-solutions", - "version": "1.0.5", + "version": "1.1.1", "source": { "type": "git", "url": "https://github.com/spatie/error-solutions.git", - "reference": "4bb6c734dc992b2db3e26df1ef021c75d2218b13" + "reference": "ae7393122eda72eed7cc4f176d1e96ea444f2d67" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/error-solutions/zipball/4bb6c734dc992b2db3e26df1ef021c75d2218b13", - "reference": "4bb6c734dc992b2db3e26df1ef021c75d2218b13", + "url": "https://api.github.com/repos/spatie/error-solutions/zipball/ae7393122eda72eed7cc4f176d1e96ea444f2d67", + "reference": "ae7393122eda72eed7cc4f176d1e96ea444f2d67", "shasum": "" }, "require": { @@ -14483,7 +14485,7 @@ ], "support": { "issues": "https://github.com/spatie/error-solutions/issues", - "source": "https://github.com/spatie/error-solutions/tree/1.0.5" + "source": "https://github.com/spatie/error-solutions/tree/1.1.1" }, "funding": [ { @@ -14491,20 +14493,20 @@ "type": "github" } ], - "time": "2024-07-09T12:13:32+00:00" + "time": "2024-07-25T11:06:04+00:00" }, { "name": "spatie/flare-client-php", - "version": "1.7.0", + "version": "1.8.0", "source": { "type": "git", "url": "https://github.com/spatie/flare-client-php.git", - "reference": "097040ff51e660e0f6fc863684ac4b02c93fa234" + "reference": "180f8ca4c0d0d6fc51477bd8c53ce37ab5a96122" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/flare-client-php/zipball/097040ff51e660e0f6fc863684ac4b02c93fa234", - "reference": "097040ff51e660e0f6fc863684ac4b02c93fa234", + "url": "https://api.github.com/repos/spatie/flare-client-php/zipball/180f8ca4c0d0d6fc51477bd8c53ce37ab5a96122", + "reference": "180f8ca4c0d0d6fc51477bd8c53ce37ab5a96122", "shasum": "" }, "require": { @@ -14522,7 +14524,7 @@ "phpstan/extension-installer": "^1.1", "phpstan/phpstan-deprecation-rules": "^1.0", "phpstan/phpstan-phpunit": "^1.0", - "spatie/phpunit-snapshot-assertions": "^4.0|^5.0" + "spatie/pest-plugin-snapshots": "^1.0|^2.0" }, "type": "library", "extra": { @@ -14552,7 +14554,7 @@ ], "support": { "issues": "https://github.com/spatie/flare-client-php/issues", - "source": "https://github.com/spatie/flare-client-php/tree/1.7.0" + "source": "https://github.com/spatie/flare-client-php/tree/1.8.0" }, "funding": [ { @@ -14560,7 +14562,7 @@ "type": "github" } ], - "time": "2024-06-12T14:39:14+00:00" + "time": "2024-08-01T08:27:26+00:00" }, { "name": "spatie/ignition", @@ -14738,16 +14740,16 @@ }, { "name": "symfony/http-client", - "version": "v6.4.9", + "version": "v6.4.11", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "6e9db0025db565bcf8f1d46ed734b549e51e6045" + "reference": "4c92046bb788648ff1098cc66da69aa7eac8cb65" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/6e9db0025db565bcf8f1d46ed734b549e51e6045", - "reference": "6e9db0025db565bcf8f1d46ed734b549e51e6045", + "url": "https://api.github.com/repos/symfony/http-client/zipball/4c92046bb788648ff1098cc66da69aa7eac8cb65", + "reference": "4c92046bb788648ff1098cc66da69aa7eac8cb65", "shasum": "" }, "require": { @@ -14811,7 +14813,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v6.4.9" + "source": "https://github.com/symfony/http-client/tree/v6.4.11" }, "funding": [ { @@ -14827,7 +14829,7 @@ "type": "tidelift" } ], - "time": "2024-06-28T07:59:05+00:00" + "time": "2024-08-26T06:30:21+00:00" }, { "name": "symfony/http-client-contracts", diff --git a/config/app.php b/config/app.php index 41e94c09b..34484fe41 100644 --- a/config/app.php +++ b/config/app.php @@ -199,6 +199,7 @@ return [ App\Providers\EventServiceProvider::class, App\Providers\HorizonServiceProvider::class, App\Providers\RouteServiceProvider::class, + App\Providers\TelescopeServiceProvider::class, ], diff --git a/config/clockwork.php b/config/clockwork.php new file mode 100644 index 000000000..ce880464a --- /dev/null +++ b/config/clockwork.php @@ -0,0 +1,424 @@ + env('CLOCKWORK_ENABLE', null), + + /* + |------------------------------------------------------------------------------------------------------------------ + | Features + |------------------------------------------------------------------------------------------------------------------ + | + | You can enable or disable various Clockwork features here. Some features have additional settings (eg. slow query + | threshold for database queries). + | + */ + + 'features' => [ + + // Cache usage stats and cache queries including results + 'cache' => [ + 'enabled' => env('CLOCKWORK_CACHE_ENABLED', true), + + // Collect cache queries + 'collect_queries' => env('CLOCKWORK_CACHE_QUERIES', true), + + // Collect values from cache queries (high performance impact with a very high number of queries) + 'collect_values' => env('CLOCKWORK_CACHE_COLLECT_VALUES', false) + ], + + // Database usage stats and queries + 'database' => [ + 'enabled' => env('CLOCKWORK_DATABASE_ENABLED', true), + + // Collect database queries (high performance impact with a very high number of queries) + 'collect_queries' => env('CLOCKWORK_DATABASE_COLLECT_QUERIES', true), + + // Collect details of models updates (high performance impact with a lot of model updates) + 'collect_models_actions' => env('CLOCKWORK_DATABASE_COLLECT_MODELS_ACTIONS', true), + + // Collect details of retrieved models (very high performance impact with a lot of models retrieved) + 'collect_models_retrieved' => env('CLOCKWORK_DATABASE_COLLECT_MODELS_RETRIEVED', false), + + // Query execution time threshold in milliseconds after which the query will be marked as slow + 'slow_threshold' => env('CLOCKWORK_DATABASE_SLOW_THRESHOLD'), + + // Collect only slow database queries + 'slow_only' => env('CLOCKWORK_DATABASE_SLOW_ONLY', false), + + // Detect and report duplicate queries + 'detect_duplicate_queries' => env('CLOCKWORK_DATABASE_DETECT_DUPLICATE_QUERIES', false) + ], + + // Dispatched events + 'events' => [ + 'enabled' => env('CLOCKWORK_EVENTS_ENABLED', true), + + // Ignored events (framework events are ignored by default) + 'ignored_events' => [ + // App\Events\UserRegistered::class, + // 'user.registered' + ], + ], + + // Laravel log (you can still log directly to Clockwork with laravel log disabled) + 'log' => [ + 'enabled' => env('CLOCKWORK_LOG_ENABLED', true) + ], + + // Sent notifications + 'notifications' => [ + 'enabled' => env('CLOCKWORK_NOTIFICATIONS_ENABLED', true), + ], + + // Performance metrics + 'performance' => [ + // Allow collecting of client metrics. Requires separate clockwork-browser npm package. + 'client_metrics' => env('CLOCKWORK_PERFORMANCE_CLIENT_METRICS', true) + ], + + // Dispatched queue jobs + 'queue' => [ + 'enabled' => env('CLOCKWORK_QUEUE_ENABLED', true) + ], + + // Redis commands + 'redis' => [ + 'enabled' => env('CLOCKWORK_REDIS_ENABLED', true) + ], + + // Routes list + 'routes' => [ + 'enabled' => env('CLOCKWORK_ROUTES_ENABLED', false), + + // Collect only routes from particular namespaces (only application routes by default) + 'only_namespaces' => [ 'App' ] + ], + + // Rendered views + 'views' => [ + 'enabled' => env('CLOCKWORK_VIEWS_ENABLED', true), + + // Collect views including view data (high performance impact with a high number of views) + 'collect_data' => env('CLOCKWORK_VIEWS_COLLECT_DATA', false), + + // Use Twig profiler instead of Laravel events for apps using laravel-twigbridge (more precise, but does + // not support collecting view data) + 'use_twig_profiler' => env('CLOCKWORK_VIEWS_USE_TWIG_PROFILER', false) + ] + + ], + + /* + |------------------------------------------------------------------------------------------------------------------ + | Enable web UI + |------------------------------------------------------------------------------------------------------------------ + | + | Clockwork comes with a web UI accessible via http://your.app/clockwork. Here you can enable or disable this + | feature. You can also set a custom path for the web UI. + | + */ + + 'web' => env('CLOCKWORK_WEB', true), + + /* + |------------------------------------------------------------------------------------------------------------------ + | Enable toolbar + |------------------------------------------------------------------------------------------------------------------ + | + | Clockwork can show a toolbar with basic metrics on all responses. Here you can enable or disable this feature. + | Requires a separate clockwork-browser npm library. + | For installation instructions see https://underground.works/clockwork/#docs-viewing-data + | + */ + + 'toolbar' => env('CLOCKWORK_TOOLBAR', true), + + /* + |------------------------------------------------------------------------------------------------------------------ + | HTTP requests collection + |------------------------------------------------------------------------------------------------------------------ + | + | Clockwork collects data about HTTP requests to your app. Here you can choose which requests should be collected. + | + */ + + 'requests' => [ + // With on-demand mode enabled, Clockwork will only profile requests when the browser extension is open or you + // manually pass a "clockwork-profile" cookie or get/post data key. + // Optionally you can specify a "secret" that has to be passed as the value to enable profiling. + 'on_demand' => env('CLOCKWORK_REQUESTS_ON_DEMAND', false), + + // Collect only errors (requests with HTTP 4xx and 5xx responses) + 'errors_only' => env('CLOCKWORK_REQUESTS_ERRORS_ONLY', false), + + // Response time threshold in milliseconds after which the request will be marked as slow + 'slow_threshold' => env('CLOCKWORK_REQUESTS_SLOW_THRESHOLD'), + + // Collect only slow requests + 'slow_only' => env('CLOCKWORK_REQUESTS_SLOW_ONLY', false), + + // Sample the collected requests (e.g. set to 100 to collect only 1 in 100 requests) + 'sample' => env('CLOCKWORK_REQUESTS_SAMPLE', false), + + // List of URIs that should not be collected + 'except' => [ + '/horizon/.*', // Laravel Horizon requests + '/telescope/.*', // Laravel Telescope requests + '/_tt/.*', // Laravel Telescope toolbar + '/_debugbar/.*', // Laravel DebugBar requests + ], + + // List of URIs that should be collected, any other URI will not be collected if not empty + 'only' => [ + // '/api/.*' + ], + + // Don't collect OPTIONS requests, mostly used in the CSRF pre-flight requests and are rarely of interest + 'except_preflight' => env('CLOCKWORK_REQUESTS_EXCEPT_PREFLIGHT', true) + ], + + /* + |------------------------------------------------------------------------------------------------------------------ + | Artisan commands collection + |------------------------------------------------------------------------------------------------------------------ + | + | Clockwork can collect data about executed artisan commands. Here you can enable and configure which commands + | should be collected. + | + */ + + 'artisan' => [ + // Enable or disable collection of executed Artisan commands + 'collect' => env('CLOCKWORK_ARTISAN_COLLECT', false), + + // List of commands that should not be collected (built-in commands are not collected by default) + 'except' => [ + // 'inspire' + ], + + // List of commands that should be collected, any other command will not be collected if not empty + 'only' => [ + // 'inspire' + ], + + // Enable or disable collection of command output + 'collect_output' => env('CLOCKWORK_ARTISAN_COLLECT_OUTPUT', false), + + // Enable or disable collection of built-in Laravel commands + 'except_laravel_commands' => env('CLOCKWORK_ARTISAN_EXCEPT_LARAVEL_COMMANDS', true) + ], + + /* + |------------------------------------------------------------------------------------------------------------------ + | Queue jobs collection + |------------------------------------------------------------------------------------------------------------------ + | + | Clockwork can collect data about executed queue jobs. Here you can enable and configure which queue jobs should + | be collected. + | + */ + + 'queue' => [ + // Enable or disable collection of executed queue jobs + 'collect' => env('CLOCKWORK_QUEUE_COLLECT', false), + + // List of queue jobs that should not be collected + 'except' => [ + // App\Jobs\ExpensiveJob::class + ], + + // List of queue jobs that should be collected, any other queue job will not be collected if not empty + 'only' => [ + // App\Jobs\BuggyJob::class + ] + ], + + /* + |------------------------------------------------------------------------------------------------------------------ + | Tests collection + |------------------------------------------------------------------------------------------------------------------ + | + | Clockwork can collect data about executed tests. Here you can enable and configure which tests should be + | collected. + | + */ + + 'tests' => [ + // Enable or disable collection of ran tests + 'collect' => env('CLOCKWORK_TESTS_COLLECT', false), + + // List of tests that should not be collected + 'except' => [ + // Tests\Unit\ExampleTest::class + ] + ], + + /* + |------------------------------------------------------------------------------------------------------------------ + | Enable data collection when Clockwork is disabled + |------------------------------------------------------------------------------------------------------------------ + | + | You can enable this setting to collect data even when Clockwork is disabled, e.g. for future analysis. + | + */ + + 'collect_data_always' => env('CLOCKWORK_COLLECT_DATA_ALWAYS', false), + + /* + |------------------------------------------------------------------------------------------------------------------ + | Metadata storage + |------------------------------------------------------------------------------------------------------------------ + | + | Configure how is the metadata collected by Clockwork stored. Three options are available: + | - files - A simple fast storage implementation storing data in one-per-request files. + | - sql - Stores requests in a sql database. Supports MySQL, PostgreSQL and SQLite. Requires PDO. + | - redis - Stores requests in redis. Requires phpredis. + */ + + 'storage' => env('CLOCKWORK_STORAGE', 'files'), + + // Path where the Clockwork metadata is stored + 'storage_files_path' => env('CLOCKWORK_STORAGE_FILES_PATH', storage_path('clockwork')), + + // Compress the metadata files using gzip, trading a little bit of performance for lower disk usage + 'storage_files_compress' => env('CLOCKWORK_STORAGE_FILES_COMPRESS', false), + + // SQL database to use, can be a name of database configured in database.php or a path to a SQLite file + 'storage_sql_database' => env('CLOCKWORK_STORAGE_SQL_DATABASE', storage_path('clockwork.sqlite')), + + // SQL table name to use, the table is automatically created and updated when needed + 'storage_sql_table' => env('CLOCKWORK_STORAGE_SQL_TABLE', 'clockwork'), + + // Redis connection, name of redis connection or cluster configured in database.php + 'storage_redis' => env('CLOCKWORK_STORAGE_REDIS', 'default'), + + // Redis prefix for Clockwork keys ("clockwork" if not set) + 'storage_redis_prefix' => env('CLOCKWORK_STORAGE_REDIS_PREFIX', 'clockwork'), + + // Maximum lifetime of collected metadata in minutes, older requests will automatically be deleted, false to disable + 'storage_expiration' => env('CLOCKWORK_STORAGE_EXPIRATION', 60 * 24 * 7), + + /* + |------------------------------------------------------------------------------------------------------------------ + | Authentication + |------------------------------------------------------------------------------------------------------------------ + | + | Clockwork can be configured to require authentication before allowing access to the collected data. This might be + | useful when the application is publicly accessible. Setting to true will enable a simple authentication with a + | pre-configured password. You can also pass a class name of a custom implementation. + | + */ + + 'authentication' => env('CLOCKWORK_AUTHENTICATION', false), + + // Password for the simple authentication + 'authentication_password' => env('CLOCKWORK_AUTHENTICATION_PASSWORD', 'VerySecretPassword'), + + /* + |------------------------------------------------------------------------------------------------------------------ + | Stack traces collection + |------------------------------------------------------------------------------------------------------------------ + | + | Clockwork can collect stack traces for log messages and certain data like database queries. Here you can set + | whether to collect stack traces, limit the number of collected frames and set further configuration. Collecting + | long stack traces considerably increases metadata size. + | + */ + + 'stack_traces' => [ + // Enable or disable collecting of stack traces + 'enabled' => env('CLOCKWORK_STACK_TRACES_ENABLED', true), + + // Limit the number of frames to be collected + 'limit' => env('CLOCKWORK_STACK_TRACES_LIMIT', 10), + + // List of vendor names to skip when determining caller, common vendors are automatically added + 'skip_vendors' => [ + // 'phpunit' + ], + + // List of namespaces to skip when determining caller + 'skip_namespaces' => [ + // 'Laravel' + ], + + // List of class names to skip when determining caller + 'skip_classes' => [ + // App\CustomLog::class + ] + + ], + + /* + |------------------------------------------------------------------------------------------------------------------ + | Serialization + |------------------------------------------------------------------------------------------------------------------ + | + | Clockwork serializes the collected data to json for storage and transfer. Here you can configure certain aspects + | of serialization. Serialization has a large effect on the cpu time and memory usage. + | + */ + + // Maximum depth of serialized multi-level arrays and objects + 'serialization_depth' => env('CLOCKWORK_SERIALIZATION_DEPTH', 10), + + // A list of classes that will never be serialized (e.g. a common service container class) + 'serialization_blackbox' => [ + \Illuminate\Container\Container::class, + \Illuminate\Foundation\Application::class, + \Laravel\Lumen\Application::class + ], + + /* + |------------------------------------------------------------------------------------------------------------------ + | Register helpers + |------------------------------------------------------------------------------------------------------------------ + | + | Clockwork comes with a "clock" global helper function. You can use this helper to quickly log something and to + | access the Clockwork instance. + | + */ + + 'register_helpers' => env('CLOCKWORK_REGISTER_HELPERS', true), + + /* + |------------------------------------------------------------------------------------------------------------------ + | Send headers for AJAX request + |------------------------------------------------------------------------------------------------------------------ + | + | When trying to collect data, the AJAX method can sometimes fail if it is missing required headers. For example, an + | API might require a version number using Accept headers to route the HTTP request to the correct codebase. + | + */ + + 'headers' => [ + // 'Accept' => 'application/vnd.com.whatever.v1+json', + ], + + /* + |------------------------------------------------------------------------------------------------------------------ + | Server timing + |------------------------------------------------------------------------------------------------------------------ + | + | Clockwork supports the W3C Server Timing specification, which allows for collecting a simple performance metrics + | in a cross-browser way. E.g. in Chrome, your app, database and timeline event timings will be shown in the Dev + | Tools network tab. This setting specifies the max number of timeline events that will be sent. Setting to false + | will disable the feature. + | + */ + + 'server_timing' => env('CLOCKWORK_SERVER_TIMING', 10) + +]; diff --git a/config/constants.php b/config/constants.php index 861b645ed..906ef3ba2 100644 --- a/config/constants.php +++ b/config/constants.php @@ -6,7 +6,9 @@ return [ 'contact' => 'https://coolify.io/docs/contact', ], 'ssh' => [ - 'mux_persist_time' => env('SSH_MUX_PERSIST_TIME', '1m'), + // Using MUX + 'mux_enabled' => env('MUX_ENABLED', env('SSH_MUX_ENABLED', true), true), + 'mux_persist_time' => env('SSH_MUX_PERSIST_TIME', '1h'), 'connection_timeout' => 10, 'server_interval' => 20, 'command_timeout' => 7200, diff --git a/config/coolify.php b/config/coolify.php index a6d6d8581..f9878fff7 100644 --- a/config/coolify.php +++ b/config/coolify.php @@ -7,11 +7,10 @@ return [ 'self_hosted' => env('SELF_HOSTED', true), 'waitlist' => env('WAITLIST', false), 'license_url' => 'https://licenses.coollabs.io', - 'mux_enabled' => env('MUX_ENABLED', true), 'dev_webhook' => env('SERVEO_URL'), 'is_windows_docker_desktop' => env('IS_WINDOWS_DOCKER_DESKTOP', false), 'base_config_path' => env('BASE_CONFIG_PATH', '/data/coolify'), - 'helper_image' => env('HELPER_IMAGE', 'ghcr.io/coollabsio/coolify-helper:latest'), + 'helper_image' => env('HELPER_IMAGE', 'ghcr.io/coollabsio/coolify-helper'), 'is_horizon_enabled' => env('HORIZON_ENABLED', true), 'is_scheduler_enabled' => env('SCHEDULER_ENABLED', true), ]; diff --git a/config/database.php b/config/database.php index 248c6150a..f48a68082 100644 --- a/config/database.php +++ b/config/database.php @@ -35,34 +35,6 @@ return [ 'connections' => [ - 'sqlite' => [ - 'driver' => 'sqlite', - 'url' => env('DATABASE_URL'), - 'database' => env('DB_DATABASE', database_path('database.sqlite')), - 'prefix' => '', - 'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true), - ], - - 'mysql' => [ - 'driver' => 'mysql', - 'url' => env('DATABASE_URL'), - 'host' => env('DB_HOST', '127.0.0.1'), - 'port' => env('DB_PORT', '3306'), - 'database' => env('DB_DATABASE', 'forge'), - 'username' => env('DB_USERNAME', 'forge'), - 'password' => env('DB_PASSWORD', ''), - 'unix_socket' => env('DB_SOCKET', ''), - 'charset' => 'utf8mb4', - 'collation' => 'utf8mb4_unicode_ci', - 'prefix' => '', - 'prefix_indexes' => true, - 'strict' => true, - 'engine' => null, - 'options' => extension_loaded('pdo_mysql') ? array_filter([ - PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), - ]) : [], - ], - 'pgsql' => [ 'driver' => 'pgsql', 'url' => env('DATABASE_URL'), @@ -77,22 +49,6 @@ return [ 'search_path' => 'public', 'sslmode' => 'prefer', ], - - 'sqlsrv' => [ - 'driver' => 'sqlsrv', - 'url' => env('DATABASE_URL'), - 'host' => env('DB_HOST', 'localhost'), - 'port' => env('DB_PORT', '1433'), - 'database' => env('DB_DATABASE', 'forge'), - 'username' => env('DB_USERNAME', 'forge'), - 'password' => env('DB_PASSWORD', ''), - 'charset' => 'utf8', - 'prefix' => '', - 'prefix_indexes' => true, - // 'encrypt' => env('DB_ENCRYPT', 'yes'), - // 'trust_server_certificate' => env('DB_TRUST_SERVER_CERTIFICATE', 'false'), - ], - ], /* diff --git a/config/sentry.php b/config/sentry.php index 7b48b96b0..471a1e0fc 100644 --- a/config/sentry.php +++ b/config/sentry.php @@ -7,7 +7,7 @@ return [ // The release version of your application // Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD')) - 'release' => '4.0.0-beta.323', + 'release' => '4.0.0-beta.341', // When left empty or `null` the Laravel environment will be used 'environment' => config('app.env'), diff --git a/config/telescope.php b/config/telescope.php new file mode 100644 index 000000000..24077c24d --- /dev/null +++ b/config/telescope.php @@ -0,0 +1,206 @@ + env('TELESCOPE_ENABLED', false), + + /* + |-------------------------------------------------------------------------- + | Telescope Domain + |-------------------------------------------------------------------------- + | + | This is the subdomain where Telescope will be accessible from. If the + | setting is null, Telescope will reside under the same domain as the + | application. Otherwise, this value will be used as the subdomain. + | + */ + + 'domain' => env('TELESCOPE_DOMAIN'), + + /* + |-------------------------------------------------------------------------- + | Telescope Path + |-------------------------------------------------------------------------- + | + | This is the URI path where Telescope will be accessible from. Feel free + | to change this path to anything you like. Note that the URI will not + | affect the paths of its internal API that aren't exposed to users. + | + */ + + 'path' => env('TELESCOPE_PATH', 'telescope'), + + /* + |-------------------------------------------------------------------------- + | Telescope Storage Driver + |-------------------------------------------------------------------------- + | + | This configuration options determines the storage driver that will + | be used to store Telescope's data. In addition, you may set any + | custom options as needed by the particular driver you choose. + | + */ + + 'driver' => env('TELESCOPE_DRIVER', 'database'), + + 'storage' => [ + 'database' => [ + 'connection' => env('DB_CONNECTION', 'pgsql'), + 'chunk' => 1000, + ], + ], + + /* + |-------------------------------------------------------------------------- + | Telescope Queue + |-------------------------------------------------------------------------- + | + | This configuration options determines the queue connection and queue + | which will be used to process ProcessPendingUpdate jobs. This can + | be changed if you would prefer to use a non-default connection. + | + */ + + 'queue' => [ + 'connection' => env('TELESCOPE_QUEUE_CONNECTION', null), + 'queue' => env('TELESCOPE_QUEUE', null), + ], + + /* + |-------------------------------------------------------------------------- + | Telescope Route Middleware + |-------------------------------------------------------------------------- + | + | These middleware will be assigned to every Telescope route, giving you + | the chance to add your own middleware to this list or change any of + | the existing middleware. Or, you can simply stick with this list. + | + */ + + 'middleware' => [ + 'web', + Authorize::class, + ], + + /* + |-------------------------------------------------------------------------- + | Allowed / Ignored Paths & Commands + |-------------------------------------------------------------------------- + | + | The following array lists the URI paths and Artisan commands that will + | not be watched by Telescope. In addition to this list, some Laravel + | commands, like migrations and queue commands, are always ignored. + | + */ + + 'only_paths' => [ + // 'api/*' + ], + + 'ignore_paths' => [ + 'livewire*', + 'nova-api*', + 'pulse*', + 'broadcasting/auth', + ], + + 'ignore_commands' => [ + // + ], + + /* + |-------------------------------------------------------------------------- + | Telescope Watchers + |-------------------------------------------------------------------------- + | + | The following array lists the "watchers" that will be registered with + | Telescope. The watchers gather the application's profile data when + | a request or task is executed. Feel free to customize this list. + | + */ + + 'watchers' => [ + Watchers\BatchWatcher::class => env('TELESCOPE_BATCH_WATCHER', true), + + Watchers\CacheWatcher::class => [ + 'enabled' => env('TELESCOPE_CACHE_WATCHER', true), + 'hidden' => [], + ], + + Watchers\ClientRequestWatcher::class => env('TELESCOPE_CLIENT_REQUEST_WATCHER', true), + + Watchers\CommandWatcher::class => [ + 'enabled' => env('TELESCOPE_COMMAND_WATCHER', true), + 'ignore' => [], + ], + + Watchers\DumpWatcher::class => [ + 'enabled' => env('TELESCOPE_DUMP_WATCHER', true), + 'always' => env('TELESCOPE_DUMP_WATCHER_ALWAYS', false), + ], + + Watchers\EventWatcher::class => [ + 'enabled' => env('TELESCOPE_EVENT_WATCHER', true), + 'ignore' => [], + ], + + Watchers\ExceptionWatcher::class => env('TELESCOPE_EXCEPTION_WATCHER', true), + + Watchers\GateWatcher::class => [ + 'enabled' => env('TELESCOPE_GATE_WATCHER', false), + 'ignore_abilities' => [], + 'ignore_packages' => true, + 'ignore_paths' => [], + ], + + Watchers\JobWatcher::class => env('TELESCOPE_JOB_WATCHER', false), + + Watchers\LogWatcher::class => [ + 'enabled' => env('TELESCOPE_LOG_WATCHER', true), + 'level' => 'debug', + ], + + Watchers\MailWatcher::class => env('TELESCOPE_MAIL_WATCHER', false), + + Watchers\ModelWatcher::class => [ + 'enabled' => env('TELESCOPE_MODEL_WATCHER', true), + 'events' => ['eloquent.*'], + 'hydrations' => true, + ], + + Watchers\NotificationWatcher::class => env('TELESCOPE_NOTIFICATION_WATCHER', false), + + Watchers\QueryWatcher::class => [ + 'enabled' => env('TELESCOPE_QUERY_WATCHER', true), + 'ignore_packages' => true, + 'ignore_paths' => [], + 'slow' => 100, + ], + + Watchers\RedisWatcher::class => env('TELESCOPE_REDIS_WATCHER', true), + + Watchers\RequestWatcher::class => [ + 'enabled' => env('TELESCOPE_REQUEST_WATCHER', true), + 'size_limit' => env('TELESCOPE_RESPONSE_SIZE_LIMIT', 64), + 'ignore_http_methods' => [], + 'ignore_status_codes' => [], + ], + + Watchers\ScheduleWatcher::class => env('TELESCOPE_SCHEDULE_WATCHER', false), + Watchers\ViewWatcher::class => env('TELESCOPE_VIEW_WATCHER', true), + ], +]; diff --git a/config/version.php b/config/version.php index 73f3e1263..32eb01cd0 100644 --- a/config/version.php +++ b/config/version.php @@ -1,3 +1,3 @@ getConnection()); + + $schema->create('telescope_entries', function (Blueprint $table) { + $table->bigIncrements('sequence'); + $table->uuid('uuid'); + $table->uuid('batch_id'); + $table->string('family_hash')->nullable(); + $table->boolean('should_display_on_index')->default(true); + $table->string('type', 20); + $table->longText('content'); + $table->dateTime('created_at')->nullable(); + + $table->unique('uuid'); + $table->index('batch_id'); + $table->index('family_hash'); + $table->index('created_at'); + $table->index(['type', 'should_display_on_index']); + }); + + $schema->create('telescope_entries_tags', function (Blueprint $table) { + $table->uuid('entry_uuid'); + $table->string('tag'); + + $table->primary(['entry_uuid', 'tag']); + $table->index('tag'); + + $table->foreign('entry_uuid') + ->references('uuid') + ->on('telescope_entries') + ->onDelete('cascade'); + }); + + $schema->create('telescope_monitoring', function (Blueprint $table) { + $table->string('tag')->primary(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + $schema = Schema::connection($this->getConnection()); + + $schema->dropIfExists('telescope_entries_tags'); + $schema->dropIfExists('telescope_entries'); + $schema->dropIfExists('telescope_monitoring'); + } +}; diff --git a/database/migrations/2024_08_09_215659_add_server_cleanup_fields_to_server_settings_table.php b/database/migrations/2024_08_09_215659_add_server_cleanup_fields_to_server_settings_table.php new file mode 100644 index 000000000..b5300c905 --- /dev/null +++ b/database/migrations/2024_08_09_215659_add_server_cleanup_fields_to_server_settings_table.php @@ -0,0 +1,60 @@ +boolean('force_docker_cleanup')->default(false); + $table->string('docker_cleanup_frequency')->default('*/10 * * * *'); + $table->integer('docker_cleanup_threshold')->default(80); + + // Remove old columns + $table->dropColumn('cleanup_after_percentage'); + $table->dropColumn('is_force_cleanup_enabled'); + }); + foreach ($serverSettings as $serverSetting) { + $serverSetting->force_docker_cleanup = $serverSetting->is_force_cleanup_enabled; + $serverSetting->docker_cleanup_threshold = $serverSetting->cleanup_after_percentage; + $serverSetting->save(); + } + + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + $serverSettings = ServerSetting::all(); + Schema::table('server_settings', function (Blueprint $table) { + $table->dropColumn('force_docker_cleanup'); + $table->dropColumn('docker_cleanup_frequency'); + $table->dropColumn('docker_cleanup_threshold'); + + // Add back old columns + $table->integer('cleanup_after_percentage')->default(80); + $table->boolean('force_server_cleanup')->default(false); + $table->boolean('is_force_cleanup_enabled')->default(false); + }); + foreach ($serverSettings as $serverSetting) { + $serverSetting->is_force_cleanup_enabled = $serverSetting->force_docker_cleanup; + $serverSetting->cleanup_after_percentage = $serverSetting->docker_cleanup_threshold; + $serverSetting->save(); + } + } +} diff --git a/database/migrations/2024_08_12_131659_add_local_file_volume_based_on_git.php b/database/migrations/2024_08_12_131659_add_local_file_volume_based_on_git.php new file mode 100644 index 000000000..d180c7ec2 --- /dev/null +++ b/database/migrations/2024_08_12_131659_add_local_file_volume_based_on_git.php @@ -0,0 +1,28 @@ +boolean('is_based_on_git')->default(false); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('local_file_volumes', function (Blueprint $table) { + $table->dropColumn('is_based_on_git'); + }); + } +}; diff --git a/database/migrations/2024_08_12_155023_add_timezone_to_server_and_instance_settings.php b/database/migrations/2024_08_12_155023_add_timezone_to_server_and_instance_settings.php new file mode 100644 index 000000000..5bc73a54e --- /dev/null +++ b/database/migrations/2024_08_12_155023_add_timezone_to_server_and_instance_settings.php @@ -0,0 +1,30 @@ +string('server_timezone')->default(''); + }); + + Schema::table('instance_settings', function (Blueprint $table) { + $table->string('instance_timezone')->default('UTC'); + }); + } + + public function down() + { + Schema::table('server_settings', function (Blueprint $table) { + $table->dropColumn('server_timezone'); + }); + + Schema::table('instance_settings', function (Blueprint $table) { + $table->dropColumn('instance_timezone'); + }); + } +} diff --git a/database/migrations/2024_08_14_183120_add_order_to_environment_variables_table.php b/database/migrations/2024_08_14_183120_add_order_to_environment_variables_table.php new file mode 100644 index 000000000..527535827 --- /dev/null +++ b/database/migrations/2024_08_14_183120_add_order_to_environment_variables_table.php @@ -0,0 +1,28 @@ +integer('order')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('environment_variables', function (Blueprint $table) { + $table->dropColumn('order'); + }); + } +}; diff --git a/database/migrations/2024_08_15_115907_add_build_server_id_to_deployment_queue.php b/database/migrations/2024_08_15_115907_add_build_server_id_to_deployment_queue.php new file mode 100644 index 000000000..da5f065a6 --- /dev/null +++ b/database/migrations/2024_08_15_115907_add_build_server_id_to_deployment_queue.php @@ -0,0 +1,28 @@ +integer('build_server_id')->nullable()->after('server_id'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('application_deployment_queues', function (Blueprint $table) { + $table->dropColumn('build_server_id'); + }); + } +}; diff --git a/database/migrations/2024_08_16_105649_add_custom_docker_options_to_dbs.php b/database/migrations/2024_08_16_105649_add_custom_docker_options_to_dbs.php new file mode 100644 index 000000000..dbca8c10a --- /dev/null +++ b/database/migrations/2024_08_16_105649_add_custom_docker_options_to_dbs.php @@ -0,0 +1,70 @@ +text('custom_docker_run_options')->nullable(); + }); + Schema::table('standalone_mysqls', function (Blueprint $table) { + $table->text('custom_docker_run_options')->nullable(); + }); + Schema::table('standalone_mariadbs', function (Blueprint $table) { + $table->text('custom_docker_run_options')->nullable(); + }); + Schema::table('standalone_redis', function (Blueprint $table) { + $table->text('custom_docker_run_options')->nullable(); + }); + Schema::table('standalone_clickhouses', function (Blueprint $table) { + $table->text('custom_docker_run_options')->nullable(); + }); + Schema::table('standalone_dragonflies', function (Blueprint $table) { + $table->text('custom_docker_run_options')->nullable(); + }); + Schema::table('standalone_keydbs', function (Blueprint $table) { + $table->text('custom_docker_run_options')->nullable(); + }); + Schema::table('standalone_mongodbs', function (Blueprint $table) { + $table->text('custom_docker_run_options')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('standalone_postgresqls', function (Blueprint $table) { + $table->dropColumn('custom_docker_run_options'); + }); + Schema::table('standalone_mysqls', function (Blueprint $table) { + $table->dropColumn('custom_docker_run_options'); + }); + Schema::table('standalone_mariadbs', function (Blueprint $table) { + $table->dropColumn('custom_docker_run_options'); + }); + Schema::table('standalone_redis', function (Blueprint $table) { + $table->dropColumn('custom_docker_run_options'); + }); + Schema::table('standalone_clickhouses', function (Blueprint $table) { + $table->dropColumn('custom_docker_run_options'); + }); + Schema::table('standalone_dragonflies', function (Blueprint $table) { + $table->dropColumn('custom_docker_run_options'); + }); + Schema::table('standalone_keydbs', function (Blueprint $table) { + $table->dropColumn('custom_docker_run_options'); + }); + Schema::table('standalone_mongodbs', function (Blueprint $table) { + $table->dropColumn('custom_docker_run_options'); + }); + } +}; diff --git a/database/migrations/2024_08_27_090528_add_compose_parsing_version_to_services.php b/database/migrations/2024_08_27_090528_add_compose_parsing_version_to_services.php new file mode 100644 index 000000000..e8c0474d2 --- /dev/null +++ b/database/migrations/2024_08_27_090528_add_compose_parsing_version_to_services.php @@ -0,0 +1,28 @@ +string('compose_parsing_version')->default('2'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('services', function (Blueprint $table) { + $table->dropColumn('compose_parsing_version'); + }); + } +}; diff --git a/database/migrations/2024_09_05_085700_add_helper_version_to_instance_settings.php b/database/migrations/2024_09_05_085700_add_helper_version_to_instance_settings.php new file mode 100644 index 000000000..53ecce0f1 --- /dev/null +++ b/database/migrations/2024_09_05_085700_add_helper_version_to_instance_settings.php @@ -0,0 +1,28 @@ +string('helper_version')->default('1.0.0'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('instance_settings', function (Blueprint $table) { + $table->dropColumn('helper_version'); + }); + } +}; diff --git a/database/migrations/2024_09_06_062534_change_server_cleanup_to_forced.php b/database/migrations/2024_09_06_062534_change_server_cleanup_to_forced.php new file mode 100644 index 000000000..ad6e5bd9e --- /dev/null +++ b/database/migrations/2024_09_06_062534_change_server_cleanup_to_forced.php @@ -0,0 +1,37 @@ +boolean('force_docker_cleanup')->default(true)->change(); + }); + $serverSettings = ServerSetting::all(); + foreach ($serverSettings as $serverSetting) { + if ($serverSetting->force_docker_cleanup === false) { + $serverSetting->force_docker_cleanup = true; + $serverSetting->docker_cleanup_frequency = '*/10 * * * *'; + $serverSetting->save(); + } + } + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('server_settings', function (Blueprint $table) { + $table->boolean('force_docker_cleanup')->default(false)->change(); + }); + } +}; diff --git a/database/migrations/2024_09_07_185402_change_cleanup_schedule.php b/database/migrations/2024_09_07_185402_change_cleanup_schedule.php new file mode 100644 index 000000000..ea8861f9b --- /dev/null +++ b/database/migrations/2024_09_07_185402_change_cleanup_schedule.php @@ -0,0 +1,37 @@ +string('docker_cleanup_frequency')->default('0 0 * * *')->change(); + }); + + $serverSettings = ServerSetting::all(); + foreach ($serverSettings as $serverSetting) { + if ($serverSetting->force_docker_cleanup && $serverSetting->docker_cleanup_frequency === '*/10 * * * *') { + $serverSetting->docker_cleanup_frequency = '0 0 * * *'; + $serverSetting->save(); + } + } + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('server_settings', function (Blueprint $table) { + $table->string('docker_cleanup_frequency')->default('*/10 * * * *')->change(); + }); + } +}; diff --git a/database/migrations/2024_09_08_130756_update_server_settings_default_timezone.php b/database/migrations/2024_09_08_130756_update_server_settings_default_timezone.php new file mode 100644 index 000000000..db1322e62 --- /dev/null +++ b/database/migrations/2024_09_08_130756_update_server_settings_default_timezone.php @@ -0,0 +1,28 @@ +string('server_timezone')->default('UTC')->change(); + }); + + DB::table('server_settings') + ->whereNull('server_timezone') + ->orWhere('server_timezone', '') + ->update(['server_timezone' => 'UTC']); + } + + public function down() + { + Schema::table('server_settings', function (Blueprint $table) { + $table->string('server_timezone')->default('')->change(); + }); + } +} diff --git a/database/seeders/ProductionSeeder.php b/database/seeders/ProductionSeeder.php index c88a35f6a..799dd0440 100644 --- a/database/seeders/ProductionSeeder.php +++ b/database/seeders/ProductionSeeder.php @@ -79,7 +79,8 @@ class ProductionSeeder extends Seeder ], [ 'name' => 'localhost\'s key', - 'description' => 'The private key for the Coolify host machine (localhost).', 'private_key' => $coolify_key, + 'description' => 'The private key for the Coolify host machine (localhost).', + 'private_key' => $coolify_key, ] ); } else { @@ -180,5 +181,6 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA== $oauth_settings_seeder = new OauthSettingSeeder; $oauth_settings_seeder->run(); + } } diff --git a/database/seeders/StandaloneDockerSeeder.php b/database/seeders/StandaloneDockerSeeder.php index 1967bf2d0..a466de56b 100644 --- a/database/seeders/StandaloneDockerSeeder.php +++ b/database/seeders/StandaloneDockerSeeder.php @@ -12,11 +12,13 @@ class StandaloneDockerSeeder extends Seeder */ public function run(): void { - StandaloneDocker::create([ - 'id' => 0, - 'name' => 'Standalone Docker 1', - 'network' => 'coolify', - 'server_id' => 0, - ]); + if (StandaloneDocker::find(0) == null) { + StandaloneDocker::create([ + 'id' => 0, + 'name' => 'Standalone Docker 1', + 'network' => 'coolify', + 'server_id' => 0, + ]); + } } } diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 7eda14d41..750ad45d4 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -19,6 +19,7 @@ services: PUSHER_APP_SECRET: "${PUSHER_APP_SECRET:-coolify}" volumes: - .:/var/www/html/:cached + - /data/coolify/backups/:/var/www/html/storage/app/backups postgres: pull_policy: always ports: @@ -43,10 +44,17 @@ services: - /data/coolify/_volumes/redis/:/data # - coolify-redis-data-dev:/data soketi: + build: + context: . + dockerfile: ./docker/coolify-realtime/Dockerfile env_file: - .env ports: - "${FORWARD_SOKETI_PORT:-6001}:6001" + - "6002:6002" + volumes: + - ./storage:/var/www/html/storage + - ./docker/coolify-realtime/terminal-server.js:/terminal/terminal-server.js environment: SOKETI_DEBUG: "false" SOKETI_DEFAULT_APP_ID: "${PUSHER_APP_ID:-coolify}" diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index b8156cab5..9777c52df 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -13,19 +13,18 @@ services: - /data/coolify/backups:/var/www/html/storage/app/backups - /data/coolify/webhooks-during-maintenance:/var/www/html/storage/app/webhooks-during-maintenance environment: - - PHP_MEMORY_LIMIT - - APP_ID - APP_ENV=production - - APP_DEBUG - APP_NAME + - APP_ID - APP_KEY - APP_URL - - DB_CONNECTION - - DB_HOST - - DB_PORT + - APP_DEBUG - DB_DATABASE - DB_USERNAME - DB_PASSWORD + - DB_HOST + - DB_PORT + - DB_CONNECTION - QUEUE_CONNECTION - REDIS_HOST - REDIS_PASSWORD @@ -34,6 +33,7 @@ services: - HORIZON_BALANCE_MAX_SHIFT - HORIZON_BALANCE_COOLDOWN - SSL_MODE=off + - PHP_MEMORY_LIMIT - PHP_PM_CONTROL=dynamic - PHP_PM_START_SERVERS=1 - PHP_PM_MIN_SPARE_SERVERS=1 @@ -46,8 +46,12 @@ services: - PUSHER_APP_ID - PUSHER_APP_KEY - PUSHER_APP_SECRET + - TERMINAL_PROTOCOL + - TERMINAL_HOST + - TERMINAL_PORT - AUTOUPDATE - SELF_HOSTED + - SSH_MUX_ENABLED - SSH_MUX_PERSIST_TIME - FEEDBACK_DISCORD_WEBHOOK - WAITLIST @@ -83,21 +87,17 @@ services: condition: service_healthy redis: condition: service_healthy + soketi: + condition: service_healthy postgres: volumes: - coolify-db:/var/lib/postgresql/data environment: - POSTGRES_USER: "${DB_USERNAME:-coolify}" + POSTGRES_USER: "${DB_USERNAME}" POSTGRES_PASSWORD: "${DB_PASSWORD}" POSTGRES_DB: "${DB_DATABASE:-coolify}" healthcheck: - test: - [ - "CMD-SHELL", - "pg_isready -U ${DB_USERNAME:-coolify}", - "-d", - "${DB_DATABASE:-coolify}" - ] + test: [ "CMD-SHELL", "pg_isready -U ${DB_USERNAME}", "-d", "${DB_DATABASE:-coolify}" ] interval: 5s retries: 10 timeout: 2s @@ -113,20 +113,30 @@ services: retries: 10 timeout: 2s soketi: + image: 'ghcr.io/coollabsio/coolify-realtime:1.0.1' ports: - "${SOKETI_PORT:-6001}:6001" + - "6002:6002" + volumes: + - /data/coolify/ssh:/var/www/html/storage/app/ssh environment: + APP_NAME: "${APP_NAME:-Coolify}" SOKETI_DEBUG: "${SOKETI_DEBUG:-false}" SOKETI_DEFAULT_APP_ID: "${PUSHER_APP_ID}" SOKETI_DEFAULT_APP_KEY: "${PUSHER_APP_KEY}" SOKETI_DEFAULT_APP_SECRET: "${PUSHER_APP_SECRET}" healthcheck: - test: wget -qO- http://127.0.0.1:6001/ready || exit 1 + test: [ "CMD-SHELL", "wget -qO- http://127.0.0.1:6001/ready && wget -qO- http://127.0.0.1:6002/ready || exit 1" ] interval: 5s retries: 10 timeout: 2s + volumes: coolify-db: name: coolify-db coolify-redis: name: coolify-redis + +networks: + coolify: + external: true diff --git a/docker-compose.windows.yml b/docker-compose.windows.yml index af5ecc0f7..ef2de82e9 100644 --- a/docker-compose.windows.yml +++ b/docker-compose.windows.yml @@ -45,7 +45,7 @@ services: - PUSHER_APP_SECRET - AUTOUPDATE=true - SELF_HOSTED=true - - MUX_ENABLED=false + - SSH_MUX_ENABLED=false - IS_WINDOWS_DOCKER_DESKTOP=true ports: - "${APP_PORT:-8000}:80" @@ -71,14 +71,14 @@ services: volumes: - coolify-db:/var/lib/postgresql/data environment: - POSTGRES_USER: "${DB_USERNAME:-coolify}" + POSTGRES_USER: "${DB_USERNAME}" POSTGRES_PASSWORD: "${DB_PASSWORD}" POSTGRES_DB: "${DB_DATABASE:-coolify}" healthcheck: test: [ "CMD-SHELL", - "pg_isready -U ${DB_USERNAME:-coolify}", + "pg_isready -U ${DB_USERNAME}", "-d", "${DB_DATABASE:-coolify}" ] @@ -103,7 +103,7 @@ services: retries: 10 timeout: 2s soketi: - image: 'quay.io/soketi/soketi:1.6-16-alpine' + image: 'ghcr.io/coollabsio/coolify-realtime:1.0.0' pull_policy: always container_name: coolify-realtime restart: always @@ -111,16 +111,21 @@ services: - .env ports: - "${SOKETI_PORT:-6001}:6001" + - "6002:6002" + volumes: + - ./ssh:/var/www/html/storage/app/ssh environment: + APP_NAME: "${APP_NAME:-Coolify}" SOKETI_DEBUG: "${SOKETI_DEBUG:-false}" SOKETI_DEFAULT_APP_ID: "${PUSHER_APP_ID}" SOKETI_DEFAULT_APP_KEY: "${PUSHER_APP_KEY}" SOKETI_DEFAULT_APP_SECRET: "${PUSHER_APP_SECRET}" healthcheck: - test: wget -qO- http://localhost:6001/ready || exit 1 + test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:6001/ready && wget -qO- http://127.0.0.1:6002/ready || exit 1"] interval: 5s retries: 10 timeout: 2s + volumes: coolify-db: name: coolify-db diff --git a/docker-compose.yml b/docker-compose.yml index 8eed44f8c..68d0f0744 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,6 +10,7 @@ services: depends_on: - postgres - redis + - soketi postgres: image: postgres:15-alpine container_name: coolify-db @@ -23,8 +24,9 @@ services: networks: - coolify soketi: - image: 'quay.io/soketi/soketi:1.6-16-alpine' container_name: coolify-realtime + extra_hosts: + - 'host.docker.internal:host-gateway' restart: always networks: - coolify @@ -32,4 +34,4 @@ networks: coolify: name: coolify driver: bridge - external: true + external: false diff --git a/docker/coolify-helper/Dockerfile b/docker/coolify-helper/Dockerfile index 472f46c5d..7aa9d8722 100644 --- a/docker/coolify-helper/Dockerfile +++ b/docker/coolify-helper/Dockerfile @@ -8,9 +8,9 @@ ARG DOCKER_COMPOSE_VERSION=2.27.1 # https://github.com/docker/buildx/releases ARG DOCKER_BUILDX_VERSION=0.14.1 # https://github.com/buildpacks/pack/releases -ARG PACK_VERSION=0.34.1 +ARG PACK_VERSION=0.35.1 # https://github.com/railwayapp/nixpacks/releases -ARG NIXPACKS_VERSION=1.24.1 +ARG NIXPACKS_VERSION=1.28.0 USER root WORKDIR /artifacts @@ -34,7 +34,7 @@ RUN if [[ ${TARGETPLATFORM} == 'linux/arm64' ]]; then \ chmod +x ~/.docker/cli-plugins/docker-compose /usr/bin/docker /usr/local/bin/pack /root/.docker/cli-plugins/docker-buildx \ ;fi -COPY --from=minio/mc:RELEASE.2024-03-13T23-51-57Z /usr/bin/mc /usr/bin/mc +COPY --from=minio/mc:RELEASE.2024-09-09T07-53-10Z /usr/bin/mc /usr/bin/mc RUN chmod +x /usr/bin/mc ENTRYPOINT ["/sbin/tini", "--"] diff --git a/docker/coolify-realtime/Dockerfile b/docker/coolify-realtime/Dockerfile new file mode 100644 index 000000000..9a7a68376 --- /dev/null +++ b/docker/coolify-realtime/Dockerfile @@ -0,0 +1,9 @@ +FROM quay.io/soketi/soketi:1.6-16-alpine +WORKDIR /terminal +RUN apk add --no-cache openssh-client make g++ python3 +COPY docker/coolify-realtime/package.json ./ +RUN npm i +RUN npm rebuild node-pty --update-binary +COPY docker/coolify-realtime/soketi-entrypoint.sh /soketi-entrypoint.sh +COPY docker/coolify-realtime/terminal-server.js /terminal/terminal-server.js +ENTRYPOINT ["/bin/sh", "/soketi-entrypoint.sh"] diff --git a/docker/coolify-realtime/package.json b/docker/coolify-realtime/package.json new file mode 100644 index 000000000..90d4f77db --- /dev/null +++ b/docker/coolify-realtime/package.json @@ -0,0 +1,13 @@ +{ + "private": true, + "type": "module", + "dependencies": { + "@xterm/addon-fit": "^0.10.0", + "@xterm/xterm": "^5.5.0", + "cookie": "^0.6.0", + "axios": "1.7.5", + "dotenv": "^16.4.5", + "node-pty": "^1.0.0", + "ws": "^8.17.0" + } +} diff --git a/docker/coolify-realtime/soketi-entrypoint.sh b/docker/coolify-realtime/soketi-entrypoint.sh new file mode 100644 index 000000000..3f1f0dc8c --- /dev/null +++ b/docker/coolify-realtime/soketi-entrypoint.sh @@ -0,0 +1,27 @@ +#!/bin/sh +# Function to timestamp logs +timestamp() { + date "+%Y-%m-%d %H:%M:%S" +} + +# Start the terminal server in the background with logging +node /terminal/terminal-server.js > >(while read line; do echo "$(timestamp) [TERMINAL] $line"; done) 2>&1 & +TERMINAL_PID=$! + +# Start the Soketi process in the background with logging +node /app/bin/server.js start > >(while read line; do echo "$(timestamp) [SOKETI] $line"; done) 2>&1 & +SOKETI_PID=$! + +# Function to forward signals to child processes +forward_signal() { + kill -$1 $TERMINAL_PID $SOKETI_PID +} + +# Forward SIGTERM to child processes +trap 'forward_signal TERM' TERM + +# Wait for any process to exit +wait -n + +# Exit with status of process that exited first +exit $? diff --git a/docker/coolify-realtime/terminal-server.js b/docker/coolify-realtime/terminal-server.js new file mode 100755 index 000000000..6dfbe3531 --- /dev/null +++ b/docker/coolify-realtime/terminal-server.js @@ -0,0 +1,229 @@ +import { WebSocketServer } from 'ws'; +import http from 'http'; +import pty from 'node-pty'; +import axios from 'axios'; +import cookie from 'cookie'; +import 'dotenv/config' + +const server = http.createServer((req, res) => { + if (req.url === '/ready') { + res.writeHead(200, { 'Content-Type': 'text/plain' }); + res.end('OK'); + } else { + res.writeHead(404, { 'Content-Type': 'text/plain' }); + res.end('Not Found'); + } +}); + +const verifyClient = async (info, callback) => { + const cookies = cookie.parse(info.req.headers.cookie || ''); + // const origin = new URL(info.origin); + // const protocol = origin.protocol; + const xsrfToken = cookies['XSRF-TOKEN']; + + // Generate session cookie name based on APP_NAME + const appName = process.env.APP_NAME || 'laravel'; + const sessionCookieName = `${appName.replace(/[^a-zA-Z0-9]/g, '_').toLowerCase()}_session`; + const laravelSession = cookies[sessionCookieName]; + + // Verify presence of required tokens + if (!laravelSession || !xsrfToken) { + return callback(false, 401, 'Unauthorized: Missing required tokens'); + } + + try { + // Authenticate with Laravel backend + const response = await axios.post(`http://coolify/terminal/auth`, null, { + headers: { + 'Cookie': `${sessionCookieName}=${laravelSession}`, + 'X-XSRF-TOKEN': xsrfToken + }, + }); + + if (response.status === 200) { + // Authentication successful + callback(true); + } else { + callback(false, 401, 'Unauthorized: Invalid credentials'); + } + } catch (error) { + console.error('Authentication error:', error.message); + callback(false, 500, 'Internal Server Error'); + } +}; + + +const wss = new WebSocketServer({ server, path: '/terminal/ws', verifyClient: verifyClient }); +const userSessions = new Map(); + +wss.on('connection', (ws) => { + const userId = generateUserId(); + const userSession = { ws, userId, ptyProcess: null, isActive: false }; + userSessions.set(userId, userSession); + + ws.on('message', (message) => handleMessage(userSession, message)); + ws.on('error', (err) => handleError(err, userId)); + ws.on('close', () => handleClose(userId)); +}); + +const messageHandlers = { + message: (session, data) => session.ptyProcess.write(data), + resize: (session, { cols, rows }) => session.ptyProcess.resize(cols, rows), + pause: (session) => session.ptyProcess.pause(), + resume: (session) => session.ptyProcess.resume(), + checkActive: (session, data) => { + if (data === 'force' && session.isActive) { + killPtyProcess(session.userId); + } else { + session.ws.send(session.isActive); + } + }, + command: (session, data) => handleCommand(session.ws, data, session.userId) +}; + +function handleMessage(userSession, message) { + const parsed = parseMessage(message); + if (!parsed) return; + + Object.entries(parsed).forEach(([key, value]) => { + const handler = messageHandlers[key]; + if (handler && (userSession.isActive || key === 'checkActive' || key === 'command')) { + handler(userSession, value); + } + }); +} + +function parseMessage(message) { + try { + return JSON.parse(message); + } catch (e) { + console.error('Failed to parse message:', e); + return null; + } +} + +async function handleCommand(ws, command, userId) { + const userSession = userSessions.get(userId); + + if (userSession && userSession.isActive) { + const result = await killPtyProcess(userId); + if (!result) { + // if terminal is still active, even after we tried to kill it, dont continue and show error + ws.send('unprocessable'); + return; + } + } + + const commandString = command[0].split('\n').join(' '); + const timeout = extractTimeout(commandString); + const sshArgs = extractSshArgs(commandString); + const hereDocContent = extractHereDocContent(commandString); + const options = { + name: 'xterm-color', + cols: 80, + rows: 30, + cwd: process.env.HOME, + }; + + // NOTE: - Initiates a process within the Terminal container + // Establishes an SSH connection to root@coolify with RequestTTY enabled + // Executes the 'docker exec' command to connect to a specific container + // If the user types 'exit', it terminates the container connection and reverts to the server. + const ptyProcess = pty.spawn('ssh', sshArgs.concat(['bash']), options); + userSession.ptyProcess = ptyProcess; + userSession.isActive = true; + ptyProcess.write(hereDocContent + '\n'); + // clear the terminal if the user has clear command + ptyProcess.write('command -v clear >/dev/null 2>&1 && clear\n'); + + ws.send('pty-ready'); + + ptyProcess.onData((data) => ws.send(data)); + + ptyProcess.onExit(({ exitCode, signal }) => { + console.error(`Process exited with code ${exitCode} and signal ${signal}`); + userSession.isActive = false; + }); + + if (timeout) { + setTimeout(async () => { + await killPtyProcess(userId); + }, timeout * 1000); + } +} + +async function handleError(err, userId) { + console.error('WebSocket error:', err); + await killPtyProcess(userId); +} + +async function handleClose(userId) { + await killPtyProcess(userId); + userSessions.delete(userId); +} + +async function killPtyProcess(userId) { + const session = userSessions.get(userId); + if (!session?.ptyProcess) return false; + + return new Promise((resolve) => { + // Loop to ensure terminal is killed before continuing + let killAttempts = 0; + const maxAttempts = 5; + + const attemptKill = () => { + killAttempts++; + + // session.ptyProcess.kill() wont work here because of https://github.com/moby/moby/issues/9098 + // patch with https://github.com/moby/moby/issues/9098#issuecomment-189743947 + session.ptyProcess.write('kill -TERM -$$ && exit\n'); + + setTimeout(() => { + if (!session.isActive || !session.ptyProcess) { + resolve(true); + return; + } + + if (killAttempts < maxAttempts) { + attemptKill(); + } else { + resolve(false); + } + }, 500); + }; + + attemptKill(); + }); +} + +function generateUserId() { + return Math.random().toString(36).substring(2, 11); +} + +function extractTimeout(commandString) { + const timeoutMatch = commandString.match(/timeout (\d+)/); + return timeoutMatch ? parseInt(timeoutMatch[1], 10) : null; +} + +function extractSshArgs(commandString) { + const sshCommandMatch = commandString.match(/ssh (.+?) 'bash -se'/); + let sshArgs = sshCommandMatch ? sshCommandMatch[1].split(' ') : []; + sshArgs = sshArgs.map(arg => arg === 'RequestTTY=no' ? 'RequestTTY=yes' : arg); + if (!sshArgs.includes('RequestTTY=yes')) { + sshArgs.push('-o', 'RequestTTY=yes'); + } + return sshArgs; +} + +function extractHereDocContent(commandString) { + const delimiterMatch = commandString.match(/<< (\S+)/); + const delimiter = delimiterMatch ? delimiterMatch[1] : null; + const escapedDelimiter = delimiter.slice(1).trim().replace(/[/\-\\^$*+?.()|[\]{}]/g, '\\$&'); + const hereDocRegex = new RegExp(`<< \\\\${escapedDelimiter}([\\s\\S\\.]*?)${escapedDelimiter}`); + const hereDocMatch = commandString.match(hereDocRegex); + return hereDocMatch ? hereDocMatch[1] : ''; +} + +server.listen(6002, () => { + console.log('Server listening on port 6002'); +}); diff --git a/docker/dev/Dockerfile b/docker/dev/Dockerfile index f75a0ff1e..63832dc36 100644 --- a/docker/dev/Dockerfile +++ b/docker/dev/Dockerfile @@ -37,6 +37,9 @@ RUN /bin/bash -c "if [[ ${TARGETPLATFORM} == 'linux/arm64' ]]; then \ curl -L https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-arm64 -o /usr/local/bin/cloudflared && chmod +x /usr/local/bin/cloudflared \ ;fi" +COPY --from=minio/mc:RELEASE.2024-09-09T07-53-10Z /usr/bin/mc /usr/bin/mc +RUN chmod +x /usr/bin/mc + RUN { \ echo 'upload_max_filesize=256M'; \ echo 'post_max_size=256M'; \ diff --git a/docker/prod/Dockerfile b/docker/prod/Dockerfile index f46124062..d0cebcbca 100644 --- a/docker/prod/Dockerfile +++ b/docker/prod/Dockerfile @@ -68,3 +68,6 @@ RUN { \ echo 'upload_max_filesize=256M'; \ echo 'post_max_size=256M'; \ } > /etc/php/current_version/cli/conf.d/upload-limits.ini + +COPY --from=minio/mc:RELEASE.2024-09-09T07-53-10Z /usr/bin/mc /usr/bin/mc +RUN chmod +x /usr/bin/mc diff --git a/openapi.yaml b/openapi.yaml index 00d7bff43..ce0503e1f 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -13,7 +13,7 @@ paths: - Applications summary: List description: 'List all applications.' - operationId: 02978e79fc0b54d573b2359f2a1f7d86 + operationId: list-applications responses: '200': description: 'Get all applications.' @@ -36,7 +36,7 @@ paths: - Applications summary: 'Create (Public)' description: 'Create new application based on a public git repository.' - operationId: cb56324ad19693469b4461d3f6065a5b + operationId: create-public-application requestBody: description: 'Application object that needs to be created.' required: true @@ -247,13 +247,13 @@ paths: security: - bearerAuth: [] - /applications/private-gh-app: + /applications/private-github-app: post: tags: - Applications summary: 'Create (Private - GH App)' description: 'Create new application based on a private repository through a Github App.' - operationId: 4d46c84bda4f1a411f6dda15fce4061f + operationId: create-private-github-app-application requestBody: description: 'Application object that needs to be created.' required: true @@ -474,7 +474,7 @@ paths: - Applications summary: 'Create (Private - Deploy Key)' description: 'Create new application based on a private repository through a Deploy Key.' - operationId: e3eaa989ffb05366247a00cdfd551efa + operationId: create-private-deploy-key-application requestBody: description: 'Application object that needs to be created.' required: true @@ -695,7 +695,7 @@ paths: - Applications summary: 'Create (Dockerfile)' description: 'Create new application based on a simple Dockerfile.' - operationId: 2b433ad6f5d259eb7f4f3b5af9913708 + operationId: create-dockerfile-application requestBody: description: 'Application object that needs to be created.' required: true @@ -867,7 +867,7 @@ paths: - Applications summary: 'Create (Docker Image)' description: 'Create new application based on a prebuilt docker image' - operationId: e9a2d6dd9404acf880dc3053f09477fc + operationId: create-dockerimage-application requestBody: description: 'Application object that needs to be created.' required: true @@ -1030,7 +1030,7 @@ paths: - Applications summary: 'Create (Docker Compose)' description: 'Create new application based on a docker-compose file.' - operationId: 3731add8226c2d664455978cac46c242 + operationId: create-dockercompose-application requestBody: description: 'Application object that needs to be created.' required: true @@ -1084,7 +1084,7 @@ paths: - Applications summary: Get description: 'Get application by UUID.' - operationId: 3630b62c28e7358e7f0087c1d8fe1845 + operationId: get-application-by-uuid parameters: - name: uuid @@ -1115,7 +1115,7 @@ paths: - Applications summary: Delete description: 'Delete application by UUID.' - operationId: 1e110b190a1045d34f3e1c61608a8702 + operationId: delete-application-by-uuid parameters: - name: uuid @@ -1156,7 +1156,7 @@ paths: - Applications summary: Update description: 'Update application by UUID.' - operationId: 62a3b1775e8cba5d39a236ebb69830b7 + operationId: update-application-by-uuid requestBody: description: 'Application updated.' required: true @@ -1376,7 +1376,7 @@ paths: - Applications summary: 'List Envs' description: 'List all envs by application UUID.' - operationId: 7c8e0c286870e23294a075cc0584df2f + operationId: list-envs-by-application-uuid parameters: - name: uuid @@ -1409,7 +1409,7 @@ paths: - Applications summary: 'Create Env' description: 'Create env by application UUID.' - operationId: 4699ffbb7d6e58581fd0b0a14f36ffc2 + operationId: create-env-by-application-uuid parameters: - name: uuid @@ -1471,7 +1471,7 @@ paths: - Applications summary: 'Update Env' description: 'Update env by application UUID.' - operationId: 3d70a2d569f395be220b3f09ad36674b + operationId: update-env-by-application-uuid parameters: - name: uuid @@ -1537,7 +1537,7 @@ paths: - Applications summary: 'Update Envs (Bulk)' description: 'Update multiple envs by application UUID.' - operationId: ae96f0f585ed158b2abd2d9ba40f3cf9 + operationId: update-envs-by-application-uuid parameters: - name: uuid @@ -1584,7 +1584,7 @@ paths: - Applications summary: 'Delete Env' description: 'Delete env by UUID.' - operationId: 96097c5cfc7dc0e7a3de229645f630c7 + operationId: delete-env-by-application-uuid parameters: - name: uuid @@ -1626,7 +1626,7 @@ paths: - Applications summary: Start description: 'Start application. `Post` request is also accepted.' - operationId: dc87c2061ab303757a0e061f87900c4c + operationId: start-application-by-uuid parameters: - name: uuid @@ -1675,7 +1675,7 @@ paths: - Applications summary: Stop description: 'Stop application. `Post` request is also accepted.' - operationId: 133ef3c7bd5043901f24bb5002a536eb + operationId: stop-application-by-uuid parameters: - name: uuid @@ -1709,7 +1709,7 @@ paths: - Applications summary: Restart description: 'Restart application. `Post` request is also accepted.' - operationId: b231ae7baab9ef47f0627be820e735bc + operationId: restart-application-by-uuid parameters: - name: uuid @@ -1744,7 +1744,7 @@ paths: - Databases summary: List description: 'List all databases.' - operationId: ecd0ee1e46e4c854c18e6c9daa3d37f3 + operationId: list-databases responses: '200': description: 'Get all databases' @@ -1766,7 +1766,7 @@ paths: - Databases summary: Get description: 'Get database by UUID.' - operationId: b49cb2d3e8f34c4e80cdffd8a201031d + operationId: get-database-by-uuid parameters: - name: uuid @@ -1798,7 +1798,7 @@ paths: - Databases summary: Delete description: 'Delete database by UUID.' - operationId: 20610931b2bae8aba34eee68624ab673 + operationId: delete-database-by-uuid parameters: - name: uuid @@ -1839,7 +1839,7 @@ paths: - Databases summary: Update description: 'Update database by UUID.' - operationId: 5ba459ed390a721711a1708760e9de3b + operationId: update-database-by-uuid parameters: - name: uuid @@ -1989,7 +1989,7 @@ paths: - Databases summary: 'Create (PostgreSQL)' description: 'Create a new PostgreSQL database.' - operationId: 8f7f491ddc46a9fa065b4424512231cd + operationId: create-database-postgresql requestBody: description: 'Database data' required: true @@ -2087,7 +2087,7 @@ paths: - Databases summary: 'Create (Clickhouse)' description: 'Create a new Clickhouse database.' - operationId: a1189fa7f956f238f0e95c9150ff57f6 + operationId: create-database-clickhouse requestBody: description: 'Database data' required: true @@ -2173,7 +2173,7 @@ paths: - Databases summary: 'Create (DragonFly)' description: 'Create a new DragonFly database.' - operationId: e73f7de1c8eee4219e5ec98c4b9b7efe + operationId: create-database-dragonfly requestBody: description: 'Database data' required: true @@ -2256,7 +2256,7 @@ paths: - Databases summary: 'Create (Redis)' description: 'Create a new Redis database.' - operationId: 4d352d13544ee2953fd48ad7b0651098 + operationId: create-database-redis requestBody: description: 'Database data' required: true @@ -2342,7 +2342,7 @@ paths: - Databases summary: 'Create (KeyDB)' description: 'Create a new KeyDB database.' - operationId: b908f3929c371c217d489638e0a21ff6 + operationId: create-database-keydb requestBody: description: 'Database data' required: true @@ -2428,7 +2428,7 @@ paths: - Databases summary: 'Create (MariaDB)' description: 'Create a new MariaDB database.' - operationId: 6bea521ddcd738dcbb5f3783a7308acf + operationId: create-database-mariadb requestBody: description: 'Database data' required: true @@ -2523,7 +2523,7 @@ paths: - Databases summary: 'Create (MySQL)' description: 'Create a new MySQL database.' - operationId: 0a1158cf759c4493cbb1e30024c60623 + operationId: create-database-mysql requestBody: description: 'Database data' required: true @@ -2615,7 +2615,7 @@ paths: - Databases summary: 'Create (MongoDB)' description: 'Create a new MongoDB database.' - operationId: fdba3de84d02519bb37599fea34b115d + operationId: create-database-mongodb requestBody: description: 'Database data' required: true @@ -2701,7 +2701,7 @@ paths: - Databases summary: Start description: 'Start database. `Post` request is also accepted.' - operationId: 4c6eb21e734d411e2b3388578761123d + operationId: start-database-by-uuid parameters: - name: uuid @@ -2735,7 +2735,7 @@ paths: - Databases summary: Stop description: 'Stop database. `Post` request is also accepted.' - operationId: cb6d983c2679aff841c7501ce612a372 + operationId: stop-database-by-uuid parameters: - name: uuid @@ -2769,7 +2769,7 @@ paths: - Databases summary: Restart description: 'Restart database. `Post` request is also accepted.' - operationId: 04c7a5e4752b4a00036addb433f3f218 + operationId: restart-database-by-uuid parameters: - name: uuid @@ -2803,7 +2803,7 @@ paths: - Deployments summary: List description: 'List currently running deployments' - operationId: a2c05736269191ad0d99cadfd4708986 + operationId: list-deployments responses: '200': description: 'Get all currently running deployments.' @@ -2826,12 +2826,12 @@ paths: - Deployments summary: Get description: 'Get deployment by UUID.' - operationId: ccf9856174c115a1430d952ccbd36aea + operationId: get-deployment-by-uuid parameters: - name: uuid in: path - description: 'Deployment Uuid' + description: 'Deployment UUID' required: true schema: type: string @@ -2857,7 +2857,7 @@ paths: - Deployments summary: Deploy description: 'Deploy by tag or uuid. `Post` request also accepted.' - operationId: 700eb6e51f4c9e86d722f600c65ed1d4 + operationId: deploy-by-tag-or-uuid parameters: - name: tag @@ -2879,7 +2879,7 @@ paths: type: boolean responses: '200': - description: "Get deployment(s) Uuid's" + description: "Get deployment(s) UUID's" content: application/json: schema: @@ -2897,7 +2897,7 @@ paths: get: summary: Version description: 'Get Coolify version.' - operationId: 187b37139844731110757711ee71c215 + operationId: version responses: '200': description: 'Returns the version of the application' @@ -2917,7 +2917,7 @@ paths: get: summary: 'Enable API' description: 'Enable API (only with root permissions).' - operationId: 595019bae03d08277def667609779ff3 + operationId: enable-api responses: '200': description: 'Enable API.' @@ -2946,7 +2946,7 @@ paths: get: summary: 'Disable API' description: 'Disable API (only with root permissions).' - operationId: 50e2486a2d196a996b24a284a283bcdb + operationId: disable-api responses: '200': description: 'Disable API.' @@ -2975,7 +2975,7 @@ paths: get: summary: Healthcheck description: 'Healthcheck endpoint.' - operationId: 64db893135e686704bb88c3c238022c1 + operationId: healthcheck responses: '200': description: 'Healthcheck endpoint.' @@ -2993,8 +2993,8 @@ paths: tags: - Projects summary: List - description: 'list projects.' - operationId: 762788f00f2dabb981df9adbc948d3f6 + description: 'List projects.' + operationId: list-projects responses: '200': description: 'Get all projects.' @@ -3016,7 +3016,7 @@ paths: - Projects summary: Create description: 'Create Project.' - operationId: cf067eb7cf18216cda3239329a2eeadb + operationId: create-project requestBody: description: 'Project created.' required: true @@ -3024,7 +3024,7 @@ paths: application/json: schema: properties: - uuid: + name: type: string description: 'The name of the project.' description: @@ -3054,8 +3054,8 @@ paths: tags: - Projects summary: Get - description: 'Get project by Uuid.' - operationId: 63bf8b6a68fbb757f09ab519331f6298 + description: 'Get project by UUID.' + operationId: get-project-by-uuid parameters: - name: uuid @@ -3085,7 +3085,7 @@ paths: - Projects summary: Delete description: 'Delete project by UUID.' - operationId: f668a936f505b4401948c74b6a663029 + operationId: delete-project-by-uuid parameters: - name: uuid @@ -3118,7 +3118,7 @@ paths: - Projects summary: Update description: 'Update Project.' - operationId: 2db343bd6fc14c658cb51a2b73b2f842 + operationId: update-project-by-uuid requestBody: description: 'Project updated.' required: true @@ -3159,7 +3159,7 @@ paths: - Projects summary: Environment description: 'Get environment by name.' - operationId: 7e44845dce5aa47ed7b0daf5595ad2e1 + operationId: get-environment-by-name parameters: - name: uuid @@ -3197,7 +3197,7 @@ paths: - Resources summary: List description: 'Get all resources.' - operationId: c399903694eb1314596832e49f7c66d7 + operationId: list-resources responses: '200': description: 'Get all resources' @@ -3219,7 +3219,7 @@ paths: - 'Private Keys' summary: List description: 'List all private keys.' - operationId: 8a5d8d3ccbbcef54ed0e913a27faea9d + operationId: list-private-keys responses: '200': description: 'Get all private keys.' @@ -3241,7 +3241,7 @@ paths: - 'Private Keys' summary: Create description: 'Create a new private key.' - operationId: eb4780acaa990c594cdbe8ffa80b4fb0 + operationId: create-private-key requestBody: required: true content: @@ -3279,7 +3279,7 @@ paths: - 'Private Keys' summary: Update description: 'Update a private key.' - operationId: 371fd26b8949a070c26a264231fe233f + operationId: update-private-key requestBody: required: true content: @@ -3318,12 +3318,12 @@ paths: - 'Private Keys' summary: Get description: 'Get key by UUID.' - operationId: 2f743a85eb65d5ddb8cd5b362bb3d26a + operationId: get-private-key-by-uuid parameters: - name: uuid in: path - description: 'Private Key Uuid' + description: 'Private Key UUID' required: true schema: type: string @@ -3350,12 +3350,12 @@ paths: - 'Private Keys' summary: Delete description: 'Delete a private key.' - operationId: 8faa0bb399142f0084dfc3e003c42cf6 + operationId: delete-private-key-by-uuid parameters: - name: uuid in: path - description: 'Private Key Uuid' + description: 'Private Key UUID' required: true schema: type: string @@ -3383,7 +3383,7 @@ paths: - Servers summary: List description: 'List all servers.' - operationId: 787448df856cefd2d9a313566be30d34 + operationId: list-servers responses: '200': description: 'Get all servers.' @@ -3405,7 +3405,7 @@ paths: - Servers summary: Create description: 'Create Server.' - operationId: fa44b42490379e428ba5b8747716a8d9 + operationId: create-server requestBody: description: 'Server created.' required: true @@ -3470,12 +3470,12 @@ paths: - Servers summary: Get description: 'Get server by UUID.' - operationId: 5baf04bddb8302c7e07f5b4c41aad10c + operationId: get-server-by-uuid parameters: - name: uuid in: path - description: "Server's Uuid" + description: "Server's UUID" required: true schema: type: string @@ -3500,7 +3500,7 @@ paths: - Servers summary: Delete description: 'Delete server by UUID.' - operationId: 0231fe0134f0306b21f006ce51b0a3dc + operationId: delete-server-by-uuid parameters: - name: uuid @@ -3533,7 +3533,7 @@ paths: - Servers summary: Update description: 'Update Server.' - operationId: 41bbdaf79eb1938592494fc5494442a0 + operationId: update-server-by-uuid requestBody: description: 'Server updated.' required: true @@ -3590,12 +3590,12 @@ paths: - Servers summary: Resources description: 'Get resources by server.' - operationId: cef26c059941b44fbd8de3a7a58c10a5 + operationId: get-resources-by-server-uuid parameters: - name: uuid in: path - description: "Server's Uuid" + description: "Server's UUID" required: true schema: type: string @@ -3622,12 +3622,12 @@ paths: - Servers summary: Domains description: 'Get domains by server.' - operationId: 1ee227755be848d572f412272f53dd93 + operationId: get-domains-by-server-uuid parameters: - name: uuid in: path - description: "Server's Uuid" + description: "Server's UUID" required: true schema: type: string @@ -3654,7 +3654,7 @@ paths: - Servers summary: Validate description: 'Validate server by UUID.' - operationId: a543a12ef2cbc7a3dd22c3dbe6cbee89 + operationId: validate-server-by-uuid parameters: - name: uuid @@ -3687,7 +3687,7 @@ paths: - Services summary: List description: 'List all services.' - operationId: 5d014ac25d33391b8f4c2316060ba452 + operationId: list-services responses: '200': description: 'Get all services' @@ -3709,7 +3709,7 @@ paths: - Services summary: Create description: 'Create a one-click service' - operationId: 3d6cbfb54d919b53ba3984a113e837d7 + operationId: create-service requestBody: required: true content: @@ -3773,7 +3773,7 @@ paths: - Services summary: Get description: 'Get service by UUID.' - operationId: 895d39ee2cb3994285de57256c2d428d + operationId: get-service-by-uuid parameters: - name: uuid @@ -3784,7 +3784,7 @@ paths: type: string responses: '200': - description: 'Get a service by Uuid.' + description: 'Get a service by UUID.' content: application/json: schema: @@ -3803,7 +3803,7 @@ paths: - Services summary: Delete description: 'Delete service by UUID.' - operationId: 6e1a61e4fddaa9d95bb9fc66dfaf0442 + operationId: delete-service-by-uuid parameters: - name: uuid @@ -3814,7 +3814,7 @@ paths: type: string responses: '200': - description: 'Delete a service by Uuid' + description: 'Delete a service by UUID' content: application/json: schema: @@ -3830,13 +3830,263 @@ paths: security: - bearerAuth: [] + '/services/{uuid}/envs': + get: + tags: + - Services + summary: 'List Envs' + description: 'List all envs by service UUID.' + operationId: list-envs-by-service-uuid + parameters: + - + name: uuid + in: path + description: 'UUID of the service.' + required: true + schema: + type: string + format: uuid + responses: + '200': + description: 'All environment variables by service UUID.' + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/EnvironmentVariable' + '401': + $ref: '#/components/responses/401' + '400': + $ref: '#/components/responses/400' + '404': + $ref: '#/components/responses/404' + security: + - + bearerAuth: [] + post: + tags: + - Services + summary: 'Create Env' + description: 'Create env by service UUID.' + operationId: create-env-by-service-uuid + parameters: + - + name: uuid + in: path + description: 'UUID of the service.' + required: true + schema: + type: string + format: uuid + requestBody: + description: 'Env created.' + required: true + content: + application/json: + schema: + properties: + key: + type: string + description: 'The key of the environment variable.' + value: + type: string + description: 'The value of the environment variable.' + is_preview: + type: boolean + description: 'The flag to indicate if the environment variable is used in preview deployments.' + is_build_time: + type: boolean + description: 'The flag to indicate if the environment variable is used in build time.' + is_literal: + type: boolean + description: 'The flag to indicate if the environment variable is a literal, nothing espaced.' + is_multiline: + type: boolean + description: 'The flag to indicate if the environment variable is multiline.' + is_shown_once: + type: boolean + description: "The flag to indicate if the environment variable's value is shown on the UI." + type: object + responses: + '201': + description: 'Environment variable created.' + content: + application/json: + schema: + properties: + uuid: { type: string, example: nc0k04gk8g0cgsk440g0koko } + type: object + '401': + $ref: '#/components/responses/401' + '400': + $ref: '#/components/responses/400' + '404': + $ref: '#/components/responses/404' + security: + - + bearerAuth: [] + patch: + tags: + - Services + summary: 'Update Env' + description: 'Update env by service UUID.' + operationId: update-env-by-service-uuid + parameters: + - + name: uuid + in: path + description: 'UUID of the service.' + required: true + schema: + type: string + format: uuid + requestBody: + description: 'Env updated.' + required: true + content: + application/json: + schema: + required: + - key + - value + properties: + key: + type: string + description: 'The key of the environment variable.' + value: + type: string + description: 'The value of the environment variable.' + is_preview: + type: boolean + description: 'The flag to indicate if the environment variable is used in preview deployments.' + is_build_time: + type: boolean + description: 'The flag to indicate if the environment variable is used in build time.' + is_literal: + type: boolean + description: 'The flag to indicate if the environment variable is a literal, nothing espaced.' + is_multiline: + type: boolean + description: 'The flag to indicate if the environment variable is multiline.' + is_shown_once: + type: boolean + description: "The flag to indicate if the environment variable's value is shown on the UI." + type: object + responses: + '201': + description: 'Environment variable updated.' + content: + application/json: + schema: + properties: + message: { type: string, example: 'Environment variable updated.' } + type: object + '401': + $ref: '#/components/responses/401' + '400': + $ref: '#/components/responses/400' + '404': + $ref: '#/components/responses/404' + security: + - + bearerAuth: [] + '/services/{uuid}/envs/bulk': + patch: + tags: + - Services + summary: 'Update Envs (Bulk)' + description: 'Update multiple envs by service UUID.' + operationId: update-envs-by-service-uuid + parameters: + - + name: uuid + in: path + description: 'UUID of the service.' + required: true + schema: + type: string + format: uuid + requestBody: + description: 'Bulk envs updated.' + required: true + content: + application/json: + schema: + required: + - data + properties: + data: + type: array + items: { properties: { key: { type: string, description: 'The key of the environment variable.' }, value: { type: string, description: 'The value of the environment variable.' }, is_preview: { type: boolean, description: 'The flag to indicate if the environment variable is used in preview deployments.' }, is_build_time: { type: boolean, description: 'The flag to indicate if the environment variable is used in build time.' }, is_literal: { type: boolean, description: 'The flag to indicate if the environment variable is a literal, nothing espaced.' }, is_multiline: { type: boolean, description: 'The flag to indicate if the environment variable is multiline.' }, is_shown_once: { type: boolean, description: "The flag to indicate if the environment variable's value is shown on the UI." } }, type: object } + type: object + responses: + '201': + description: 'Environment variables updated.' + content: + application/json: + schema: + properties: + message: { type: string, example: 'Environment variables updated.' } + type: object + '401': + $ref: '#/components/responses/401' + '400': + $ref: '#/components/responses/400' + '404': + $ref: '#/components/responses/404' + security: + - + bearerAuth: [] + '/services/{uuid}/envs/{env_uuid}': + delete: + tags: + - Services + summary: 'Delete Env' + description: 'Delete env by UUID.' + operationId: delete-env-by-service-uuid + parameters: + - + name: uuid + in: path + description: 'UUID of the service.' + required: true + schema: + type: string + format: uuid + - + name: env_uuid + in: path + description: 'UUID of the environment variable.' + required: true + schema: + type: string + format: uuid + responses: + '200': + description: 'Environment variable deleted.' + content: + application/json: + schema: + properties: + message: { type: string, example: 'Environment variable deleted.' } + type: object + '401': + $ref: '#/components/responses/401' + '400': + $ref: '#/components/responses/400' + '404': + $ref: '#/components/responses/404' + security: + - + bearerAuth: [] '/services/{uuid}/start': get: tags: - Services summary: Start description: 'Start service. `Post` request is also accepted.' - operationId: d2ddd9c028d123fbdec830dc4b25b4cb + operationId: start-service-by-uuid parameters: - name: uuid @@ -3870,7 +4120,7 @@ paths: - Services summary: Stop description: 'Stop service. `Post` request is also accepted.' - operationId: 87399d34758ce16830740c68626614db + operationId: stop-service-by-uuid parameters: - name: uuid @@ -3904,7 +4154,7 @@ paths: - Services summary: Restart description: 'Restart service. `Post` request is also accepted.' - operationId: 836645faa615b75052759dae78639469 + operationId: restart-service-by-uuid parameters: - name: uuid @@ -3938,7 +4188,7 @@ paths: - Teams summary: List description: 'Get all teams.' - operationId: f9c530b5b25df9601cb87d6a58646f0a + operationId: list-teams responses: '200': description: 'List of teams.' @@ -3961,7 +4211,7 @@ paths: - Teams summary: Get description: 'Get team by TeamId.' - operationId: ac57ff546c002032cef44602c46a4e76 + operationId: get-team-by-id parameters: - name: id @@ -3992,7 +4242,7 @@ paths: - Teams summary: Members description: 'Get members by TeamId.' - operationId: 7858f5a45d9ea55184c182852a7f0f6c + operationId: get-members-by-team-id parameters: - name: id @@ -4025,7 +4275,7 @@ paths: - Teams summary: 'Authenticated Team' description: 'Get currently authenticated team.' - operationId: 6a4ec9fed1aad7b0b38356c47d7ac509 + operationId: get-current-team responses: '200': description: 'Current Team.' @@ -4046,7 +4296,7 @@ paths: - Teams summary: 'Authenticated Team Members' description: 'Get currently authenticated team members.' - operationId: 97e636a5796dbe71afb0bbcf1eec6e41 + operationId: get-current-team-members responses: '200': description: 'Currently authenticated team members.' @@ -4480,6 +4730,8 @@ components: type: string name: type: string + description: + type: string environments: description: 'The environments of the project.' type: array @@ -4523,14 +4775,14 @@ components: properties: id: type: integer - cleanup_after_percentage: - type: integer concurrent_builds: type: integer dynamic_timeout: type: integer force_disabled: type: boolean + force_server_cleanup: + type: boolean is_build_server: type: boolean is_cloudflare_tunnel: @@ -4577,6 +4829,10 @@ components: type: integer metrics_token: type: string + docker_cleanup_frequency: + type: string + docker_cleanup_threshold: + type: integer server_id: type: integer wildcard_domain: @@ -4613,6 +4869,9 @@ components: docker_compose: type: string description: 'The docker-compose.yml file that is parsed and modified by Coolify.' + destination_type: + type: string + description: 'Destination type.' destination_id: type: integer description: 'The unique identifier of the destination where the service is running.' diff --git a/other/logos/glueops.webp b/other/logos/glueops.webp new file mode 100644 index 000000000..d5acda999 Binary files /dev/null and b/other/logos/glueops.webp differ diff --git a/other/logos/hostinger.svg b/other/logos/hostinger.svg new file mode 100644 index 000000000..42ec702ab --- /dev/null +++ b/other/logos/hostinger.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/other/logos/jobscollider.svg b/other/logos/jobscollider.svg new file mode 100644 index 000000000..a57492f41 --- /dev/null +++ b/other/logos/jobscollider.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/other/logos/juxtdigital.png b/other/logos/juxtdigital.png new file mode 100644 index 000000000..92257bd35 Binary files /dev/null and b/other/logos/juxtdigital.png differ diff --git a/other/logos/massivegrid.svg b/other/logos/massivegrid.svg new file mode 100644 index 000000000..09f7ba00f --- /dev/null +++ b/other/logos/massivegrid.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + diff --git a/other/logos/saasykit.png b/other/logos/saasykit.png new file mode 100644 index 000000000..27013eadd Binary files /dev/null and b/other/logos/saasykit.png differ diff --git a/other/logos/saasykit.webp b/other/logos/saasykit.webp new file mode 100644 index 000000000..0d1085e6c Binary files /dev/null and b/other/logos/saasykit.webp differ diff --git a/other/logos/ubicloud.svg b/other/logos/ubicloud.svg new file mode 100644 index 000000000..3613858ca --- /dev/null +++ b/other/logos/ubicloud.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/other/nightly/.env.development.example b/other/nightly/.env.development.example new file mode 100644 index 000000000..3023a21a6 --- /dev/null +++ b/other/nightly/.env.development.example @@ -0,0 +1,38 @@ +# Coolify Configuration +APP_ENV=local +APP_NAME="Coolify Development" +APP_ID=development +APP_KEY= +APP_URL=http://localhost +APP_PORT=8000 +APP_DEBUG=true +SSH_MUX_ENABLED=false + +# PostgreSQL Database Configuration +DB_DATABASE=coolify +DB_USERNAME=coolify +DB_PASSWORD=password +DB_HOST=host.docker.internal +DB_PORT=5432 + +# Ray Configuration +# Set to true to enable Ray +RAY_ENABLED=false +# Set custom ray port +RAY_PORT= + +# Clockwork Configuration +CLOCKWORK_ENABLED=false +CLOCKWORK_QUEUE_COLLECT=true + +# Enable Laravel Telescope for debugging +TELESCOPE_ENABLED=false + +# Selenium Driver URL for Dusk +DUSK_DRIVER_URL=http://selenium:4444 + +# Special Keys for Andras +# For cache purging +BUNNY_API_KEY= +# For asset uploads +BUNNY_STORAGE_API_KEY= diff --git a/other/nightly/.env.production b/other/nightly/.env.production new file mode 100644 index 000000000..099ec7c25 --- /dev/null +++ b/other/nightly/.env.production @@ -0,0 +1,16 @@ +# Coolify Configuration +APP_ID= +APP_NAME=Coolify +APP_KEY= + +# PostgreSQL Database Configuration +DB_USERNAME=coolify +DB_PASSWORD= + +# Redis Configuration +REDIS_PASSWORD= + +# Pusher Configuration +PUSHER_APP_ID= +PUSHER_APP_KEY= +PUSHER_APP_SECRET= diff --git a/other/nightly/docker-compose.prod.yml b/other/nightly/docker-compose.prod.yml new file mode 100644 index 000000000..3eb270a2a --- /dev/null +++ b/other/nightly/docker-compose.prod.yml @@ -0,0 +1,139 @@ +services: + coolify: + image: "ghcr.io/coollabsio/coolify:${LATEST_IMAGE:-latest}" + volumes: + - type: bind + source: /data/coolify/source/.env + target: /var/www/html/.env + read_only: true + - /data/coolify/ssh:/var/www/html/storage/app/ssh + - /data/coolify/applications:/var/www/html/storage/app/applications + - /data/coolify/databases:/var/www/html/storage/app/databases + - /data/coolify/services:/var/www/html/storage/app/services + - /data/coolify/backups:/var/www/html/storage/app/backups + - /data/coolify/webhooks-during-maintenance:/var/www/html/storage/app/webhooks-during-maintenance + environment: + - APP_ENV=production + - APP_NAME + - APP_ID + - APP_KEY + - APP_URL + - APP_DEBUG + - DB_DATABASE + - DB_USERNAME + - DB_PASSWORD + - DB_HOST + - DB_PORT + - DB_CONNECTION + - QUEUE_CONNECTION + - REDIS_HOST + - REDIS_PASSWORD + - HORIZON_BALANCE + - HORIZON_MAX_PROCESSES + - HORIZON_BALANCE_MAX_SHIFT + - HORIZON_BALANCE_COOLDOWN + - SSL_MODE=off + - PHP_MEMORY_LIMIT + - PHP_PM_CONTROL=dynamic + - PHP_PM_START_SERVERS=1 + - PHP_PM_MIN_SPARE_SERVERS=1 + - PHP_PM_MAX_SPARE_SERVERS=10 + - PUSHER_HOST + - PUSHER_BACKEND_HOST + - PUSHER_PORT + - PUSHER_BACKEND_PORT + - PUSHER_SCHEME + - PUSHER_APP_ID + - PUSHER_APP_KEY + - PUSHER_APP_SECRET + - AUTOUPDATE + - SELF_HOSTED + - SSH_MUX_ENABLED + - SSH_MUX_PERSIST_TIME + - FEEDBACK_DISCORD_WEBHOOK + - WAITLIST + - SUBSCRIPTION_PROVIDER + - STRIPE_API_KEY + - STRIPE_WEBHOOK_SECRET + - STRIPE_PRICE_ID_BASIC_MONTHLY + - STRIPE_PRICE_ID_BASIC_YEARLY + - STRIPE_PRICE_ID_PRO_MONTHLY + - STRIPE_PRICE_ID_PRO_YEARLY + - STRIPE_PRICE_ID_ULTIMATE_MONTHLY + - STRIPE_PRICE_ID_ULTIMATE_YEARLY + - STRIPE_PRICE_ID_DYNAMIC_MONTHLY + - STRIPE_PRICE_ID_DYNAMIC_YEARLY + - STRIPE_PRICE_ID_BASIC_MONTHLY_OLD + - STRIPE_PRICE_ID_BASIC_YEARLY_OLD + - STRIPE_PRICE_ID_PRO_MONTHLY_OLD + - STRIPE_PRICE_ID_PRO_YEARLY_OLD + - STRIPE_PRICE_ID_ULTIMATE_MONTHLY_OLD + - STRIPE_PRICE_ID_ULTIMATE_YEARLY_OLD + - STRIPE_EXCLUDED_PLANS + ports: + - "${APP_PORT:-8000}:80" + expose: + - "${APP_PORT:-8000}" + healthcheck: + test: curl --fail http://127.0.0.1:80/api/health || exit 1 + interval: 5s + retries: 10 + timeout: 2s + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + soketi: + condition: service_healthy + postgres: + volumes: + - coolify-db:/var/lib/postgresql/data + environment: + POSTGRES_USER: "${DB_USERNAME}" + POSTGRES_PASSWORD: "${DB_PASSWORD}" + POSTGRES_DB: "${DB_DATABASE:-coolify}" + healthcheck: + test: [ "CMD-SHELL", "pg_isready -U ${DB_USERNAME}", "-d", "${DB_DATABASE:-coolify}" ] + interval: 5s + retries: 10 + timeout: 2s + redis: + command: redis-server --save 20 1 --loglevel warning --requirepass ${REDIS_PASSWORD} + environment: + REDIS_PASSWORD: "${REDIS_PASSWORD}" + volumes: + - coolify-redis:/data + healthcheck: + test: redis-cli ping + interval: 5s + retries: 10 + timeout: 2s + soketi: + image: 'ghcr.io/coollabsio/coolify-realtime:1.0.1' + ports: + - "${SOKETI_PORT:-6001}:6001" + - "6002:6002" + volumes: + - /data/coolify/ssh:/var/www/html/storage/app/ssh + environment: + APP_NAME: "${APP_NAME:-Coolify}" + SOKETI_DEBUG: "${SOKETI_DEBUG:-false}" + SOKETI_DEFAULT_APP_ID: "${PUSHER_APP_ID}" + SOKETI_DEFAULT_APP_KEY: "${PUSHER_APP_KEY}" + SOKETI_DEFAULT_APP_SECRET: "${PUSHER_APP_SECRET}" + healthcheck: + test: [ "CMD-SHELL", "wget -qO- http://127.0.0.1:6001/ready && wget -qO- http://127.0.0.1:6002/ready || exit 1" ] + interval: 5s + retries: 10 + timeout: 2s + +volumes: + coolify-db: + name: coolify-db + coolify-redis: + name: coolify-redis + +networks: + coolify: + external: true diff --git a/other/nightly/docker-compose.windows.yml b/other/nightly/docker-compose.windows.yml new file mode 100644 index 000000000..ef2de82e9 --- /dev/null +++ b/other/nightly/docker-compose.windows.yml @@ -0,0 +1,133 @@ +services: + coolify-testing-host: + init: true + image: "ghcr.io/coollabsio/coolify-testing-host:latest" + pull_policy: always + container_name: coolify-testing-host + volumes: + - //var/run/docker.sock://var/run/docker.sock + - ./:/data/coolify + coolify: + image: "ghcr.io/coollabsio/coolify:latest" + pull_policy: always + container_name: coolify + restart: always + working_dir: /var/www/html + extra_hosts: + - 'host.docker.internal:host-gateway' + volumes: + - type: bind + source: .env + target: /var/www/html/.env + read_only: true + - ./ssh:/var/www/html/storage/app/ssh + - ./applications:/var/www/html/storage/app/applications + - ./databases:/var/www/html/storage/app/databases + - ./services:/var/www/html/storage/app/services + - ./backups:/var/www/html/storage/app/backups + - ./webhooks-during-maintenance:/var/www/html/storage/app/webhooks-during-maintenance + env_file: + - .env + environment: + - APP_ID + - APP_ENV=production + - APP_NAME + - APP_KEY + - DB_PASSWORD + - REDIS_PASSWORD + - SSL_MODE=off + - PHP_PM_CONTROL=dynamic + - PHP_PM_START_SERVERS=1 + - PHP_PM_MIN_SPARE_SERVERS=1 + - PHP_PM_MAX_SPARE_SERVERS=10 + - PUSHER_APP_ID + - PUSHER_APP_KEY + - PUSHER_APP_SECRET + - AUTOUPDATE=true + - SELF_HOSTED=true + - SSH_MUX_ENABLED=false + - IS_WINDOWS_DOCKER_DESKTOP=true + ports: + - "${APP_PORT:-8000}:80" + expose: + - "${APP_PORT:-8000}" + healthcheck: + test: curl --fail http://localhost:80/api/health || exit 1 + interval: 5s + retries: 10 + timeout: 2s + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + postgres: + image: postgres:15-alpine + pull_policy: always + container_name: coolify-db + restart: always + env_file: + - .env + volumes: + - coolify-db:/var/lib/postgresql/data + environment: + POSTGRES_USER: "${DB_USERNAME}" + POSTGRES_PASSWORD: "${DB_PASSWORD}" + POSTGRES_DB: "${DB_DATABASE:-coolify}" + healthcheck: + test: + [ + "CMD-SHELL", + "pg_isready -U ${DB_USERNAME}", + "-d", + "${DB_DATABASE:-coolify}" + ] + interval: 5s + retries: 10 + timeout: 2s + redis: + image: redis:alpine + pull_policy: always + container_name: coolify-redis + restart: always + command: redis-server --save 20 1 --loglevel warning --requirepass ${REDIS_PASSWORD} + env_file: + - .env + environment: + REDIS_PASSWORD: "${REDIS_PASSWORD}" + volumes: + - coolify-redis:/data + healthcheck: + test: redis-cli ping + interval: 5s + retries: 10 + timeout: 2s + soketi: + image: 'ghcr.io/coollabsio/coolify-realtime:1.0.0' + pull_policy: always + container_name: coolify-realtime + restart: always + env_file: + - .env + ports: + - "${SOKETI_PORT:-6001}:6001" + - "6002:6002" + volumes: + - ./ssh:/var/www/html/storage/app/ssh + environment: + APP_NAME: "${APP_NAME:-Coolify}" + SOKETI_DEBUG: "${SOKETI_DEBUG:-false}" + SOKETI_DEFAULT_APP_ID: "${PUSHER_APP_ID}" + SOKETI_DEFAULT_APP_KEY: "${PUSHER_APP_KEY}" + SOKETI_DEFAULT_APP_SECRET: "${PUSHER_APP_SECRET}" + healthcheck: + test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:6001/ready && wget -qO- http://127.0.0.1:6002/ready || exit 1"] + interval: 5s + retries: 10 + timeout: 2s + +volumes: + coolify-db: + name: coolify-db + coolify-redis: + name: coolify-redis diff --git a/other/nightly/docker-compose.yml b/other/nightly/docker-compose.yml new file mode 100644 index 000000000..68d0f0744 --- /dev/null +++ b/other/nightly/docker-compose.yml @@ -0,0 +1,37 @@ +services: + coolify: + container_name: coolify + restart: always + working_dir: /var/www/html + extra_hosts: + - 'host.docker.internal:host-gateway' + networks: + - coolify + depends_on: + - postgres + - redis + - soketi + postgres: + image: postgres:15-alpine + container_name: coolify-db + restart: always + networks: + - coolify + redis: + image: redis:alpine + container_name: coolify-redis + restart: always + networks: + - coolify + soketi: + container_name: coolify-realtime + extra_hosts: + - 'host.docker.internal:host-gateway' + restart: always + networks: + - coolify +networks: + coolify: + name: coolify + driver: bridge + external: false diff --git a/other/nightly/install.sh b/other/nightly/install.sh new file mode 100755 index 000000000..09613e12b --- /dev/null +++ b/other/nightly/install.sh @@ -0,0 +1,509 @@ +#!/bin/bash +## Do not modify this file. You will lose the ability to install and auto-update! + +set -e # Exit immediately if a command exits with a non-zero status +## $1 could be empty, so we need to disable this check +#set -u # Treat unset variables as an error and exit +set -o pipefail # Cause a pipeline to return the status of the last command that exited with a non-zero status +CDN="https://cdn.coollabs.io/coolify-nightly" +DATE=$(date +"%Y%m%d-%H%M%S") + +VERSION="1.5" +DOCKER_VERSION="26.0" + +mkdir -p /data/coolify/{source,ssh,applications,databases,backups,services,proxy,webhooks-during-maintenance,metrics,logs} +mkdir -p /data/coolify/ssh/{keys,mux} +mkdir -p /data/coolify/proxy/dynamic + +chown -R 9999:root /data/coolify +chmod -R 700 /data/coolify + +INSTALLATION_LOG_WITH_DATE="/data/coolify/source/installation-${DATE}.log" + +exec > >(tee -a $INSTALLATION_LOG_WITH_DATE) 2>&1 + +getAJoke() { + JOKES=$(curl -s --max-time 2 https://v2.jokeapi.dev/joke/Programming?format=txt&type=single&amount=1 || true) + if [ "$JOKES" != "" ]; then + echo -e " - Until then, here's a joke for you:\n" + echo -e "$JOKES\n" + fi +} +OS_TYPE=$(grep -w "ID" /etc/os-release | cut -d "=" -f 2 | tr -d '"') +ENV_FILE="/data/coolify/source/.env" + +# Check if the OS is manjaro, if so, change it to arch +if [ "$OS_TYPE" = "manjaro" ] || [ "$OS_TYPE" = "manjaro-arm" ]; then + OS_TYPE="arch" +fi + +# Check if the OS is popOS, if so, change it to ubuntu +if [ "$OS_TYPE" = "pop" ]; then + OS_TYPE="ubuntu" +fi + +# Check if the OS is linuxmint, if so, change it to ubuntu +if [ "$OS_TYPE" = "linuxmint" ]; then + OS_TYPE="ubuntu" +fi + +#Check if the OS is zorin, if so, change it to ubuntu +if [ "$OS_TYPE" = "zorin" ]; then + OS_TYPE="ubuntu" +fi + +if [ "$OS_TYPE" = "arch" ] || [ "$OS_TYPE" = "archarm" ]; then + OS_VERSION="rolling" +else + OS_VERSION=$(grep -w "VERSION_ID" /etc/os-release | cut -d "=" -f 2 | tr -d '"') +fi + +# Install xargs on Amazon Linux 2023 - lol +if [ "$OS_TYPE" = 'amzn' ]; then + dnf install -y findutils >/dev/null +fi + +LATEST_VERSION=$(curl --silent $CDN/versions.json | grep -i version | xargs | awk '{print $2}' | tr -d ',') +LATEST_HELPER_VERSION=$(curl --silent $CDN/versions.json | grep -i version | xargs | awk '{print $6}' | tr -d ',') +LATEST_REALTIME_VERSION=$(curl --silent $CDN/versions.json | grep -i version | xargs | awk '{print $8}' | tr -d ',') + +if [ -z "$LATEST_HELPER_VERSION" ]; then + LATEST_HELPER_VERSION=latest +fi + +if [ -z "$LATEST_REALTIME_VERSION" ]; then + LATEST_REALTIME_VERSION=latest +fi + + +if [ $EUID != 0 ]; then + echo "Please run as root" + exit +fi + +case "$OS_TYPE" in +arch | ubuntu | debian | raspbian | centos | fedora | rhel | ol | rocky | sles | opensuse-leap | opensuse-tumbleweed | almalinux | amzn | alpine) ;; +*) + echo "This script only supports Debian, Redhat, Arch Linux, Alpine Linux, or SLES based operating systems for now." + exit + ;; +esac + +# Overwrite LATEST_VERSION if user pass a version number +if [ "$1" != "" ]; then + LATEST_VERSION=$1 + LATEST_VERSION="${LATEST_VERSION,,}" + LATEST_VERSION="${LATEST_VERSION#v}" +fi + +echo -e "\033[0;35m" +cat << "EOF" + _____ _ _ __ + / ____| | (_)/ _| + | | ___ ___ | |_| |_ _ _ + | | / _ \ / _ \| | | _| | | | + | |___| (_) | (_) | | | | | |_| | + \_____\___/ \___/|_|_|_| \__, | + __/ | + |___/ +EOF +echo -e "\033[0m" +echo -e "Welcome to Coolify Installer!" +echo -e "This script will install everything for you. Sit back and relax." +echo -e "Source code: https://github.com/coollabsio/coolify/blob/main/scripts/install.sh\n" +echo -e "---------------------------------------------" +echo "| Operating System | $OS_TYPE $OS_VERSION" +echo "| Docker | $DOCKER_VERSION" +echo "| Coolify | $LATEST_VERSION" +echo "| Helper | $LATEST_HELPER_VERSION" +echo "| Realtime | $LATEST_REALTIME_VERSION" +echo -e "---------------------------------------------\n" +echo -e "1. Installing required packages (curl, wget, git, jq). " + +case "$OS_TYPE" in +arch) + pacman -Sy --noconfirm --needed curl wget git jq >/dev/null || true + ;; +alpine) + sed -i '/^#.*\/community/s/^#//' /etc/apk/repositories + apk update >/dev/null + apk add curl wget git jq >/dev/null + ;; +ubuntu | debian | raspbian) + apt-get update -y >/dev/null + apt-get install -y curl wget git jq >/dev/null + ;; +centos | fedora | rhel | ol | rocky | almalinux | amzn) + if [ "$OS_TYPE" = "amzn" ]; then + dnf install -y wget git jq >/dev/null + else + if ! command -v dnf >/dev/null; then + yum install -y dnf >/dev/null + fi + if ! command -v curl >/dev/null; then + dnf install -y curl >/dev/null + fi + dnf install -y wget git jq >/dev/null + fi + ;; +sles | opensuse-leap | opensuse-tumbleweed) + zypper refresh >/dev/null + zypper install -y curl wget git jq >/dev/null + ;; +*) + echo "This script only supports Debian, Redhat, Arch Linux, or SLES based operating systems for now." + exit + ;; +esac + + + +echo -e "2. Check OpenSSH server configuration. " + +# Detect OpenSSH server +SSH_DETECTED=false +if [ -x "$(command -v systemctl)" ]; then + if systemctl status sshd >/dev/null 2>&1; then + echo " - OpenSSH server is installed." + SSH_DETECTED=true + elif systemctl status ssh >/dev/null 2>&1; then + echo " - OpenSSH server is installed." + SSH_DETECTED=true + fi +elif [ -x "$(command -v service)" ]; then + if service sshd status >/dev/null 2>&1; then + echo " - OpenSSH server is installed." + SSH_DETECTED=true + elif service ssh status >/dev/null 2>&1; then + echo " - OpenSSH server is installed." + SSH_DETECTED=true + fi +fi +if [ "$SSH_DETECTED" = "false" ]; then + echo "###############################################################################" + echo "WARNING: Could not detect if OpenSSH server is installed and running - this does not mean that it is not installed, just that we could not detect it." + echo -e "Please make sure it is set, otherwise Coolify cannot connect to the host system. \n" + echo "###############################################################################" +fi + +# Detect SSH PermitRootLogin +SSH_PERMIT_ROOT_LOGIN=$(sshd -T | grep -i "permitrootlogin" | awk '{print $2}') || true +if [ "$SSH_PERMIT_ROOT_LOGIN" = "yes" ] || [ "$SSH_PERMIT_ROOT_LOGIN" = "without-password" ] || [ "$SSH_PERMIT_ROOT_LOGIN" = "prohibit-password" ]; then + echo " - SSH PermitRootLogin is enabled." +else + echo " - SSH PermitRootLogin is disabled." + echo " If you have problems with SSH, please read this: https://coolify.io/docs/knowledge-base/server/openssh" +fi + +# Detect if docker is installed via snap +if [ -x "$(command -v snap)" ]; then + SNAP_DOCKER_INSTALLED=$(snap list docker >/dev/null 2>&1 && echo "true" || echo "false") + if [ "$SNAP_DOCKER_INSTALLED" = "true" ]; then + echo " - Docker is installed via snap." + echo " Please note that Coolify does not support Docker installed via snap." + echo " Please remove Docker with snap (snap remove docker) and reexecute this script." + exit 1 + fi +fi + +echo -e "3. Check Docker Installation. " +if ! [ -x "$(command -v docker)" ]; then + echo " - Docker is not installed. Installing Docker. It may take a while." + getAJoke + case "$OS_TYPE" in + "almalinux") + dnf config-manager --add-repo=https://download.docker.com/linux/centos/docker-ce.repo >/dev/null 2>&1 + dnf install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin >/dev/null 2>&1 + if ! [ -x "$(command -v docker)" ]; then + echo " - Docker could not be installed automatically. Please visit https://docs.docker.com/engine/install/ and install Docker manually to continue." + exit 1 + fi + systemctl start docker >/dev/null 2>&1 + systemctl enable docker >/dev/null 2>&1 + ;; + "alpine") + apk add docker docker-cli-compose >/dev/null 2>&1 + rc-update add docker default >/dev/null 2>&1 + service docker start >/dev/null 2>&1 + if ! [ -x "$(command -v docker)" ]; then + echo " - Failed to install Docker with apk. Try to install it manually." + echo " Please visit https://wiki.alpinelinux.org/wiki/Docker for more information." + exit 1 + fi + ;; + "arch") + pacman -Sy docker docker-compose --noconfirm >/dev/null 2>&1 + systemctl enable docker.service >/dev/null 2>&1 + if ! [ -x "$(command -v docker)" ]; then + echo " - Failed to install Docker with pacman. Try to install it manually." + echo " Please visit https://wiki.archlinux.org/title/docker for more information." + exit 1 + fi + ;; + "amzn") + dnf install docker -y >/dev/null 2>&1 + DOCKER_CONFIG=${DOCKER_CONFIG:-/usr/local/lib/docker} + mkdir -p $DOCKER_CONFIG/cli-plugins >/dev/null 2>&1 + curl -sL https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m) -o $DOCKER_CONFIG/cli-plugins/docker-compose >/dev/null 2>&1 + chmod +x $DOCKER_CONFIG/cli-plugins/docker-compose >/dev/null 2>&1 + systemctl start docker >/dev/null 2>&1 + systemctl enable docker >/dev/null 2>&1 + if ! [ -x "$(command -v docker)" ]; then + echo " - Failed to install Docker with dnf. Try to install it manually." + echo " Please visit https://www.cyberciti.biz/faq/how-to-install-docker-on-amazon-linux-2/ for more information." + exit 1 + fi + ;; + *) + curl -s https://releases.rancher.com/install-docker/${DOCKER_VERSION}.sh | sh >/dev/null 2>&1 + if ! [ -x "$(command -v docker)" ]; then + curl -s https://get.docker.com | sh -s -- --version ${DOCKER_VERSION} >/dev/null 2>&1 + if ! [ -x "$(command -v docker)" ]; then + echo " - Docker installation failed." + echo " Maybe your OS is not supported?" + echo " - Please visit https://docs.docker.com/engine/install/ and install Docker manually to continue." + exit 1 + fi + fi + esac + echo " - Docker installed successfully." +else + echo " - Docker is installed." +fi + +echo -e "4. Check Docker Configuration. " +mkdir -p /etc/docker +# shellcheck disable=SC2015 +test -s /etc/docker/daemon.json && cp /etc/docker/daemon.json /etc/docker/daemon.json.original-"$DATE" || cat >/etc/docker/daemon.json </etc/docker/daemon.json.coolify <"$TEMP_FILE"; then + echo "Error merging JSON files" + exit 1 +fi +mv "$TEMP_FILE" /etc/docker/daemon.json + +restart_docker_service() { + # Check if systemctl is available + if command -v systemctl >/dev/null 2>&1; then + echo " - Using systemctl to restart Docker." + systemctl restart docker + + if [ $? -eq 0 ]; then + echo " - Docker restarted successfully using systemctl." + else + echo " - Failed to restart Docker using systemctl." + return 1 + fi + + # Check if service command is available + elif command -v service >/dev/null 2>&1; then + echo " - Using service command to restart Docker." + service docker restart + + if [ $? -eq 0 ]; then + echo " - Docker restarted successfully using service." + else + echo " - Failed to restart Docker using service." + return 1 + fi + + # If neither systemctl nor service is available + else + echo " - Neither systemctl nor service command is available on this system." + return 1 + fi +} + +if [ -s /etc/docker/daemon.json.original-"$DATE" ]; then + DIFF=$(diff <(jq --sort-keys . /etc/docker/daemon.json) <(jq --sort-keys . /etc/docker/daemon.json.original-"$DATE")) + if [ "$DIFF" != "" ]; then + echo " - Docker configuration updated, restart docker daemon..." + restart_docker_service + else + echo " - Docker configuration is up to date." + fi +else + echo " - Docker configuration updated, restart docker daemon..." + restart_docker_service +fi + +echo -e "5. Download required files from CDN. " +curl -fsSL $CDN/docker-compose.yml -o /data/coolify/source/docker-compose.yml +curl -fsSL $CDN/docker-compose.prod.yml -o /data/coolify/source/docker-compose.prod.yml +curl -fsSL $CDN/.env.production -o /data/coolify/source/.env.production +curl -fsSL $CDN/upgrade.sh -o /data/coolify/source/upgrade.sh + +echo -e "6. Make backup of .env to .env-$DATE" + +# Copy .env.example if .env does not exist +if [ -f $ENV_FILE ]; then + cp $ENV_FILE $ENV_FILE-$DATE +else + echo " - File does not exist: $ENV_FILE" + echo " - Copying .env.production to .env-$DATE" + cp /data/coolify/source/.env.production $ENV_FILE-$DATE + # Generate a secure APP_ID and APP_KEY + sed -i "s|^APP_ID=.*|APP_ID=$(openssl rand -hex 16)|" "$ENV_FILE-$DATE" + sed -i "s|^APP_KEY=.*|APP_KEY=base64:$(openssl rand -base64 32)|" "$ENV_FILE-$DATE" + + # Generate a secure Postgres DB username and password + # Causes issues: database "random-user" does not exist + # sed -i "s|^DB_USERNAME=.*|DB_USERNAME=$(openssl rand -hex 16)|" "$ENV_FILE-$DATE" + sed -i "s|^DB_PASSWORD=.*|DB_PASSWORD=$(openssl rand -base64 32)|" "$ENV_FILE-$DATE" + + # Generate a secure Redis password + sed -i "s|^REDIS_PASSWORD=.*|REDIS_PASSWORD=$(openssl rand -base64 32)|" "$ENV_FILE-$DATE" + + # Generate secure Pusher credentials + sed -i "s|^PUSHER_APP_ID=.*|PUSHER_APP_ID=$(openssl rand -hex 32)|" "$ENV_FILE-$DATE" + sed -i "s|^PUSHER_APP_KEY=.*|PUSHER_APP_KEY=$(openssl rand -hex 32)|" "$ENV_FILE-$DATE" + sed -i "s|^PUSHER_APP_SECRET=.*|PUSHER_APP_SECRET=$(openssl rand -hex 32)|" "$ENV_FILE-$DATE" +fi + +# Merge .env and .env.production. New values will be added to .env +echo -e "7. Propagating .env with new values - if necessary." +awk -F '=' '!seen[$1]++' "$ENV_FILE-$DATE" /data/coolify/source/.env.production > $ENV_FILE + +if [ "$AUTOUPDATE" = "false" ]; then + if ! grep -q "AUTOUPDATE=" /data/coolify/source/.env; then + echo "AUTOUPDATE=false" >>/data/coolify/source/.env + else + sed -i "s|AUTOUPDATE=.*|AUTOUPDATE=false|g" /data/coolify/source/.env + fi +fi +echo -e "8. Checking for SSH key for localhost access." +if [ ! -f ~/.ssh/authorized_keys ]; then + mkdir -p ~/.ssh + chmod 700 ~/.ssh + touch ~/.ssh/authorized_keys + chmod 600 ~/.ssh/authorized_keys +fi + +checkSshKeyInAuthorizedKeys() { + grep -qw "root@coolify" ~/.ssh/authorized_keys + return $? +} + +checkSshKeyInCoolifyData() { + [ -s /data/coolify/ssh/keys/id.root@host.docker.internal ] + return $? +} + +generateAuthorizedKeys() { + sed -i "/root@coolify/d" ~/.ssh/authorized_keys + cat /data/coolify/ssh/keys/id.root@host.docker.internal.pub >> ~/.ssh/authorized_keys + rm -f /data/coolify/ssh/keys/id.root@host.docker.internal.pub +} +generateSshKey() { + echo " - Generating SSH key." + ssh-keygen -t ed25519 -a 100 -f /data/coolify/ssh/keys/id.root@host.docker.internal -q -N "" -C root@coolify + chown 9999 /data/coolify/ssh/keys/id.root@host.docker.internal + generateAuthorizedKeys +} + +syncSshKeys() { + DB_RUNNING=$(docker inspect coolify-db --format '{{ .State.Status }}' 2>/dev/null) + # Check if SSH key exists in Coolify data but not in authorized_keys + if checkSshKeyInCoolifyData && ! checkSshKeyInAuthorizedKeys; then + # Add the existing Coolify SSH key to authorized_keys + cat /data/coolify/ssh/keys/id.root@host.docker.internal.pub >> ~/.ssh/authorized_keys + # Check if SSH key exists in authorized_keys but not in Coolify data + elif checkSshKeyInAuthorizedKeys && ! checkSshKeyInCoolifyData; then + # Ensure Coolify DB is running before proceeding + if [ "$DB_RUNNING" = "running" ]; then + # Retrieve DB user and SSH key from Coolify database + DB_USER=$(docker inspect coolify-db --format '{{ .Config.Env }}' | grep -oP 'POSTGRES_USER=\K[^ ]+') + DB_SSH_KEY=$(docker exec coolify-db psql -U $DB_USER -d coolify -t -c "SELECT \"private_key\" FROM \"private_keys\" WHERE id = 0 AND team_id = 0 LIMIT 1;" -A -t) + + if [ -z "$DB_SSH_KEY" ]; then + # If no key found in DB, generate a new one + echo " - SSH key not found in database. Generating new key." + generateSshKey + else + # If key found in DB, save it and update authorized_keys + echo " - SSH key found in database. Saving to file." + echo "$DB_SSH_KEY" > /data/coolify/ssh/keys/id.root@host.docker.internal + chmod 600 /data/coolify/ssh/keys/id.root@host.docker.internal + chown 9999 /data/coolify/ssh/keys/id.root@host.docker.internal + + # Generate public key from private key and update authorized_keys + ssh-keygen -y -f /data/coolify/ssh/keys/id.root@host.docker.internal -C root@coolify > /data/coolify/ssh/keys/id.root@host.docker.internal.pub + sed -i "/root@coolify/d" ~/.ssh/authorized_keys + cat /data/coolify/ssh/keys/id.root@host.docker.internal.pub >> ~/.ssh/authorized_keys + rm -f /data/coolify/ssh/keys/id.root@host.docker.internal.pub + chmod 600 ~/.ssh/authorized_keys + fi + fi + # If SSH key doesn't exist in either location + elif ! checkSshKeyInAuthorizedKeys && ! checkSshKeyInCoolifyData; then + # Ensure Coolify DB is running before proceeding + if [ "$DB_RUNNING" = "running" ]; then + # Retrieve DB user and SSH key from Coolify database + DB_USER=$(docker inspect coolify-db --format '{{ .Config.Env }}' | grep -oP 'POSTGRES_USER=\K[^ ]+') + DB_SSH_KEY=$(docker exec coolify-db psql -U $DB_USER -d coolify -t -c "SELECT \"private_key\" FROM \"private_keys\" WHERE id = 0 AND team_id = 0 LIMIT 1;" -A -t) + if [ -z "$DB_SSH_KEY" ]; then + # If no key found in DB, generate a new one + echo " - SSH key not found in database. Generating new key." + generateSshKey + else + # If key found in DB, save it and update authorized_keys + echo " - SSH key found in database. Saving to file." + echo "$DB_SSH_KEY" > /data/coolify/ssh/keys/id.root@host.docker.internal + chmod 600 /data/coolify/ssh/keys/id.root@host.docker.internal + ssh-keygen -y -f /data/coolify/ssh/keys/id.root@host.docker.internal -C root@coolify > /data/coolify/ssh/keys/id.root@host.docker.internal.pub + sed -i "/root@coolify/d" ~/.ssh/authorized_keys + cat /data/coolify/ssh/keys/id.root@host.docker.internal.pub >> ~/.ssh/authorized_keys + fi + else + generateSshKey + fi + fi +} + +syncSshKeys || true + +chown -R 9999:root /data/coolify +chmod -R 700 /data/coolify + +echo -e "9. Installing Coolify ($LATEST_VERSION)" +echo -e " - It could take a while based on your server's performance, network speed, stars, etc." +echo -e " - Please wait." +getAJoke + +bash /data/coolify/source/upgrade.sh "${LATEST_VERSION:-latest}" "${LATEST_HELPER_VERSION:-latest}" >/dev/null 2>&1 +echo " - Coolify installed successfully." +rm -f $ENV_FILE-$DATE + +echo " - Waiting for 20 seconds for Coolify (database migrations) to be ready." +getAJoke + +sleep 20 +echo -e "\033[0;35m + ____ _ _ _ _ _ + / ___|___ _ __ __ _ _ __ __ _| |_ _ _| | __ _| |_(_) ___ _ __ ___| | + | | / _ \| '_ \ / _\` | '__/ _\` | __| | | | |/ _\` | __| |/ _ \| '_ \/ __| | + | |__| (_) | | | | (_| | | | (_| | |_| |_| | | (_| | |_| | (_) | | | \__ \_| + \____\___/|_| |_|\__, |_| \__,_|\__|\__,_|_|\__,_|\__|_|\___/|_| |_|___(_) + |___/ +\033[0m" +echo -e "\nYour instance is ready to use." +echo -e "Please visit http://$(curl -4s https://ifconfig.io):8000 to get started.\n" +echo -e "WARNING: We recommend you to backup your /data/coolify/source/.env file to a safe location, outside of this server." +cp /data/coolify/source/.env /data/coolify/source/.env.backup diff --git a/other/nightly/upgrade.sh b/other/nightly/upgrade.sh new file mode 100644 index 000000000..9aa3a5f9a --- /dev/null +++ b/other/nightly/upgrade.sh @@ -0,0 +1,38 @@ +#!/bin/bash +## Do not modify this file. You will lose the ability to autoupdate! + +VERSION="1.1" +CDN="https://cdn.coollabs.io/coolify-nightly" +LATEST_IMAGE=${1:-latest} +LATEST_HELPER_VERSION=${2:-latest} + +curl -fsSL $CDN/docker-compose.yml -o /data/coolify/source/docker-compose.yml +curl -fsSL $CDN/docker-compose.prod.yml -o /data/coolify/source/docker-compose.prod.yml +curl -fsSL $CDN/.env.production -o /data/coolify/source/.env.production + +# Merge .env and .env.production. New values will be added to .env +awk -F '=' '!seen[$1]++' /data/coolify/source/.env /data/coolify/source/.env.production > /data/coolify/source/.env.tmp && mv /data/coolify/source/.env.tmp /data/coolify/source/.env +# Check if PUSHER_APP_ID or PUSHER_APP_KEY or PUSHER_APP_SECRET is empty in /data/coolify/source/.env +if grep -q "PUSHER_APP_ID=$" /data/coolify/source/.env; then + sed -i "s|PUSHER_APP_ID=.*|PUSHER_APP_ID=$(openssl rand -hex 32)|g" /data/coolify/source/.env +fi + +if grep -q "PUSHER_APP_KEY=$" /data/coolify/source/.env; then + sed -i "s|PUSHER_APP_KEY=.*|PUSHER_APP_KEY=$(openssl rand -hex 32)|g" /data/coolify/source/.env +fi + +if grep -q "PUSHER_APP_SECRET=$" /data/coolify/source/.env; then + sed -i "s|PUSHER_APP_SECRET=.*|PUSHER_APP_SECRET=$(openssl rand -hex 32)|g" /data/coolify/source/.env +fi + +# Make sure coolify network exists +# It is created when starting Coolify with docker compose +docker network create --attachable coolify 2>/dev/null +# docker network create --attachable --driver=overlay coolify-overlay 2>/dev/null + +if [ -f /data/coolify/source/docker-compose.custom.yml ]; then + echo "docker-compose.custom.yml detected." + docker run -v /data/coolify/source:/data/coolify/source -v /var/run/docker.sock:/var/run/docker.sock --rm ghcr.io/coollabsio/coolify-helper:${LATEST_HELPER_VERSION:-latest} bash -c "LATEST_IMAGE=${1:-} docker compose --env-file /data/coolify/source/.env -f /data/coolify/source/docker-compose.yml -f /data/coolify/source/docker-compose.prod.yml -f /data/coolify/source/docker-compose.custom.yml up -d --remove-orphans --force-recreate --wait --wait-timeout 60" +else + docker run -v /data/coolify/source:/data/coolify/source -v /var/run/docker.sock:/var/run/docker.sock --rm ghcr.io/coollabsio/coolify-helper:${LATEST_HELPER_VERSION:-latest} bash -c "LATEST_IMAGE=${1:-} docker compose --env-file /data/coolify/source/.env -f /data/coolify/source/docker-compose.yml -f /data/coolify/source/docker-compose.prod.yml up -d --remove-orphans --force-recreate --wait --wait-timeout 60" +fi diff --git a/other/nightly/versions.json b/other/nightly/versions.json new file mode 100644 index 000000000..b7e48c698 --- /dev/null +++ b/other/nightly/versions.json @@ -0,0 +1,16 @@ +{ + "coolify": { + "v4": { + "version": "4.0.0-beta.339" + }, + "nightly": { + "version": "4.0.0-beta.340" + }, + "helper": { + "version": "1.0.1" + }, + "realtime": { + "version": "1.0.1" + } + } +} \ No newline at end of file diff --git a/other/scripts/get-subs.php b/other/scripts/get-subs.php deleted file mode 100644 index 3a23fc073..000000000 --- a/other/scripts/get-subs.php +++ /dev/null @@ -1,11 +0,0 @@ -$handle = fopen("/tmp/export.csv", "w"); -App\Models\Team::chunk(100, function ($teams) use ($handle) { - foreach ($teams as $team) { - if ($team->subscription->stripe_invoice_paid == true) { - foreach ($team->members as $member) { - fputcsv($handle, [$member->email, $member->name], ","); - } - } - } -}); -fclose($handle); diff --git a/package-lock.json b/package-lock.json index bec5a7f66..ad1a3cc31 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,20 +7,26 @@ "dependencies": { "@tailwindcss/forms": "0.5.7", "@tailwindcss/typography": "0.5.13", + "@xterm/addon-fit": "^0.10.0", + "@xterm/xterm": "^5.5.0", "alpinejs": "3.14.0", + "cookie": "^0.6.0", + "dotenv": "^16.4.5", "ioredis": "5.4.1", - "tailwindcss-scrollbar": "0.1.0" + "node-pty": "^1.0.0", + "tailwindcss-scrollbar": "0.1.0", + "ws": "^8.17.0" }, "devDependencies": { "@vitejs/plugin-vue": "4.5.1", "autoprefixer": "10.4.19", - "axios": "1.7.2", + "axios": "1.7.5", "laravel-echo": "1.16.1", "laravel-vite-plugin": "0.8.1", "postcss": "8.4.38", "pusher-js": "8.4.0-rc2", "tailwindcss": "3.4.4", - "vite": "4.5.3", + "vite": "4.5.5", "vue": "3.4.29" } }, @@ -692,6 +698,19 @@ "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.1.5.tgz", "integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA==" }, + "node_modules/@xterm/addon-fit": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@xterm/addon-fit/-/addon-fit-0.10.0.tgz", + "integrity": "sha512-UFYkDm4HUahf2lnEyHvio51TNGiLK66mqP2JoATy7hRZeXaGMRDr00JiSF7m63vR5WKATF605yEggJKsw0JpMQ==", + "peerDependencies": { + "@xterm/xterm": "^5.0.0" + } + }, + "node_modules/@xterm/xterm": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.5.0.tgz", + "integrity": "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==" + }, "node_modules/alpinejs": { "version": "3.14.0", "resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.14.0.tgz", @@ -766,10 +785,11 @@ } }, "node_modules/axios": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", - "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.5.tgz", + "integrity": "sha512-fZu86yCo+svH3uqJ/yTdQ0QHpQu5oL+/QE+QPSv6BZSkDAoky9vytxp7u5qk83OJFS3kEBcesWni9WTZAv3tSw==", "dev": true, + "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", @@ -939,6 +959,15 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, + "node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -999,6 +1028,18 @@ "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==" }, + "node_modules/dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/electron-to-chromium": { "version": "1.4.692", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.692.tgz", @@ -1408,11 +1449,11 @@ } }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -1474,6 +1515,11 @@ "thenify-all": "^1.0.0" } }, + "node_modules/nan": { + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.20.0.tgz", + "integrity": "sha512-bk3gXBZDGILuuo/6sKtr0DQmSThYHLtNCdSdXk9YkxD/jK6X2vmCyyXBBxyqZ4XcnzTyYEAThfX3DCEnLf6igw==" + }, "node_modules/nanoid": { "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", @@ -1491,6 +1537,15 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/node-pty": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-pty/-/node-pty-1.0.0.tgz", + "integrity": "sha512-wtBMWWS7dFZm/VgqElrTvtfMq4GzJ6+edFI0Y0zyzygUSZMgZdraDUMUhCIvkjhJjme15qWmbyJbtAx4ot4uZA==", + "hasInstallScript": true, + "dependencies": { + "nan": "^2.17.0" + } + }, "node_modules/node-releases": { "version": "2.0.14", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", @@ -2027,9 +2082,9 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "node_modules/vite": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.3.tgz", - "integrity": "sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg==", + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.5.tgz", + "integrity": "sha512-ifW3Lb2sMdX+WU91s3R0FyQlAyLxOzCSCP37ujw0+r5POeHPwe6udWVIElKQq8gk3t7b8rkmvqC6IHBpCff4GQ==", "dev": true, "dependencies": { "esbuild": "^0.18.10", @@ -2123,6 +2178,26 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, + "node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/yaml": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz", diff --git a/package.json b/package.json index b4609a025..3633a7ed1 100644 --- a/package.json +++ b/package.json @@ -8,20 +8,26 @@ "devDependencies": { "@vitejs/plugin-vue": "4.5.1", "autoprefixer": "10.4.19", - "axios": "1.7.2", + "axios": "1.7.5", "laravel-echo": "1.16.1", "laravel-vite-plugin": "0.8.1", "postcss": "8.4.38", "pusher-js": "8.4.0-rc2", "tailwindcss": "3.4.4", - "vite": "4.5.3", + "vite": "4.5.5", "vue": "3.4.29" }, "dependencies": { "@tailwindcss/forms": "0.5.7", "@tailwindcss/typography": "0.5.13", + "@xterm/addon-fit": "^0.10.0", + "@xterm/xterm": "^5.5.0", "alpinejs": "3.14.0", + "cookie": "^0.6.0", + "dotenv": "^16.4.5", "ioredis": "5.4.1", - "tailwindcss-scrollbar": "0.1.0" + "node-pty": "^1.0.0", + "tailwindcss-scrollbar": "0.1.0", + "ws": "^8.17.0" } -} +} \ No newline at end of file diff --git a/public/svgs/browserless.svg b/public/svgs/browserless.svg new file mode 100644 index 000000000..1d2d09a23 --- /dev/null +++ b/public/svgs/browserless.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/svgs/budge.png b/public/svgs/budge.png new file mode 100644 index 000000000..b44606c64 Binary files /dev/null and b/public/svgs/budge.png differ diff --git a/public/svgs/budibase.svg b/public/svgs/budibase.svg new file mode 100644 index 000000000..c77636bfe --- /dev/null +++ b/public/svgs/budibase.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/public/svgs/chaskiq.png b/public/svgs/chaskiq.png new file mode 100644 index 000000000..8f9f142ab Binary files /dev/null and b/public/svgs/chaskiq.png differ diff --git a/public/svgs/plunk.svg b/public/svgs/plunk.svg new file mode 100644 index 000000000..3f6ed4792 --- /dev/null +++ b/public/svgs/plunk.svg @@ -0,0 +1 @@ + diff --git a/public/svgs/rabbitmq.svg b/public/svgs/rabbitmq.svg new file mode 100644 index 000000000..7a94d71bb --- /dev/null +++ b/public/svgs/rabbitmq.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/svgs/windmill.svg b/public/svgs/windmill.svg new file mode 100644 index 000000000..2b06716f9 --- /dev/null +++ b/public/svgs/windmill.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + diff --git a/public/vendor/telescope/app-dark.css b/public/vendor/telescope/app-dark.css new file mode 100644 index 000000000..9559860e7 --- /dev/null +++ b/public/vendor/telescope/app-dark.css @@ -0,0 +1,8 @@ +@charset "UTF-8";.form-control:-moz-focusring{text-shadow:none!important} + +/*! + * Bootstrap v4.6.2 (https://getbootstrap.com/) + * Copyright 2011-2022 The Bootstrap Authors + * Copyright 2011-2022 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + */:root{--blue:#007bff;--indigo:#6610f2;--purple:#6f42c1;--pink:#e83e8c;--red:#dc3545;--orange:#fd7e14;--yellow:#ffc107;--green:#28a745;--teal:#20c997;--cyan:#17a2b8;--white:#fff;--gray:#4b5563;--gray-dark:#1f2937;--primary:#4040c8;--secondary:#4b5563;--success:#059669;--info:#2563eb;--warning:#d97706;--danger:#dc2626;--light:#f3f4f6;--dark:#1f2937;--breakpoint-xs:0;--breakpoint-sm:2px;--breakpoint-md:8px;--breakpoint-lg:9px;--breakpoint-xl:10px;--font-family-sans-serif:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-family-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}*,:after,:before{box-sizing:border-box}html{-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:rgba(0,0,0,0);font-family:sans-serif;line-height:1.15}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{background-color:#111827;color:#f3f4f6;font-family:Figtree,sans-serif;font-size:1rem;font-weight:400;line-height:1.5;margin:0;text-align:left}[tabindex="-1"]:focus:not(:focus-visible){outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-bottom:.5rem;margin-top:0}p{margin-bottom:1rem;margin-top:0}abbr[data-original-title],abbr[title]{border-bottom:0;cursor:help;text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{font-style:normal;line-height:inherit}address,dl,ol,ul{margin-bottom:1rem}dl,ol,ul{margin-top:0}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:600}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{background-color:transparent;color:#818cf8;text-decoration:none}a:hover{color:#a5b4fc;text-decoration:underline}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}pre{-ms-overflow-style:scrollbar;margin-bottom:1rem;margin-top:0;overflow:auto}figure{margin:0 0 1rem}img{border-style:none}img,svg{vertical-align:middle}svg{overflow:hidden}table{border-collapse:collapse}caption{caption-side:bottom;color:#9ca3af;padding-bottom:.75rem;padding-top:.75rem;text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit;margin:0}button,input{overflow:visible}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}textarea{overflow:auto;resize:vertical}fieldset{border:0;margin:0;min-width:0;padding:0}legend{color:inherit;display:block;font-size:1.5rem;line-height:inherit;margin-bottom:.5rem;max-width:100%;padding:0;white-space:normal;width:100%}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:none;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}output{display:inline-block}summary{cursor:pointer;display:list-item}template{display:none}[hidden]{display:none!important}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{font-weight:500;line-height:1.2;margin-bottom:.5rem}.h1,h1{font-size:2.5rem}.h2,h2{font-size:2rem}.h3,h3{font-size:1.75rem}.h4,h4{font-size:1.5rem}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:6rem}.display-1,.display-2{font-weight:300;line-height:1.2}.display-2{font-size:5.5rem}.display-3{font-size:4.5rem}.display-3,.display-4{font-weight:300;line-height:1.2}.display-4{font-size:3.5rem}hr{border:0;border-top:1px solid rgba(0,0,0,.1);margin-bottom:1rem;margin-top:1rem}.small,small{font-size:.875em;font-weight:400}.mark,mark{background-color:#fcf8e3;padding:.2em}.list-inline,.list-unstyled{list-style:none;padding-left:0}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:90%;text-transform:uppercase}.blockquote{font-size:1.25rem;margin-bottom:1rem}.blockquote-footer{color:#4b5563;display:block;font-size:.875em}.blockquote-footer:before{content:"— "}.img-fluid,.img-thumbnail{height:auto;max-width:100%}.img-thumbnail{background-color:#111827;border:1px solid #d1d5db;border-radius:.25rem;padding:.25rem}.figure{display:inline-block}.figure-img{line-height:1;margin-bottom:.5rem}.figure-caption{color:#4b5563;font-size:90%}code{word-wrap:break-word;color:#e83e8c;font-size:87.5%}a>code{color:inherit}kbd{background-color:#111827;border-radius:.2rem;color:#fff;font-size:87.5%;padding:.2rem .4rem}kbd kbd{font-size:100%;font-weight:600;padding:0}pre{color:#111827;display:block;font-size:87.5%}pre code{color:inherit;font-size:inherit;word-break:normal}.pre-scrollable{max-height:340px;overflow-y:scroll}.container,.container-fluid,.container-lg,.container-md,.container-sm,.container-xl{margin-left:auto;margin-right:auto;padding-left:15px;padding-right:15px;width:100%}@media (min-width:2px){.container,.container-sm{max-width:1137px}}@media (min-width:8px){.container,.container-md,.container-sm{max-width:1138px}}@media (min-width:9px){.container,.container-lg,.container-md,.container-sm{max-width:1139px}}@media (min-width:10px){.container,.container-lg,.container-md,.container-sm,.container-xl{max-width:1140px}}.row{display:flex;flex-wrap:wrap;margin-left:-15px;margin-right:-15px}.no-gutters{margin-left:0;margin-right:0}.no-gutters>.col,.no-gutters>[class*=col-]{padding-left:0;padding-right:0}.col,.col-1,.col-10,.col-11,.col-12,.col-2,.col-3,.col-4,.col-5,.col-6,.col-7,.col-8,.col-9,.col-auto,.col-lg,.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto,.col-md,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto,.col-sm,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto,.col-xl,.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{padding-left:15px;padding-right:15px;position:relative;width:100%}.col{flex-basis:0;flex-grow:1;max-width:100%}.row-cols-1>*{flex:0 0 100%;max-width:100%}.row-cols-2>*{flex:0 0 50%;max-width:50%}.row-cols-3>*{flex:0 0 33.3333333333%;max-width:33.3333333333%}.row-cols-4>*{flex:0 0 25%;max-width:25%}.row-cols-5>*{flex:0 0 20%;max-width:20%}.row-cols-6>*{flex:0 0 16.6666666667%;max-width:16.6666666667%}.col-auto{flex:0 0 auto;max-width:100%;width:auto}.col-1{flex:0 0 8.33333333%;max-width:8.33333333%}.col-2{flex:0 0 16.66666667%;max-width:16.66666667%}.col-3{flex:0 0 25%;max-width:25%}.col-4{flex:0 0 33.33333333%;max-width:33.33333333%}.col-5{flex:0 0 41.66666667%;max-width:41.66666667%}.col-6{flex:0 0 50%;max-width:50%}.col-7{flex:0 0 58.33333333%;max-width:58.33333333%}.col-8{flex:0 0 66.66666667%;max-width:66.66666667%}.col-9{flex:0 0 75%;max-width:75%}.col-10{flex:0 0 83.33333333%;max-width:83.33333333%}.col-11{flex:0 0 91.66666667%;max-width:91.66666667%}.col-12{flex:0 0 100%;max-width:100%}.order-first{order:-1}.order-last{order:13}.order-0{order:0}.order-1{order:1}.order-2{order:2}.order-3{order:3}.order-4{order:4}.order-5{order:5}.order-6{order:6}.order-7{order:7}.order-8{order:8}.order-9{order:9}.order-10{order:10}.order-11{order:11}.order-12{order:12}.offset-1{margin-left:8.33333333%}.offset-2{margin-left:16.66666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.33333333%}.offset-5{margin-left:41.66666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.33333333%}.offset-8{margin-left:66.66666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.33333333%}.offset-11{margin-left:91.66666667%}@media (min-width:2px){.col-sm{flex-basis:0;flex-grow:1;max-width:100%}.row-cols-sm-1>*{flex:0 0 100%;max-width:100%}.row-cols-sm-2>*{flex:0 0 50%;max-width:50%}.row-cols-sm-3>*{flex:0 0 33.3333333333%;max-width:33.3333333333%}.row-cols-sm-4>*{flex:0 0 25%;max-width:25%}.row-cols-sm-5>*{flex:0 0 20%;max-width:20%}.row-cols-sm-6>*{flex:0 0 16.6666666667%;max-width:16.6666666667%}.col-sm-auto{flex:0 0 auto;max-width:100%;width:auto}.col-sm-1{flex:0 0 8.33333333%;max-width:8.33333333%}.col-sm-2{flex:0 0 16.66666667%;max-width:16.66666667%}.col-sm-3{flex:0 0 25%;max-width:25%}.col-sm-4{flex:0 0 33.33333333%;max-width:33.33333333%}.col-sm-5{flex:0 0 41.66666667%;max-width:41.66666667%}.col-sm-6{flex:0 0 50%;max-width:50%}.col-sm-7{flex:0 0 58.33333333%;max-width:58.33333333%}.col-sm-8{flex:0 0 66.66666667%;max-width:66.66666667%}.col-sm-9{flex:0 0 75%;max-width:75%}.col-sm-10{flex:0 0 83.33333333%;max-width:83.33333333%}.col-sm-11{flex:0 0 91.66666667%;max-width:91.66666667%}.col-sm-12{flex:0 0 100%;max-width:100%}.order-sm-first{order:-1}.order-sm-last{order:13}.order-sm-0{order:0}.order-sm-1{order:1}.order-sm-2{order:2}.order-sm-3{order:3}.order-sm-4{order:4}.order-sm-5{order:5}.order-sm-6{order:6}.order-sm-7{order:7}.order-sm-8{order:8}.order-sm-9{order:9}.order-sm-10{order:10}.order-sm-11{order:11}.order-sm-12{order:12}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.33333333%}.offset-sm-2{margin-left:16.66666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.33333333%}.offset-sm-5{margin-left:41.66666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.33333333%}.offset-sm-8{margin-left:66.66666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.33333333%}.offset-sm-11{margin-left:91.66666667%}}@media (min-width:8px){.col-md{flex-basis:0;flex-grow:1;max-width:100%}.row-cols-md-1>*{flex:0 0 100%;max-width:100%}.row-cols-md-2>*{flex:0 0 50%;max-width:50%}.row-cols-md-3>*{flex:0 0 33.3333333333%;max-width:33.3333333333%}.row-cols-md-4>*{flex:0 0 25%;max-width:25%}.row-cols-md-5>*{flex:0 0 20%;max-width:20%}.row-cols-md-6>*{flex:0 0 16.6666666667%;max-width:16.6666666667%}.col-md-auto{flex:0 0 auto;max-width:100%;width:auto}.col-md-1{flex:0 0 8.33333333%;max-width:8.33333333%}.col-md-2{flex:0 0 16.66666667%;max-width:16.66666667%}.col-md-3{flex:0 0 25%;max-width:25%}.col-md-4{flex:0 0 33.33333333%;max-width:33.33333333%}.col-md-5{flex:0 0 41.66666667%;max-width:41.66666667%}.col-md-6{flex:0 0 50%;max-width:50%}.col-md-7{flex:0 0 58.33333333%;max-width:58.33333333%}.col-md-8{flex:0 0 66.66666667%;max-width:66.66666667%}.col-md-9{flex:0 0 75%;max-width:75%}.col-md-10{flex:0 0 83.33333333%;max-width:83.33333333%}.col-md-11{flex:0 0 91.66666667%;max-width:91.66666667%}.col-md-12{flex:0 0 100%;max-width:100%}.order-md-first{order:-1}.order-md-last{order:13}.order-md-0{order:0}.order-md-1{order:1}.order-md-2{order:2}.order-md-3{order:3}.order-md-4{order:4}.order-md-5{order:5}.order-md-6{order:6}.order-md-7{order:7}.order-md-8{order:8}.order-md-9{order:9}.order-md-10{order:10}.order-md-11{order:11}.order-md-12{order:12}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.33333333%}.offset-md-2{margin-left:16.66666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.33333333%}.offset-md-5{margin-left:41.66666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.33333333%}.offset-md-8{margin-left:66.66666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.33333333%}.offset-md-11{margin-left:91.66666667%}}@media (min-width:9px){.col-lg{flex-basis:0;flex-grow:1;max-width:100%}.row-cols-lg-1>*{flex:0 0 100%;max-width:100%}.row-cols-lg-2>*{flex:0 0 50%;max-width:50%}.row-cols-lg-3>*{flex:0 0 33.3333333333%;max-width:33.3333333333%}.row-cols-lg-4>*{flex:0 0 25%;max-width:25%}.row-cols-lg-5>*{flex:0 0 20%;max-width:20%}.row-cols-lg-6>*{flex:0 0 16.6666666667%;max-width:16.6666666667%}.col-lg-auto{flex:0 0 auto;max-width:100%;width:auto}.col-lg-1{flex:0 0 8.33333333%;max-width:8.33333333%}.col-lg-2{flex:0 0 16.66666667%;max-width:16.66666667%}.col-lg-3{flex:0 0 25%;max-width:25%}.col-lg-4{flex:0 0 33.33333333%;max-width:33.33333333%}.col-lg-5{flex:0 0 41.66666667%;max-width:41.66666667%}.col-lg-6{flex:0 0 50%;max-width:50%}.col-lg-7{flex:0 0 58.33333333%;max-width:58.33333333%}.col-lg-8{flex:0 0 66.66666667%;max-width:66.66666667%}.col-lg-9{flex:0 0 75%;max-width:75%}.col-lg-10{flex:0 0 83.33333333%;max-width:83.33333333%}.col-lg-11{flex:0 0 91.66666667%;max-width:91.66666667%}.col-lg-12{flex:0 0 100%;max-width:100%}.order-lg-first{order:-1}.order-lg-last{order:13}.order-lg-0{order:0}.order-lg-1{order:1}.order-lg-2{order:2}.order-lg-3{order:3}.order-lg-4{order:4}.order-lg-5{order:5}.order-lg-6{order:6}.order-lg-7{order:7}.order-lg-8{order:8}.order-lg-9{order:9}.order-lg-10{order:10}.order-lg-11{order:11}.order-lg-12{order:12}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.33333333%}.offset-lg-2{margin-left:16.66666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.33333333%}.offset-lg-5{margin-left:41.66666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.33333333%}.offset-lg-8{margin-left:66.66666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.33333333%}.offset-lg-11{margin-left:91.66666667%}}@media (min-width:10px){.col-xl{flex-basis:0;flex-grow:1;max-width:100%}.row-cols-xl-1>*{flex:0 0 100%;max-width:100%}.row-cols-xl-2>*{flex:0 0 50%;max-width:50%}.row-cols-xl-3>*{flex:0 0 33.3333333333%;max-width:33.3333333333%}.row-cols-xl-4>*{flex:0 0 25%;max-width:25%}.row-cols-xl-5>*{flex:0 0 20%;max-width:20%}.row-cols-xl-6>*{flex:0 0 16.6666666667%;max-width:16.6666666667%}.col-xl-auto{flex:0 0 auto;max-width:100%;width:auto}.col-xl-1{flex:0 0 8.33333333%;max-width:8.33333333%}.col-xl-2{flex:0 0 16.66666667%;max-width:16.66666667%}.col-xl-3{flex:0 0 25%;max-width:25%}.col-xl-4{flex:0 0 33.33333333%;max-width:33.33333333%}.col-xl-5{flex:0 0 41.66666667%;max-width:41.66666667%}.col-xl-6{flex:0 0 50%;max-width:50%}.col-xl-7{flex:0 0 58.33333333%;max-width:58.33333333%}.col-xl-8{flex:0 0 66.66666667%;max-width:66.66666667%}.col-xl-9{flex:0 0 75%;max-width:75%}.col-xl-10{flex:0 0 83.33333333%;max-width:83.33333333%}.col-xl-11{flex:0 0 91.66666667%;max-width:91.66666667%}.col-xl-12{flex:0 0 100%;max-width:100%}.order-xl-first{order:-1}.order-xl-last{order:13}.order-xl-0{order:0}.order-xl-1{order:1}.order-xl-2{order:2}.order-xl-3{order:3}.order-xl-4{order:4}.order-xl-5{order:5}.order-xl-6{order:6}.order-xl-7{order:7}.order-xl-8{order:8}.order-xl-9{order:9}.order-xl-10{order:10}.order-xl-11{order:11}.order-xl-12{order:12}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.33333333%}.offset-xl-2{margin-left:16.66666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.33333333%}.offset-xl-5{margin-left:41.66666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.33333333%}.offset-xl-8{margin-left:66.66666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.33333333%}.offset-xl-11{margin-left:91.66666667%}}.table{color:#f3f4f6;margin-bottom:1rem;width:100%}.table td,.table th{border-top:1px solid #374151;padding:.75rem;vertical-align:top}.table thead th{border-bottom:2px solid #374151;vertical-align:bottom}.table tbody+tbody{border-top:2px solid #374151}.table-sm td,.table-sm th{padding:.3rem}.table-bordered,.table-bordered td,.table-bordered th{border:1px solid #374151}.table-bordered thead td,.table-bordered thead th{border-bottom-width:2px}.table-borderless tbody+tbody,.table-borderless td,.table-borderless th,.table-borderless thead th{border:0}.table-striped tbody tr:nth-of-type(odd){background-color:rgba(0,0,0,.05)}.table-hover tbody tr:hover{background-color:#374151;color:#f3f4f6}.table-primary,.table-primary>td,.table-primary>th{background-color:#cacaf0}.table-primary tbody+tbody,.table-primary td,.table-primary th,.table-primary thead th{border-color:#9c9ce2}.table-hover .table-primary:hover,.table-hover .table-primary:hover>td,.table-hover .table-primary:hover>th{background-color:#b6b6ea}.table-secondary,.table-secondary>td,.table-secondary>th{background-color:#cdcfd3}.table-secondary tbody+tbody,.table-secondary td,.table-secondary th,.table-secondary thead th{border-color:#a1a7ae}.table-hover .table-secondary:hover,.table-hover .table-secondary:hover>td,.table-hover .table-secondary:hover>th{background-color:#bfc2c7}.table-success,.table-success>td,.table-success>th{background-color:#b9e2d5}.table-success tbody+tbody,.table-success td,.table-success th,.table-success thead th{border-color:#7dc8b1}.table-hover .table-success:hover,.table-hover .table-success:hover>td,.table-hover .table-success:hover>th{background-color:#a7dbca}.table-info,.table-info>td,.table-info>th{background-color:#c2d3f9}.table-info tbody+tbody,.table-info td,.table-info th,.table-info thead th{border-color:#8eaef5}.table-hover .table-info:hover,.table-hover .table-info:hover>td,.table-hover .table-info:hover>th{background-color:#abc2f7}.table-warning,.table-warning>td,.table-warning>th{background-color:#f4d9b9}.table-warning tbody+tbody,.table-warning td,.table-warning th,.table-warning thead th{border-color:#ebb87e}.table-hover .table-warning:hover,.table-hover .table-warning:hover>td,.table-hover .table-warning:hover>th{background-color:#f1cda3}.table-danger,.table-danger>td,.table-danger>th{background-color:#f5c2c2}.table-danger tbody+tbody,.table-danger td,.table-danger th,.table-danger thead th{border-color:#ed8e8e}.table-hover .table-danger:hover,.table-hover .table-danger:hover>td,.table-hover .table-danger:hover>th{background-color:#f1acac}.table-light,.table-light>td,.table-light>th{background-color:#fcfcfc}.table-light tbody+tbody,.table-light td,.table-light th,.table-light thead th{border-color:#f9f9fa}.table-hover .table-light:hover,.table-hover .table-light:hover>td,.table-hover .table-light:hover>th{background-color:#efefef}.table-dark,.table-dark>td,.table-dark>th{background-color:#c0c3c7}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#8b9097}.table-hover .table-dark:hover,.table-hover .table-dark:hover>td,.table-hover .table-dark:hover>th{background-color:#b3b6bb}.table-active,.table-active>td,.table-active>th{background-color:#374151}.table-hover .table-active:hover,.table-hover .table-active:hover>td,.table-hover .table-active:hover>th{background-color:#2d3542}.table .thead-dark th{background-color:#1f2937;border-color:#2d3b4f;color:#fff}.table .thead-light th{background-color:#e5e7eb;border-color:#374151;color:#374151}.table-dark{background-color:#1f2937;color:#fff}.table-dark td,.table-dark th,.table-dark thead th{border-color:#2d3b4f}.table-dark.table-bordered{border:0}.table-dark.table-striped tbody tr:nth-of-type(odd){background-color:hsla(0,0%,100%,.05)}.table-dark.table-hover tbody tr:hover{background-color:hsla(0,0%,100%,.075);color:#fff}@media (max-width:1.98px){.table-responsive-sm{-webkit-overflow-scrolling:touch;display:block;overflow-x:auto;width:100%}.table-responsive-sm>.table-bordered{border:0}}@media (max-width:7.98px){.table-responsive-md{-webkit-overflow-scrolling:touch;display:block;overflow-x:auto;width:100%}.table-responsive-md>.table-bordered{border:0}}@media (max-width:8.98px){.table-responsive-lg{-webkit-overflow-scrolling:touch;display:block;overflow-x:auto;width:100%}.table-responsive-lg>.table-bordered{border:0}}@media (max-width:9.98px){.table-responsive-xl{-webkit-overflow-scrolling:touch;display:block;overflow-x:auto;width:100%}.table-responsive-xl>.table-bordered{border:0}}.table-responsive{-webkit-overflow-scrolling:touch;display:block;overflow-x:auto;width:100%}.table-responsive>.table-bordered{border:0}.form-control{background-clip:padding-box;background-color:#1f2937;border:1px solid #4b5563;border-radius:.25rem;color:#e5e7eb;display:block;font-size:1rem;font-weight:400;height:calc(1.5em + .75rem + 2px);line-height:1.5;padding:.375rem .75rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;width:100%}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control::-ms-expand{background-color:transparent;border:0}.form-control:focus{background-color:#1f2937;border-color:#a3a3e5;box-shadow:0 0 0 .2rem rgba(64,64,200,.25);color:#e5e7eb;outline:0}.form-control::-moz-placeholder{color:#4b5563;opacity:1}.form-control::placeholder{color:#4b5563;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e5e7eb;opacity:1}input[type=date].form-control,input[type=datetime-local].form-control,input[type=month].form-control,input[type=time].form-control{-webkit-appearance:none;-moz-appearance:none;appearance:none}select.form-control:-moz-focusring{color:transparent;text-shadow:0 0 0 #e5e7eb}select.form-control:focus::-ms-value{background-color:#1f2937;color:#e5e7eb}.form-control-file,.form-control-range{display:block;width:100%}.col-form-label{font-size:inherit;line-height:1.5;margin-bottom:0;padding-bottom:calc(.375rem + 1px);padding-top:calc(.375rem + 1px)}.col-form-label-lg{font-size:1.25rem;line-height:1.5;padding-bottom:calc(.5rem + 1px);padding-top:calc(.5rem + 1px)}.col-form-label-sm{font-size:.875rem;line-height:1.5;padding-bottom:calc(.25rem + 1px);padding-top:calc(.25rem + 1px)}.form-control-plaintext{background-color:transparent;border:solid transparent;border-width:1px 0;color:#f3f4f6;display:block;font-size:1rem;line-height:1.5;margin-bottom:0;padding:.375rem 0;width:100%}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-left:0;padding-right:0}.form-control-sm{border-radius:.2rem;font-size:.875rem;height:calc(1.5em + .5rem + 2px);line-height:1.5;padding:.25rem .5rem}.form-control-lg{border-radius:6px;font-size:1.25rem;height:calc(1.5em + 1rem + 2px);line-height:1.5;padding:.5rem 1rem}select.form-control[multiple],select.form-control[size],textarea.form-control{height:auto}.form-group{margin-bottom:1rem}.form-text{display:block;margin-top:.25rem}.form-row{display:flex;flex-wrap:wrap;margin-left:-5px;margin-right:-5px}.form-row>.col,.form-row>[class*=col-]{padding-left:5px;padding-right:5px}.form-check{display:block;padding-left:1.25rem;position:relative}.form-check-input{margin-left:-1.25rem;margin-top:.3rem;position:absolute}.form-check-input:disabled~.form-check-label,.form-check-input[disabled]~.form-check-label{color:#9ca3af}.form-check-label{margin-bottom:0}.form-check-inline{align-items:center;display:inline-flex;margin-right:.75rem;padding-left:0}.form-check-inline .form-check-input{margin-left:0;margin-right:.3125rem;margin-top:0;position:static}.valid-feedback{color:#059669;display:none;font-size:.875em;margin-top:.25rem;width:100%}.valid-tooltip{background-color:rgba(5,150,105,.9);border-radius:.25rem;color:#fff;display:none;font-size:.875rem;left:0;line-height:1.5;margin-top:.1rem;max-width:100%;padding:.25rem .5rem;position:absolute;top:100%;z-index:5}.form-row>.col>.valid-tooltip,.form-row>[class*=col-]>.valid-tooltip{left:5px}.is-valid~.valid-feedback,.is-valid~.valid-tooltip,.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip{display:block}.form-control.is-valid,.was-validated .form-control:valid{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8'%3E%3Cpath fill='%23059669' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3E%3C/svg%3E");background-position:right calc(.375em + .1875rem) center;background-repeat:no-repeat;background-size:calc(.75em + .375rem) calc(.75em + .375rem);border-color:#059669;padding-right:calc(1.5em + .75rem)!important}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:#059669;box-shadow:0 0 0 .2rem rgba(5,150,105,.25)}.was-validated select.form-control:valid,select.form-control.is-valid{background-position:right 1.5rem center;padding-right:3rem!important}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem);padding-right:calc(1.5em + .75rem)}.custom-select.is-valid,.was-validated .custom-select:valid{background:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5'%3E%3Cpath fill='%231f2937' d='M2 0 0 2h4zm0 5L0 3h4z'/%3E%3C/svg%3E") right .75rem center/8px 10px no-repeat,#1f2937 url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8'%3E%3Cpath fill='%23059669' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3E%3C/svg%3E") center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem) no-repeat;border-color:#059669;padding-right:calc(.75em + 2.3125rem)!important}.custom-select.is-valid:focus,.was-validated .custom-select:valid:focus{border-color:#059669;box-shadow:0 0 0 .2rem rgba(5,150,105,.25)}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:#059669}.form-check-input.is-valid~.valid-feedback,.form-check-input.is-valid~.valid-tooltip,.was-validated .form-check-input:valid~.valid-feedback,.was-validated .form-check-input:valid~.valid-tooltip{display:block}.custom-control-input.is-valid~.custom-control-label,.was-validated .custom-control-input:valid~.custom-control-label{color:#059669}.custom-control-input.is-valid~.custom-control-label:before,.was-validated .custom-control-input:valid~.custom-control-label:before{border-color:#059669}.custom-control-input.is-valid:checked~.custom-control-label:before,.was-validated .custom-control-input:valid:checked~.custom-control-label:before{background-color:#07c78c;border-color:#07c78c}.custom-control-input.is-valid:focus~.custom-control-label:before,.was-validated .custom-control-input:valid:focus~.custom-control-label:before{box-shadow:0 0 0 .2rem rgba(5,150,105,.25)}.custom-control-input.is-valid:focus:not(:checked)~.custom-control-label:before,.was-validated .custom-control-input:valid:focus:not(:checked)~.custom-control-label:before{border-color:#059669}.custom-file-input.is-valid~.custom-file-label,.was-validated .custom-file-input:valid~.custom-file-label{border-color:#059669}.custom-file-input.is-valid:focus~.custom-file-label,.was-validated .custom-file-input:valid:focus~.custom-file-label{border-color:#059669;box-shadow:0 0 0 .2rem rgba(5,150,105,.25)}.invalid-feedback{color:#dc2626;display:none;font-size:.875em;margin-top:.25rem;width:100%}.invalid-tooltip{background-color:rgba(220,38,38,.9);border-radius:.25rem;color:#fff;display:none;font-size:.875rem;left:0;line-height:1.5;margin-top:.1rem;max-width:100%;padding:.25rem .5rem;position:absolute;top:100%;z-index:5}.form-row>.col>.invalid-tooltip,.form-row>[class*=col-]>.invalid-tooltip{left:5px}.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip,.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip{display:block}.form-control.is-invalid,.was-validated .form-control:invalid{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc2626'%3E%3Ccircle cx='6' cy='6' r='4.5'/%3E%3Cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3E%3Ccircle cx='6' cy='8.2' r='.6' fill='%23dc2626' stroke='none'/%3E%3C/svg%3E");background-position:right calc(.375em + .1875rem) center;background-repeat:no-repeat;background-size:calc(.75em + .375rem) calc(.75em + .375rem);border-color:#dc2626;padding-right:calc(1.5em + .75rem)!important}.form-control.is-invalid:focus,.was-validated .form-control:invalid:focus{border-color:#dc2626;box-shadow:0 0 0 .2rem rgba(220,38,38,.25)}.was-validated select.form-control:invalid,select.form-control.is-invalid{background-position:right 1.5rem center;padding-right:3rem!important}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem);padding-right:calc(1.5em + .75rem)}.custom-select.is-invalid,.was-validated .custom-select:invalid{background:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5'%3E%3Cpath fill='%231f2937' d='M2 0 0 2h4zm0 5L0 3h4z'/%3E%3C/svg%3E") right .75rem center/8px 10px no-repeat,#1f2937 url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc2626'%3E%3Ccircle cx='6' cy='6' r='4.5'/%3E%3Cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3E%3Ccircle cx='6' cy='8.2' r='.6' fill='%23dc2626' stroke='none'/%3E%3C/svg%3E") center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem) no-repeat;border-color:#dc2626;padding-right:calc(.75em + 2.3125rem)!important}.custom-select.is-invalid:focus,.was-validated .custom-select:invalid:focus{border-color:#dc2626;box-shadow:0 0 0 .2rem rgba(220,38,38,.25)}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:#dc2626}.form-check-input.is-invalid~.invalid-feedback,.form-check-input.is-invalid~.invalid-tooltip,.was-validated .form-check-input:invalid~.invalid-feedback,.was-validated .form-check-input:invalid~.invalid-tooltip{display:block}.custom-control-input.is-invalid~.custom-control-label,.was-validated .custom-control-input:invalid~.custom-control-label{color:#dc2626}.custom-control-input.is-invalid~.custom-control-label:before,.was-validated .custom-control-input:invalid~.custom-control-label:before{border-color:#dc2626}.custom-control-input.is-invalid:checked~.custom-control-label:before,.was-validated .custom-control-input:invalid:checked~.custom-control-label:before{background-color:#e35252;border-color:#e35252}.custom-control-input.is-invalid:focus~.custom-control-label:before,.was-validated .custom-control-input:invalid:focus~.custom-control-label:before{box-shadow:0 0 0 .2rem rgba(220,38,38,.25)}.custom-control-input.is-invalid:focus:not(:checked)~.custom-control-label:before,.was-validated .custom-control-input:invalid:focus:not(:checked)~.custom-control-label:before{border-color:#dc2626}.custom-file-input.is-invalid~.custom-file-label,.was-validated .custom-file-input:invalid~.custom-file-label{border-color:#dc2626}.custom-file-input.is-invalid:focus~.custom-file-label,.was-validated .custom-file-input:invalid:focus~.custom-file-label{border-color:#dc2626;box-shadow:0 0 0 .2rem rgba(220,38,38,.25)}.form-inline{align-items:center;display:flex;flex-flow:row wrap}.form-inline .form-check{width:100%}@media (min-width:2px){.form-inline label{justify-content:center}.form-inline .form-group,.form-inline label{align-items:center;display:flex;margin-bottom:0}.form-inline .form-group{flex:0 0 auto;flex-flow:row wrap}.form-inline .form-control{display:inline-block;vertical-align:middle;width:auto}.form-inline .form-control-plaintext{display:inline-block}.form-inline .custom-select,.form-inline .input-group{width:auto}.form-inline .form-check{align-items:center;display:flex;justify-content:center;padding-left:0;width:auto}.form-inline .form-check-input{flex-shrink:0;margin-left:0;margin-right:.25rem;margin-top:0;position:relative}.form-inline .custom-control{align-items:center;justify-content:center}.form-inline .custom-control-label{margin-bottom:0}}.btn{background-color:transparent;border:1px solid transparent;border-radius:.25rem;color:#f3f4f6;display:inline-block;font-size:1rem;font-weight:400;line-height:1.5;padding:.375rem .75rem;text-align:center;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-user-select:none;-moz-user-select:none;user-select:none;vertical-align:middle}@media (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:#f3f4f6;text-decoration:none}.btn.focus,.btn:focus{box-shadow:0 0 0 .2rem rgba(64,64,200,.25);outline:0}.btn.disabled,.btn:disabled{opacity:.65}.btn:not(:disabled):not(.disabled){cursor:pointer}a.btn.disabled,fieldset:disabled a.btn{pointer-events:none}.btn-primary{background-color:#4040c8;border-color:#4040c8;color:#fff}.btn-primary.focus,.btn-primary:focus,.btn-primary:hover{background-color:#3232af;border-color:#3030a5;color:#fff}.btn-primary.focus,.btn-primary:focus{box-shadow:0 0 0 .2rem rgba(93,93,208,.5)}.btn-primary.disabled,.btn-primary:disabled{background-color:#4040c8;border-color:#4040c8;color:#fff}.btn-primary:not(:disabled):not(.disabled).active,.btn-primary:not(:disabled):not(.disabled):active,.show>.btn-primary.dropdown-toggle{background-color:#3030a5;border-color:#2d2d9b;color:#fff}.btn-primary:not(:disabled):not(.disabled).active:focus,.btn-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(93,93,208,.5)}.btn-secondary{background-color:#4b5563;border-color:#4b5563;color:#fff}.btn-secondary.focus,.btn-secondary:focus,.btn-secondary:hover{background-color:#3b424d;border-color:#353c46;color:#fff}.btn-secondary.focus,.btn-secondary:focus{box-shadow:0 0 0 .2rem hsla(213,9%,44%,.5)}.btn-secondary.disabled,.btn-secondary:disabled{background-color:#4b5563;border-color:#4b5563;color:#fff}.btn-secondary:not(:disabled):not(.disabled).active,.btn-secondary:not(:disabled):not(.disabled):active,.show>.btn-secondary.dropdown-toggle{background-color:#353c46;border-color:#30363f;color:#fff}.btn-secondary:not(:disabled):not(.disabled).active:focus,.btn-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem hsla(213,9%,44%,.5)}.btn-success{background-color:#059669;border-color:#059669;color:#fff}.btn-success.focus,.btn-success:focus,.btn-success:hover{background-color:#04714f;border-color:#036546;color:#fff}.btn-success.focus,.btn-success:focus{box-shadow:0 0 0 .2rem rgba(43,166,128,.5)}.btn-success.disabled,.btn-success:disabled{background-color:#059669;border-color:#059669;color:#fff}.btn-success:not(:disabled):not(.disabled).active,.btn-success:not(:disabled):not(.disabled):active,.show>.btn-success.dropdown-toggle{background-color:#036546;border-color:#03583e;color:#fff}.btn-success:not(:disabled):not(.disabled).active:focus,.btn-success:not(:disabled):not(.disabled):active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(43,166,128,.5)}.btn-info{background-color:#2563eb;border-color:#2563eb;color:#fff}.btn-info.focus,.btn-info:focus,.btn-info:hover{background-color:#1451d6;border-color:#134cca;color:#fff}.btn-info.focus,.btn-info:focus{box-shadow:0 0 0 .2rem rgba(70,122,238,.5)}.btn-info.disabled,.btn-info:disabled{background-color:#2563eb;border-color:#2563eb;color:#fff}.btn-info:not(:disabled):not(.disabled).active,.btn-info:not(:disabled):not(.disabled):active,.show>.btn-info.dropdown-toggle{background-color:#134cca;border-color:#1248bf;color:#fff}.btn-info:not(:disabled):not(.disabled).active:focus,.btn-info:not(:disabled):not(.disabled):active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(70,122,238,.5)}.btn-warning{background-color:#d97706;border-color:#d97706;color:#fff}.btn-warning.focus,.btn-warning:focus,.btn-warning:hover{background-color:#b46305;border-color:#a75c05;color:#fff}.btn-warning.focus,.btn-warning:focus{box-shadow:0 0 0 .2rem rgba(223,139,43,.5)}.btn-warning.disabled,.btn-warning:disabled{background-color:#d97706;border-color:#d97706;color:#fff}.btn-warning:not(:disabled):not(.disabled).active,.btn-warning:not(:disabled):not(.disabled):active,.show>.btn-warning.dropdown-toggle{background-color:#a75c05;border-color:#9b5504;color:#fff}.btn-warning:not(:disabled):not(.disabled).active:focus,.btn-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(223,139,43,.5)}.btn-danger{background-color:#dc2626;border-color:#dc2626;color:#fff}.btn-danger.focus,.btn-danger:focus,.btn-danger:hover{background-color:#bd1f1f;border-color:#b21d1d;color:#fff}.btn-danger.focus,.btn-danger:focus{box-shadow:0 0 0 .2rem rgba(225,71,71,.5)}.btn-danger.disabled,.btn-danger:disabled{background-color:#dc2626;border-color:#dc2626;color:#fff}.btn-danger:not(:disabled):not(.disabled).active,.btn-danger:not(:disabled):not(.disabled):active,.show>.btn-danger.dropdown-toggle{background-color:#b21d1d;border-color:#a71b1b;color:#fff}.btn-danger:not(:disabled):not(.disabled).active:focus,.btn-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(225,71,71,.5)}.btn-light{background-color:#f3f4f6;border-color:#f3f4f6;color:#111827}.btn-light.focus,.btn-light:focus,.btn-light:hover{background-color:#dde0e6;border-color:#d6d9e0;color:#111827}.btn-light.focus,.btn-light:focus{box-shadow:0 0 0 .2rem hsla(220,7%,83%,.5)}.btn-light.disabled,.btn-light:disabled{background-color:#f3f4f6;border-color:#f3f4f6;color:#111827}.btn-light:not(:disabled):not(.disabled).active,.btn-light:not(:disabled):not(.disabled):active,.show>.btn-light.dropdown-toggle{background-color:#d6d9e0;border-color:#cfd3db;color:#111827}.btn-light:not(:disabled):not(.disabled).active:focus,.btn-light:not(:disabled):not(.disabled):active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem hsla(220,7%,83%,.5)}.btn-dark{background-color:#1f2937;border-color:#1f2937;color:#fff}.btn-dark.focus,.btn-dark:focus,.btn-dark:hover{background-color:#11171f;border-color:#0d1116;color:#fff}.btn-dark.focus,.btn-dark:focus{box-shadow:0 0 0 .2rem rgba(65,73,85,.5)}.btn-dark.disabled,.btn-dark:disabled{background-color:#1f2937;border-color:#1f2937;color:#fff}.btn-dark:not(:disabled):not(.disabled).active,.btn-dark:not(:disabled):not(.disabled):active,.show>.btn-dark.dropdown-toggle{background-color:#0d1116;border-color:#080b0e;color:#fff}.btn-dark:not(:disabled):not(.disabled).active:focus,.btn-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(65,73,85,.5)}.btn-outline-primary{border-color:#4040c8;color:#4040c8}.btn-outline-primary:hover{background-color:#4040c8;border-color:#4040c8;color:#fff}.btn-outline-primary.focus,.btn-outline-primary:focus{box-shadow:0 0 0 .2rem rgba(64,64,200,.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{background-color:transparent;color:#4040c8}.btn-outline-primary:not(:disabled):not(.disabled).active,.btn-outline-primary:not(:disabled):not(.disabled):active,.show>.btn-outline-primary.dropdown-toggle{background-color:#4040c8;border-color:#4040c8;color:#fff}.btn-outline-primary:not(:disabled):not(.disabled).active:focus,.btn-outline-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(64,64,200,.5)}.btn-outline-secondary{border-color:#4b5563;color:#4b5563}.btn-outline-secondary:hover{background-color:#4b5563;border-color:#4b5563;color:#fff}.btn-outline-secondary.focus,.btn-outline-secondary:focus{box-shadow:0 0 0 .2rem rgba(75,85,99,.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{background-color:transparent;color:#4b5563}.btn-outline-secondary:not(:disabled):not(.disabled).active,.btn-outline-secondary:not(:disabled):not(.disabled):active,.show>.btn-outline-secondary.dropdown-toggle{background-color:#4b5563;border-color:#4b5563;color:#fff}.btn-outline-secondary:not(:disabled):not(.disabled).active:focus,.btn-outline-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(75,85,99,.5)}.btn-outline-success{border-color:#059669;color:#059669}.btn-outline-success:hover{background-color:#059669;border-color:#059669;color:#fff}.btn-outline-success.focus,.btn-outline-success:focus{box-shadow:0 0 0 .2rem rgba(5,150,105,.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{background-color:transparent;color:#059669}.btn-outline-success:not(:disabled):not(.disabled).active,.btn-outline-success:not(:disabled):not(.disabled):active,.show>.btn-outline-success.dropdown-toggle{background-color:#059669;border-color:#059669;color:#fff}.btn-outline-success:not(:disabled):not(.disabled).active:focus,.btn-outline-success:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(5,150,105,.5)}.btn-outline-info{border-color:#2563eb;color:#2563eb}.btn-outline-info:hover{background-color:#2563eb;border-color:#2563eb;color:#fff}.btn-outline-info.focus,.btn-outline-info:focus{box-shadow:0 0 0 .2rem rgba(37,99,235,.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{background-color:transparent;color:#2563eb}.btn-outline-info:not(:disabled):not(.disabled).active,.btn-outline-info:not(:disabled):not(.disabled):active,.show>.btn-outline-info.dropdown-toggle{background-color:#2563eb;border-color:#2563eb;color:#fff}.btn-outline-info:not(:disabled):not(.disabled).active:focus,.btn-outline-info:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(37,99,235,.5)}.btn-outline-warning{border-color:#d97706;color:#d97706}.btn-outline-warning:hover{background-color:#d97706;border-color:#d97706;color:#fff}.btn-outline-warning.focus,.btn-outline-warning:focus{box-shadow:0 0 0 .2rem rgba(217,119,6,.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{background-color:transparent;color:#d97706}.btn-outline-warning:not(:disabled):not(.disabled).active,.btn-outline-warning:not(:disabled):not(.disabled):active,.show>.btn-outline-warning.dropdown-toggle{background-color:#d97706;border-color:#d97706;color:#fff}.btn-outline-warning:not(:disabled):not(.disabled).active:focus,.btn-outline-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(217,119,6,.5)}.btn-outline-danger{border-color:#dc2626;color:#dc2626}.btn-outline-danger:hover{background-color:#dc2626;border-color:#dc2626;color:#fff}.btn-outline-danger.focus,.btn-outline-danger:focus{box-shadow:0 0 0 .2rem rgba(220,38,38,.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{background-color:transparent;color:#dc2626}.btn-outline-danger:not(:disabled):not(.disabled).active,.btn-outline-danger:not(:disabled):not(.disabled):active,.show>.btn-outline-danger.dropdown-toggle{background-color:#dc2626;border-color:#dc2626;color:#fff}.btn-outline-danger:not(:disabled):not(.disabled).active:focus,.btn-outline-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(220,38,38,.5)}.btn-outline-light{border-color:#f3f4f6;color:#f3f4f6}.btn-outline-light:hover{background-color:#f3f4f6;border-color:#f3f4f6;color:#111827}.btn-outline-light.focus,.btn-outline-light:focus{box-shadow:0 0 0 .2rem rgba(243,244,246,.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{background-color:transparent;color:#f3f4f6}.btn-outline-light:not(:disabled):not(.disabled).active,.btn-outline-light:not(:disabled):not(.disabled):active,.show>.btn-outline-light.dropdown-toggle{background-color:#f3f4f6;border-color:#f3f4f6;color:#111827}.btn-outline-light:not(:disabled):not(.disabled).active:focus,.btn-outline-light:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(243,244,246,.5)}.btn-outline-dark{border-color:#1f2937;color:#1f2937}.btn-outline-dark:hover{background-color:#1f2937;border-color:#1f2937;color:#fff}.btn-outline-dark.focus,.btn-outline-dark:focus{box-shadow:0 0 0 .2rem rgba(31,41,55,.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{background-color:transparent;color:#1f2937}.btn-outline-dark:not(:disabled):not(.disabled).active,.btn-outline-dark:not(:disabled):not(.disabled):active,.show>.btn-outline-dark.dropdown-toggle{background-color:#1f2937;border-color:#1f2937;color:#fff}.btn-outline-dark:not(:disabled):not(.disabled).active:focus,.btn-outline-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(31,41,55,.5)}.btn-link{color:#818cf8;font-weight:400;text-decoration:none}.btn-link:hover{color:#a5b4fc}.btn-link.focus,.btn-link:focus,.btn-link:hover{text-decoration:underline}.btn-link.disabled,.btn-link:disabled{color:#4b5563;pointer-events:none}.btn-group-lg>.btn,.btn-lg{border-radius:6px;font-size:1.25rem;line-height:1.5;padding:.5rem 1rem}.btn-group-sm>.btn,.btn-sm{border-radius:.2rem;font-size:.875rem;line-height:1.5;padding:.25rem .5rem}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:.5rem}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{height:0;overflow:hidden;position:relative;transition:height .35s ease}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.collapsing.width{height:auto;transition:width .35s ease;width:0}@media (prefers-reduced-motion:reduce){.collapsing.width{transition:none}}.dropdown,.dropleft,.dropright,.dropup{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle:after{border-bottom:0;border-left:.3em solid transparent;border-right:.3em solid transparent;border-top:.3em solid;content:"";display:inline-block;margin-left:.255em;vertical-align:.255em}.dropdown-toggle:empty:after{margin-left:0}.dropdown-menu{background-clip:padding-box;background-color:#374151;border:1px solid rgba(0,0,0,.15);border-radius:.25rem;color:#f3f4f6;display:none;float:left;font-size:1rem;left:0;list-style:none;margin:.125rem 0 0;min-width:10rem;padding:.5rem 0;position:absolute;text-align:left;top:100%;z-index:1000}.dropdown-menu-left{left:0;right:auto}.dropdown-menu-right{left:auto;right:0}@media (min-width:2px){.dropdown-menu-sm-left{left:0;right:auto}.dropdown-menu-sm-right{left:auto;right:0}}@media (min-width:8px){.dropdown-menu-md-left{left:0;right:auto}.dropdown-menu-md-right{left:auto;right:0}}@media (min-width:9px){.dropdown-menu-lg-left{left:0;right:auto}.dropdown-menu-lg-right{left:auto;right:0}}@media (min-width:10px){.dropdown-menu-xl-left{left:0;right:auto}.dropdown-menu-xl-right{left:auto;right:0}}.dropup .dropdown-menu{bottom:100%;margin-bottom:.125rem;margin-top:0;top:auto}.dropup .dropdown-toggle:after{border-bottom:.3em solid;border-left:.3em solid transparent;border-right:.3em solid transparent;border-top:0;content:"";display:inline-block;margin-left:.255em;vertical-align:.255em}.dropup .dropdown-toggle:empty:after{margin-left:0}.dropright .dropdown-menu{left:100%;margin-left:.125rem;margin-top:0;right:auto;top:0}.dropright .dropdown-toggle:after{border-bottom:.3em solid transparent;border-left:.3em solid;border-right:0;border-top:.3em solid transparent;content:"";display:inline-block;margin-left:.255em;vertical-align:.255em}.dropright .dropdown-toggle:empty:after{margin-left:0}.dropright .dropdown-toggle:after{vertical-align:0}.dropleft .dropdown-menu{left:auto;margin-right:.125rem;margin-top:0;right:100%;top:0}.dropleft .dropdown-toggle:after{content:"";display:inline-block;display:none;margin-left:.255em;vertical-align:.255em}.dropleft .dropdown-toggle:before{border-bottom:.3em solid transparent;border-right:.3em solid;border-top:.3em solid transparent;content:"";display:inline-block;margin-right:.255em;vertical-align:.255em}.dropleft .dropdown-toggle:empty:after{margin-left:0}.dropleft .dropdown-toggle:before{vertical-align:0}.dropdown-menu[x-placement^=bottom],.dropdown-menu[x-placement^=left],.dropdown-menu[x-placement^=right],.dropdown-menu[x-placement^=top]{bottom:auto;right:auto}.dropdown-divider{border-top:1px solid #e5e7eb;height:0;margin:.5rem 0;overflow:hidden}.dropdown-item{background-color:transparent;border:0;clear:both;color:#fff;display:block;font-weight:400;padding:.25rem 1.5rem;text-align:inherit;white-space:nowrap;width:100%}.dropdown-item:focus,.dropdown-item:hover{background-color:#e5e7eb;color:#090d15;text-decoration:none}.dropdown-item.active,.dropdown-item:active{background-color:#4040c8;color:#fff;text-decoration:none}.dropdown-item.disabled,.dropdown-item:disabled{background-color:transparent;color:#6b7280;pointer-events:none}.dropdown-menu.show{display:block}.dropdown-header{color:#4b5563;display:block;font-size:.875rem;margin-bottom:0;padding:.5rem 1.5rem;white-space:nowrap}.dropdown-item-text{color:#fff;display:block;padding:.25rem 1.5rem}.btn-group,.btn-group-vertical{display:inline-flex;position:relative;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{flex:1 1 auto;position:relative}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:1}.btn-toolbar{display:flex;flex-wrap:wrap;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn-group:not(:first-child),.btn-group>.btn:not(:first-child){margin-left:-1px}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-top-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.dropdown-toggle-split{padding-left:.5625rem;padding-right:.5625rem}.dropdown-toggle-split:after,.dropright .dropdown-toggle-split:after,.dropup .dropdown-toggle-split:after{margin-left:0}.dropleft .dropdown-toggle-split:before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-left:.375rem;padding-right:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-left:.75rem;padding-right:.75rem}.btn-group-vertical{align-items:flex-start;flex-direction:column;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-left-radius:0;border-bottom-right-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn:not(:first-child){border-top-left-radius:0;border-top-right-radius:0}.btn-group-toggle>.btn,.btn-group-toggle>.btn-group>.btn{margin-bottom:0}.btn-group-toggle>.btn input[type=checkbox],.btn-group-toggle>.btn input[type=radio],.btn-group-toggle>.btn-group>.btn input[type=checkbox],.btn-group-toggle>.btn-group>.btn input[type=radio]{clip:rect(0,0,0,0);pointer-events:none;position:absolute}.input-group{align-items:stretch;display:flex;flex-wrap:wrap;position:relative;width:100%}.input-group>.custom-file,.input-group>.custom-select,.input-group>.form-control,.input-group>.form-control-plaintext{flex:1 1 auto;margin-bottom:0;min-width:0;position:relative;width:1%}.input-group>.custom-file+.custom-file,.input-group>.custom-file+.custom-select,.input-group>.custom-file+.form-control,.input-group>.custom-select+.custom-file,.input-group>.custom-select+.custom-select,.input-group>.custom-select+.form-control,.input-group>.form-control+.custom-file,.input-group>.form-control+.custom-select,.input-group>.form-control+.form-control,.input-group>.form-control-plaintext+.custom-file,.input-group>.form-control-plaintext+.custom-select,.input-group>.form-control-plaintext+.form-control{margin-left:-1px}.input-group>.custom-file .custom-file-input:focus~.custom-file-label,.input-group>.custom-select:focus,.input-group>.form-control:focus{z-index:3}.input-group>.custom-file .custom-file-input:focus{z-index:4}.input-group>.custom-select:not(:first-child),.input-group>.form-control:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.input-group>.custom-file{align-items:center;display:flex}.input-group>.custom-file:not(:last-child) .custom-file-label,.input-group>.custom-file:not(:last-child) .custom-file-label:after{border-bottom-right-radius:0;border-top-right-radius:0}.input-group>.custom-file:not(:first-child) .custom-file-label{border-bottom-left-radius:0;border-top-left-radius:0}.input-group.has-validation>.custom-file:nth-last-child(n+3) .custom-file-label,.input-group.has-validation>.custom-file:nth-last-child(n+3) .custom-file-label:after,.input-group.has-validation>.custom-select:nth-last-child(n+3),.input-group.has-validation>.form-control:nth-last-child(n+3),.input-group:not(.has-validation)>.custom-file:not(:last-child) .custom-file-label,.input-group:not(.has-validation)>.custom-file:not(:last-child) .custom-file-label:after,.input-group:not(.has-validation)>.custom-select:not(:last-child),.input-group:not(.has-validation)>.form-control:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.input-group-append,.input-group-prepend{display:flex}.input-group-append .btn,.input-group-prepend .btn{position:relative;z-index:2}.input-group-append .btn:focus,.input-group-prepend .btn:focus{z-index:3}.input-group-append .btn+.btn,.input-group-append .btn+.input-group-text,.input-group-append .input-group-text+.btn,.input-group-append .input-group-text+.input-group-text,.input-group-prepend .btn+.btn,.input-group-prepend .btn+.input-group-text,.input-group-prepend .input-group-text+.btn,.input-group-prepend .input-group-text+.input-group-text{margin-left:-1px}.input-group-prepend{margin-right:-1px}.input-group-append{margin-left:-1px}.input-group-text{align-items:center;background-color:#e5e7eb;border:1px solid #4b5563;border-radius:.25rem;color:#e5e7eb;display:flex;font-size:1rem;font-weight:400;line-height:1.5;margin-bottom:0;padding:.375rem .75rem;text-align:center;white-space:nowrap}.input-group-text input[type=checkbox],.input-group-text input[type=radio]{margin-top:0}.input-group-lg>.custom-select,.input-group-lg>.form-control:not(textarea){height:calc(1.5em + 1rem + 2px)}.input-group-lg>.custom-select,.input-group-lg>.form-control,.input-group-lg>.input-group-append>.btn,.input-group-lg>.input-group-append>.input-group-text,.input-group-lg>.input-group-prepend>.btn,.input-group-lg>.input-group-prepend>.input-group-text{border-radius:6px;font-size:1.25rem;line-height:1.5;padding:.5rem 1rem}.input-group-sm>.custom-select,.input-group-sm>.form-control:not(textarea){height:calc(1.5em + .5rem + 2px)}.input-group-sm>.custom-select,.input-group-sm>.form-control,.input-group-sm>.input-group-append>.btn,.input-group-sm>.input-group-append>.input-group-text,.input-group-sm>.input-group-prepend>.btn,.input-group-sm>.input-group-prepend>.input-group-text{border-radius:.2rem;font-size:.875rem;line-height:1.5;padding:.25rem .5rem}.input-group-lg>.custom-select,.input-group-sm>.custom-select{padding-right:1.75rem}.input-group.has-validation>.input-group-append:nth-last-child(n+3)>.btn,.input-group.has-validation>.input-group-append:nth-last-child(n+3)>.input-group-text,.input-group:not(.has-validation)>.input-group-append:not(:last-child)>.btn,.input-group:not(.has-validation)>.input-group-append:not(:last-child)>.input-group-text,.input-group>.input-group-append:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group>.input-group-append:last-child>.input-group-text:not(:last-child),.input-group>.input-group-prepend>.btn,.input-group>.input-group-prepend>.input-group-text{border-bottom-right-radius:0;border-top-right-radius:0}.input-group>.input-group-append>.btn,.input-group>.input-group-append>.input-group-text,.input-group>.input-group-prepend:first-child>.btn:not(:first-child),.input-group>.input-group-prepend:first-child>.input-group-text:not(:first-child),.input-group>.input-group-prepend:not(:first-child)>.btn,.input-group>.input-group-prepend:not(:first-child)>.input-group-text{border-bottom-left-radius:0;border-top-left-radius:0}.custom-control{display:block;min-height:1.5rem;padding-left:1.5rem;position:relative;-webkit-print-color-adjust:exact;print-color-adjust:exact;z-index:1}.custom-control-inline{display:inline-flex;margin-right:1rem}.custom-control-input{height:1.25rem;left:0;opacity:0;position:absolute;width:1rem;z-index:-1}.custom-control-input:checked~.custom-control-label:before{background-color:#4040c8;border-color:#4040c8;color:#fff}.custom-control-input:focus~.custom-control-label:before{box-shadow:0 0 0 .2rem rgba(64,64,200,.25)}.custom-control-input:focus:not(:checked)~.custom-control-label:before{border-color:#a3a3e5}.custom-control-input:not(:disabled):active~.custom-control-label:before{background-color:#cbcbf0;border-color:#cbcbf0;color:#fff}.custom-control-input:disabled~.custom-control-label,.custom-control-input[disabled]~.custom-control-label{color:#4b5563}.custom-control-input:disabled~.custom-control-label:before,.custom-control-input[disabled]~.custom-control-label:before{background-color:#e5e7eb}.custom-control-label{margin-bottom:0;position:relative;vertical-align:top}.custom-control-label:before{background-color:#1f2937;border:1px solid #6b7280;pointer-events:none}.custom-control-label:after,.custom-control-label:before{content:"";display:block;height:1rem;left:-1.5rem;position:absolute;top:.25rem;width:1rem}.custom-control-label:after{background:50%/50% 50% no-repeat}.custom-checkbox .custom-control-label:before{border-radius:.25rem}.custom-checkbox .custom-control-input:checked~.custom-control-label:after{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8'%3E%3Cpath fill='%23fff' d='m6.564.75-3.59 3.612-1.538-1.55L0 4.26l2.974 2.99L8 2.193z'/%3E%3C/svg%3E")}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label:before{background-color:#4040c8;border-color:#4040c8}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label:after{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='4' height='4'%3E%3Cpath stroke='%23fff' d='M0 2h4'/%3E%3C/svg%3E")}.custom-checkbox .custom-control-input:disabled:checked~.custom-control-label:before{background-color:rgba(64,64,200,.5)}.custom-checkbox .custom-control-input:disabled:indeterminate~.custom-control-label:before{background-color:rgba(64,64,200,.5)}.custom-radio .custom-control-label:before{border-radius:50%}.custom-radio .custom-control-input:checked~.custom-control-label:after{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='-4 -4 8 8'%3E%3Ccircle r='3' fill='%23fff'/%3E%3C/svg%3E")}.custom-radio .custom-control-input:disabled:checked~.custom-control-label:before{background-color:rgba(64,64,200,.5)}.custom-switch{padding-left:2.25rem}.custom-switch .custom-control-label:before{border-radius:.5rem;left:-2.25rem;pointer-events:all;width:1.75rem}.custom-switch .custom-control-label:after{background-color:#6b7280;border-radius:.5rem;height:calc(1rem - 4px);left:calc(-2.25rem + 2px);top:calc(.25rem + 2px);transition:transform .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;width:calc(1rem - 4px)}@media (prefers-reduced-motion:reduce){.custom-switch .custom-control-label:after{transition:none}}.custom-switch .custom-control-input:checked~.custom-control-label:after{background-color:#1f2937;transform:translateX(.75rem)}.custom-switch .custom-control-input:disabled:checked~.custom-control-label:before{background-color:rgba(64,64,200,.5)}.custom-select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#1f2937 url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5'%3E%3Cpath fill='%231f2937' d='M2 0 0 2h4zm0 5L0 3h4z'/%3E%3C/svg%3E") right .75rem center/8px 10px no-repeat;border:1px solid #4b5563;border-radius:.25rem;color:#e5e7eb;display:inline-block;font-size:1rem;font-weight:400;height:calc(1.5em + .75rem + 2px);line-height:1.5;padding:.375rem 1.75rem .375rem .75rem;vertical-align:middle;width:100%}.custom-select:focus{border-color:#a3a3e5;box-shadow:0 0 0 .2rem rgba(64,64,200,.25);outline:0}.custom-select:focus::-ms-value{background-color:#1f2937;color:#e5e7eb}.custom-select[multiple],.custom-select[size]:not([size="1"]){background-image:none;height:auto;padding-right:.75rem}.custom-select:disabled{background-color:#e5e7eb;color:#4b5563}.custom-select::-ms-expand{display:none}.custom-select:-moz-focusring{color:transparent;text-shadow:0 0 0 #e5e7eb}.custom-select-sm{font-size:.875rem;height:calc(1.5em + .5rem + 2px);padding-bottom:.25rem;padding-left:.5rem;padding-top:.25rem}.custom-select-lg{font-size:1.25rem;height:calc(1.5em + 1rem + 2px);padding-bottom:.5rem;padding-left:1rem;padding-top:.5rem}.custom-file{display:inline-block;margin-bottom:0}.custom-file,.custom-file-input{height:calc(1.5em + .75rem + 2px);position:relative;width:100%}.custom-file-input{margin:0;opacity:0;overflow:hidden;z-index:2}.custom-file-input:focus~.custom-file-label{border-color:#a3a3e5;box-shadow:0 0 0 .2rem rgba(64,64,200,.25)}.custom-file-input:disabled~.custom-file-label,.custom-file-input[disabled]~.custom-file-label{background-color:#e5e7eb}.custom-file-input:lang(en)~.custom-file-label:after{content:"Browse"}.custom-file-input~.custom-file-label[data-browse]:after{content:attr(data-browse)}.custom-file-label{background-color:#1f2937;border:1px solid #4b5563;border-radius:.25rem;font-weight:400;height:calc(1.5em + .75rem + 2px);left:0;overflow:hidden;z-index:1}.custom-file-label,.custom-file-label:after{color:#e5e7eb;line-height:1.5;padding:.375rem .75rem;position:absolute;right:0;top:0}.custom-file-label:after{background-color:#e5e7eb;border-left:inherit;border-radius:0 .25rem .25rem 0;bottom:0;content:"Browse";display:block;height:calc(1.5em + .75rem);z-index:3}.custom-range{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:transparent;height:1.4rem;padding:0;width:100%}.custom-range:focus{outline:0}.custom-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #111827,0 0 0 .2rem rgba(64,64,200,.25)}.custom-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #111827,0 0 0 .2rem rgba(64,64,200,.25)}.custom-range:focus::-ms-thumb{box-shadow:0 0 0 1px #111827,0 0 0 .2rem rgba(64,64,200,.25)}.custom-range::-moz-focus-outer{border:0}.custom-range::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;background-color:#4040c8;border:0;border-radius:1rem;height:1rem;margin-top:-.25rem;-webkit-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;width:1rem}@media (prefers-reduced-motion:reduce){.custom-range::-webkit-slider-thumb{-webkit-transition:none;transition:none}}.custom-range::-webkit-slider-thumb:active{background-color:#cbcbf0}.custom-range::-webkit-slider-runnable-track{background-color:#d1d5db;border-color:transparent;border-radius:1rem;color:transparent;cursor:pointer;height:.5rem;width:100%}.custom-range::-moz-range-thumb{-moz-appearance:none;appearance:none;background-color:#4040c8;border:0;border-radius:1rem;height:1rem;-moz-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;width:1rem}@media (prefers-reduced-motion:reduce){.custom-range::-moz-range-thumb{-moz-transition:none;transition:none}}.custom-range::-moz-range-thumb:active{background-color:#cbcbf0}.custom-range::-moz-range-track{background-color:#d1d5db;border-color:transparent;border-radius:1rem;color:transparent;cursor:pointer;height:.5rem;width:100%}.custom-range::-ms-thumb{appearance:none;background-color:#4040c8;border:0;border-radius:1rem;height:1rem;margin-left:.2rem;margin-right:.2rem;margin-top:0;-ms-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;width:1rem}@media (prefers-reduced-motion:reduce){.custom-range::-ms-thumb{-ms-transition:none;transition:none}}.custom-range::-ms-thumb:active{background-color:#cbcbf0}.custom-range::-ms-track{background-color:transparent;border-color:transparent;border-width:.5rem;color:transparent;cursor:pointer;height:.5rem;width:100%}.custom-range::-ms-fill-lower,.custom-range::-ms-fill-upper{background-color:#d1d5db;border-radius:1rem}.custom-range::-ms-fill-upper{margin-right:15px}.custom-range:disabled::-webkit-slider-thumb{background-color:#6b7280}.custom-range:disabled::-webkit-slider-runnable-track{cursor:default}.custom-range:disabled::-moz-range-thumb{background-color:#6b7280}.custom-range:disabled::-moz-range-track{cursor:default}.custom-range:disabled::-ms-thumb{background-color:#6b7280}.custom-control-label:before,.custom-file-label,.custom-select{transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.custom-control-label:before,.custom-file-label,.custom-select{transition:none}}.nav{display:flex;flex-wrap:wrap;list-style:none;margin-bottom:0;padding-left:0}.nav-link{display:block;padding:.5rem 1rem}.nav-link:focus,.nav-link:hover{text-decoration:none}.nav-link.disabled{color:#4b5563;cursor:default;pointer-events:none}.nav-tabs{border-bottom:1px solid #d1d5db}.nav-tabs .nav-link{background-color:transparent;border:1px solid transparent;border-top-left-radius:.25rem;border-top-right-radius:.25rem;margin-bottom:-1px}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{border-color:#e5e7eb #e5e7eb #d1d5db;isolation:isolate}.nav-tabs .nav-link.disabled{background-color:transparent;border-color:transparent;color:#4b5563}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{background-color:#111827;border-color:#d1d5db #d1d5db #111827;color:#374151}.nav-tabs .dropdown-menu{border-top-left-radius:0;border-top-right-radius:0;margin-top:-1px}.nav-pills .nav-link{background:none;border:0;border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{background-color:#1f2937;color:#fff}.nav-fill .nav-item,.nav-fill>.nav-link{flex:1 1 auto;text-align:center}.nav-justified .nav-item,.nav-justified>.nav-link{flex-basis:0;flex-grow:1;text-align:center}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{padding:.5rem 1rem;position:relative}.navbar,.navbar .container,.navbar .container-fluid,.navbar .container-lg,.navbar .container-md,.navbar .container-sm,.navbar .container-xl{align-items:center;display:flex;flex-wrap:wrap;justify-content:space-between}.navbar-brand{display:inline-block;font-size:1.25rem;line-height:inherit;margin-right:1rem;padding-bottom:.3125rem;padding-top:.3125rem;white-space:nowrap}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-nav{display:flex;flex-direction:column;list-style:none;margin-bottom:0;padding-left:0}.navbar-nav .nav-link{padding-left:0;padding-right:0}.navbar-nav .dropdown-menu{float:none;position:static}.navbar-text{display:inline-block;padding-bottom:.5rem;padding-top:.5rem}.navbar-collapse{align-items:center;flex-basis:100%;flex-grow:1}.navbar-toggler{background-color:transparent;border:1px solid transparent;border-radius:.25rem;font-size:1.25rem;line-height:1;padding:.25rem .75rem}.navbar-toggler:focus,.navbar-toggler:hover{text-decoration:none}.navbar-toggler-icon{background:50%/100% 100% no-repeat;content:"";display:inline-block;height:1.5em;vertical-align:middle;width:1.5em}.navbar-nav-scroll{max-height:75vh;overflow-y:auto}@media (max-width:1.98px){.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid,.navbar-expand-sm>.container-lg,.navbar-expand-sm>.container-md,.navbar-expand-sm>.container-sm,.navbar-expand-sm>.container-xl{padding-left:0;padding-right:0}}@media (min-width:2px){.navbar-expand-sm{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand-sm .navbar-nav{flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-left:.5rem;padding-right:.5rem}.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid,.navbar-expand-sm>.container-lg,.navbar-expand-sm>.container-md,.navbar-expand-sm>.container-sm,.navbar-expand-sm>.container-xl{flex-wrap:nowrap}.navbar-expand-sm .navbar-nav-scroll{overflow:visible}.navbar-expand-sm .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}}@media (max-width:7.98px){.navbar-expand-md>.container,.navbar-expand-md>.container-fluid,.navbar-expand-md>.container-lg,.navbar-expand-md>.container-md,.navbar-expand-md>.container-sm,.navbar-expand-md>.container-xl{padding-left:0;padding-right:0}}@media (min-width:8px){.navbar-expand-md{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand-md .navbar-nav{flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-left:.5rem;padding-right:.5rem}.navbar-expand-md>.container,.navbar-expand-md>.container-fluid,.navbar-expand-md>.container-lg,.navbar-expand-md>.container-md,.navbar-expand-md>.container-sm,.navbar-expand-md>.container-xl{flex-wrap:nowrap}.navbar-expand-md .navbar-nav-scroll{overflow:visible}.navbar-expand-md .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}}@media (max-width:8.98px){.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid,.navbar-expand-lg>.container-lg,.navbar-expand-lg>.container-md,.navbar-expand-lg>.container-sm,.navbar-expand-lg>.container-xl{padding-left:0;padding-right:0}}@media (min-width:9px){.navbar-expand-lg{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand-lg .navbar-nav{flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-left:.5rem;padding-right:.5rem}.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid,.navbar-expand-lg>.container-lg,.navbar-expand-lg>.container-md,.navbar-expand-lg>.container-sm,.navbar-expand-lg>.container-xl{flex-wrap:nowrap}.navbar-expand-lg .navbar-nav-scroll{overflow:visible}.navbar-expand-lg .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}}@media (max-width:9.98px){.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid,.navbar-expand-xl>.container-lg,.navbar-expand-xl>.container-md,.navbar-expand-xl>.container-sm,.navbar-expand-xl>.container-xl{padding-left:0;padding-right:0}}@media (min-width:10px){.navbar-expand-xl{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand-xl .navbar-nav{flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-left:.5rem;padding-right:.5rem}.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid,.navbar-expand-xl>.container-lg,.navbar-expand-xl>.container-md,.navbar-expand-xl>.container-sm,.navbar-expand-xl>.container-xl{flex-wrap:nowrap}.navbar-expand-xl .navbar-nav-scroll{overflow:visible}.navbar-expand-xl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}}.navbar-expand{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand>.container,.navbar-expand>.container-fluid,.navbar-expand>.container-lg,.navbar-expand>.container-md,.navbar-expand>.container-sm,.navbar-expand>.container-xl{padding-left:0;padding-right:0}.navbar-expand .navbar-nav{flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-left:.5rem;padding-right:.5rem}.navbar-expand>.container,.navbar-expand>.container-fluid,.navbar-expand>.container-lg,.navbar-expand>.container-md,.navbar-expand>.container-sm,.navbar-expand>.container-xl{flex-wrap:nowrap}.navbar-expand .navbar-nav-scroll{overflow:visible}.navbar-expand .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-light .navbar-brand,.navbar-light .navbar-brand:focus,.navbar-light .navbar-brand:hover{color:rgba(0,0,0,.9)}.navbar-light .navbar-nav .nav-link{color:rgba(0,0,0,.5)}.navbar-light .navbar-nav .nav-link:focus,.navbar-light .navbar-nav .nav-link:hover{color:rgba(0,0,0,.7)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(0,0,0,.3)}.navbar-light .navbar-nav .active>.nav-link,.navbar-light .navbar-nav .nav-link.active,.navbar-light .navbar-nav .nav-link.show,.navbar-light .navbar-nav .show>.nav-link{color:rgba(0,0,0,.9)}.navbar-light .navbar-toggler{border-color:rgba(0,0,0,.1);color:rgba(0,0,0,.5)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30'%3E%3Cpath stroke='rgba(0, 0, 0, 0.5)' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E")}.navbar-light .navbar-text{color:rgba(0,0,0,.5)}.navbar-light .navbar-text a,.navbar-light .navbar-text a:focus,.navbar-light .navbar-text a:hover{color:rgba(0,0,0,.9)}.navbar-dark .navbar-brand,.navbar-dark .navbar-brand:focus,.navbar-dark .navbar-brand:hover{color:#fff}.navbar-dark .navbar-nav .nav-link{color:hsla(0,0%,100%,.5)}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:hsla(0,0%,100%,.75)}.navbar-dark .navbar-nav .nav-link.disabled{color:hsla(0,0%,100%,.25)}.navbar-dark .navbar-nav .active>.nav-link,.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .nav-link.show,.navbar-dark .navbar-nav .show>.nav-link{color:#fff}.navbar-dark .navbar-toggler{border-color:hsla(0,0%,100%,.1);color:hsla(0,0%,100%,.5)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30'%3E%3Cpath stroke='rgba(255, 255, 255, 0.5)' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E")}.navbar-dark .navbar-text{color:hsla(0,0%,100%,.5)}.navbar-dark .navbar-text a,.navbar-dark .navbar-text a:focus,.navbar-dark .navbar-text a:hover{color:#fff}.card{word-wrap:break-word;background-clip:border-box;background-color:#1f2937;border:1px solid rgba(0,0,0,.125);border-radius:6px;display:flex;flex-direction:column;min-width:0;position:relative}.card>hr{margin-left:0;margin-right:0}.card>.list-group{border-bottom:inherit;border-top:inherit}.card>.list-group:first-child{border-top-left-radius:5px;border-top-right-radius:5px;border-top-width:0}.card>.list-group:last-child{border-bottom-left-radius:5px;border-bottom-right-radius:5px;border-bottom-width:0}.card>.card-header+.list-group,.card>.list-group+.card-footer{border-top:0}.card-body{flex:1 1 auto;min-height:1px;padding:1.25rem}.card-title{margin-bottom:.75rem}.card-subtitle{margin-top:-.375rem}.card-subtitle,.card-text:last-child{margin-bottom:0}.card-link:hover{text-decoration:none}.card-link+.card-link{margin-left:1.25rem}.card-header{background-color:#374151;border-bottom:1px solid rgba(0,0,0,.125);margin-bottom:0;padding:.75rem 1.25rem}.card-header:first-child{border-radius:5px 5px 0 0}.card-footer{background-color:#374151;border-top:1px solid rgba(0,0,0,.125);padding:.75rem 1.25rem}.card-footer:last-child{border-radius:0 0 5px 5px}.card-header-tabs{border-bottom:0;margin-bottom:-.75rem}.card-header-pills,.card-header-tabs{margin-left:-.625rem;margin-right:-.625rem}.card-img-overlay{border-radius:5px;bottom:0;left:0;padding:1.25rem;position:absolute;right:0;top:0}.card-img,.card-img-bottom,.card-img-top{flex-shrink:0;width:100%}.card-img,.card-img-top{border-top-left-radius:5px;border-top-right-radius:5px}.card-img,.card-img-bottom{border-bottom-left-radius:5px;border-bottom-right-radius:5px}.card-deck .card{margin-bottom:15px}@media (min-width:2px){.card-deck{display:flex;flex-flow:row wrap;margin-left:-15px;margin-right:-15px}.card-deck .card{flex:1 0 0%;margin-bottom:0;margin-left:15px;margin-right:15px}}.card-group>.card{margin-bottom:15px}@media (min-width:2px){.card-group{display:flex;flex-flow:row wrap}.card-group>.card{flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{border-left:0;margin-left:0}.card-group>.card:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.card-group>.card:not(:last-child) .card-header,.card-group>.card:not(:last-child) .card-img-top{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-footer,.card-group>.card:not(:last-child) .card-img-bottom{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.card-group>.card:not(:first-child) .card-header,.card-group>.card:not(:first-child) .card-img-top{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-footer,.card-group>.card:not(:first-child) .card-img-bottom{border-bottom-left-radius:0}}.card-columns .card{margin-bottom:.75rem}@media (min-width:2px){.card-columns{-moz-column-count:3;column-count:3;-moz-column-gap:1.25rem;column-gap:1.25rem;orphans:1;widows:1}.card-columns .card{display:inline-block;width:100%}}.accordion{overflow-anchor:none}.accordion>.card{overflow:hidden}.accordion>.card:not(:last-of-type){border-bottom:0;border-bottom-left-radius:0;border-bottom-right-radius:0}.accordion>.card:not(:first-of-type){border-top-left-radius:0;border-top-right-radius:0}.accordion>.card>.card-header{border-radius:0;margin-bottom:-1px}.breadcrumb{background-color:#e5e7eb;border-radius:.25rem;display:flex;flex-wrap:wrap;list-style:none;margin-bottom:1rem;padding:.75rem 1rem}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item:before{color:#4b5563;content:"/";float:left;padding-right:.5rem}.breadcrumb-item+.breadcrumb-item:hover:before{text-decoration:underline;text-decoration:none}.breadcrumb-item.active{color:#4b5563}.pagination{border-radius:.25rem;display:flex;list-style:none;padding-left:0}.page-link{background-color:#fff;border:1px solid #d1d5db;color:#818cf8;display:block;line-height:1.25;margin-left:-1px;padding:.5rem .75rem;position:relative}.page-link:hover{background-color:#e5e7eb;border-color:#d1d5db;color:#a5b4fc;text-decoration:none;z-index:2}.page-link:focus{box-shadow:0 0 0 .2rem rgba(64,64,200,.25);outline:0;z-index:3}.page-item:first-child .page-link{border-bottom-left-radius:.25rem;border-top-left-radius:.25rem;margin-left:0}.page-item:last-child .page-link{border-bottom-right-radius:.25rem;border-top-right-radius:.25rem}.page-item.active .page-link{background-color:#4040c8;border-color:#4040c8;color:#fff;z-index:3}.page-item.disabled .page-link{background-color:#fff;border-color:#d1d5db;color:#4b5563;cursor:auto;pointer-events:none}.pagination-lg .page-link{font-size:1.25rem;line-height:1.5;padding:.75rem 1.5rem}.pagination-lg .page-item:first-child .page-link{border-bottom-left-radius:6px;border-top-left-radius:6px}.pagination-lg .page-item:last-child .page-link{border-bottom-right-radius:6px;border-top-right-radius:6px}.pagination-sm .page-link{font-size:.875rem;line-height:1.5;padding:.25rem .5rem}.pagination-sm .page-item:first-child .page-link{border-bottom-left-radius:.2rem;border-top-left-radius:.2rem}.pagination-sm .page-item:last-child .page-link{border-bottom-right-radius:.2rem;border-top-right-radius:.2rem}.badge{border-radius:.25rem;display:inline-block;font-size:.875rem;font-weight:600;line-height:1;padding:.25em .4em;text-align:center;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;vertical-align:baseline;white-space:nowrap}@media (prefers-reduced-motion:reduce){.badge{transition:none}}a.badge:focus,a.badge:hover{text-decoration:none}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.badge-pill{border-radius:10rem;padding-left:.6em;padding-right:.6em}.badge-primary{background-color:#4040c8;color:#fff}a.badge-primary:focus,a.badge-primary:hover{background-color:#3030a5;color:#fff}a.badge-primary.focus,a.badge-primary:focus{box-shadow:0 0 0 .2rem rgba(64,64,200,.5);outline:0}.badge-secondary{background-color:#4b5563;color:#fff}a.badge-secondary:focus,a.badge-secondary:hover{background-color:#353c46;color:#fff}a.badge-secondary.focus,a.badge-secondary:focus{box-shadow:0 0 0 .2rem rgba(75,85,99,.5);outline:0}.badge-success{background-color:#059669}a.badge-success:focus,a.badge-success:hover{background-color:#036546;color:#fff}a.badge-success.focus,a.badge-success:focus{box-shadow:0 0 0 .2rem rgba(5,150,105,.5);outline:0}.badge-info{background-color:#2563eb}a.badge-info:focus,a.badge-info:hover{background-color:#134cca;color:#fff}a.badge-info.focus,a.badge-info:focus{box-shadow:0 0 0 .2rem rgba(37,99,235,.5);outline:0}.badge-warning{background-color:#d97706}a.badge-warning:focus,a.badge-warning:hover{background-color:#a75c05;color:#fff}a.badge-warning.focus,a.badge-warning:focus{box-shadow:0 0 0 .2rem rgba(217,119,6,.5);outline:0}.badge-danger{background-color:#dc2626}a.badge-danger:focus,a.badge-danger:hover{background-color:#b21d1d;color:#fff}a.badge-danger.focus,a.badge-danger:focus{box-shadow:0 0 0 .2rem rgba(220,38,38,.5);outline:0}.badge-light{background-color:#f3f4f6;color:#111827}a.badge-light:focus,a.badge-light:hover{background-color:#d6d9e0;color:#111827}a.badge-light.focus,a.badge-light:focus{box-shadow:0 0 0 .2rem rgba(243,244,246,.5);outline:0}.badge-dark{background-color:#1f2937;color:#fff}a.badge-dark:focus,a.badge-dark:hover{background-color:#0d1116;color:#fff}a.badge-dark.focus,a.badge-dark:focus{box-shadow:0 0 0 .2rem rgba(31,41,55,.5);outline:0}.jumbotron{background-color:#e5e7eb;border-radius:6px;margin-bottom:2rem;padding:2rem 1rem}@media (min-width:2px){.jumbotron{padding:4rem 2rem}}.jumbotron-fluid{border-radius:0;padding-left:0;padding-right:0}.alert{border:1px solid transparent;border-radius:.25rem;margin-bottom:1rem;padding:.75rem 1.25rem;position:relative}.alert-heading{color:inherit}.alert-link{font-weight:600}.alert-dismissible{padding-right:4rem}.alert-dismissible .close{color:inherit;padding:.75rem 1.25rem;position:absolute;right:0;top:0;z-index:2}.alert-primary{background-color:#d9d9f4;border-color:#cacaf0;color:#212168}.alert-primary hr{border-top-color:#b6b6ea}.alert-primary .alert-link{color:#151541}.alert-secondary{background-color:#dbdde0;border-color:#cdcfd3;color:#272c33}.alert-secondary hr{border-top-color:#bfc2c7}.alert-secondary .alert-link{color:#111316}.alert-success{background-color:#cdeae1;border-color:#b9e2d5;color:#034e37}.alert-success hr{border-top-color:#a7dbca}.alert-success .alert-link{color:#011d14}.alert-info{background-color:#d3e0fb;border-color:#c2d3f9;color:#13337a}.alert-info hr{border-top-color:#abc2f7}.alert-info .alert-link{color:#0c214e}.alert-warning{background-color:#f7e4cd;border-color:#f4d9b9;color:#713e03}.alert-warning hr{border-top-color:#f1cda3}.alert-warning .alert-link{color:#3f2302}.alert-danger{background-color:#f8d4d4;border-color:#f5c2c2;color:#721414}.alert-danger hr{border-top-color:#f1acac}.alert-danger .alert-link{color:#470c0c}.alert-light{background-color:#fdfdfd;border-color:#fcfcfc;color:#7e7f80}.alert-light hr{border-top-color:#efefef}.alert-light .alert-link{color:#656666}.alert-dark{background-color:#d2d4d7;border-color:#c0c3c7;color:#10151d}.alert-dark hr{border-top-color:#b3b6bb}.alert-dark .alert-link{color:#000}@keyframes progress-bar-stripes{0%{background-position:1rem 0}to{background-position:0 0}}.progress{background-color:#e5e7eb;border-radius:.25rem;font-size:.75rem;height:1rem;line-height:0}.progress,.progress-bar{display:flex;overflow:hidden}.progress-bar{background-color:#4040c8;color:#fff;flex-direction:column;justify-content:center;text-align:center;transition:width .6s ease;white-space:nowrap}@media (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,hsla(0,0%,100%,.15) 25%,transparent 0,transparent 50%,hsla(0,0%,100%,.15) 0,hsla(0,0%,100%,.15) 75%,transparent 0,transparent);background-size:1rem 1rem}.progress-bar-animated{animation:progress-bar-stripes 1s linear infinite}@media (prefers-reduced-motion:reduce){.progress-bar-animated{animation:none}}.media{align-items:flex-start;display:flex}.media-body{flex:1}.list-group{border-radius:.25rem;display:flex;flex-direction:column;margin-bottom:0;padding-left:0}.list-group-item-action{color:#374151;text-align:inherit;width:100%}.list-group-item-action:focus,.list-group-item-action:hover{background-color:#f3f4f6;color:#374151;text-decoration:none;z-index:1}.list-group-item-action:active{background-color:#e5e7eb;color:#f3f4f6}.list-group-item{background-color:#fff;border:1px solid rgba(0,0,0,.125);display:block;padding:.75rem 1.25rem;position:relative}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-left-radius:inherit;border-bottom-right-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{background-color:#fff;color:#4b5563;pointer-events:none}.list-group-item.active{background-color:#4040c8;border-color:#4040c8;color:#fff;z-index:2}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{border-top-width:1px;margin-top:-1px}.list-group-horizontal{flex-direction:row}.list-group-horizontal>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child{border-bottom-left-radius:0;border-top-right-radius:.25rem}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-left-width:0;border-top-width:1px}.list-group-horizontal>.list-group-item+.list-group-item.active{border-left-width:1px;margin-left:-1px}@media (min-width:2px){.list-group-horizontal-sm{flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child{border-bottom-left-radius:0;border-top-right-radius:.25rem}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-left-width:0;border-top-width:1px}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{border-left-width:1px;margin-left:-1px}}@media (min-width:8px){.list-group-horizontal-md{flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child{border-bottom-left-radius:0;border-top-right-radius:.25rem}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-left-width:0;border-top-width:1px}.list-group-horizontal-md>.list-group-item+.list-group-item.active{border-left-width:1px;margin-left:-1px}}@media (min-width:9px){.list-group-horizontal-lg{flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child{border-bottom-left-radius:0;border-top-right-radius:.25rem}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-left-width:0;border-top-width:1px}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{border-left-width:1px;margin-left:-1px}}@media (min-width:10px){.list-group-horizontal-xl{flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child{border-bottom-left-radius:0;border-top-right-radius:.25rem}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-left-width:0;border-top-width:1px}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{border-left-width:1px;margin-left:-1px}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 1px}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-primary{background-color:#cacaf0;color:#212168}.list-group-item-primary.list-group-item-action:focus,.list-group-item-primary.list-group-item-action:hover{background-color:#b6b6ea;color:#212168}.list-group-item-primary.list-group-item-action.active{background-color:#212168;border-color:#212168;color:#fff}.list-group-item-secondary{background-color:#cdcfd3;color:#272c33}.list-group-item-secondary.list-group-item-action:focus,.list-group-item-secondary.list-group-item-action:hover{background-color:#bfc2c7;color:#272c33}.list-group-item-secondary.list-group-item-action.active{background-color:#272c33;border-color:#272c33;color:#fff}.list-group-item-success{background-color:#b9e2d5;color:#034e37}.list-group-item-success.list-group-item-action:focus,.list-group-item-success.list-group-item-action:hover{background-color:#a7dbca;color:#034e37}.list-group-item-success.list-group-item-action.active{background-color:#034e37;border-color:#034e37;color:#fff}.list-group-item-info{background-color:#c2d3f9;color:#13337a}.list-group-item-info.list-group-item-action:focus,.list-group-item-info.list-group-item-action:hover{background-color:#abc2f7;color:#13337a}.list-group-item-info.list-group-item-action.active{background-color:#13337a;border-color:#13337a;color:#fff}.list-group-item-warning{background-color:#f4d9b9;color:#713e03}.list-group-item-warning.list-group-item-action:focus,.list-group-item-warning.list-group-item-action:hover{background-color:#f1cda3;color:#713e03}.list-group-item-warning.list-group-item-action.active{background-color:#713e03;border-color:#713e03;color:#fff}.list-group-item-danger{background-color:#f5c2c2;color:#721414}.list-group-item-danger.list-group-item-action:focus,.list-group-item-danger.list-group-item-action:hover{background-color:#f1acac;color:#721414}.list-group-item-danger.list-group-item-action.active{background-color:#721414;border-color:#721414;color:#fff}.list-group-item-light{background-color:#fcfcfc;color:#7e7f80}.list-group-item-light.list-group-item-action:focus,.list-group-item-light.list-group-item-action:hover{background-color:#efefef;color:#7e7f80}.list-group-item-light.list-group-item-action.active{background-color:#7e7f80;border-color:#7e7f80;color:#fff}.list-group-item-dark{background-color:#c0c3c7;color:#10151d}.list-group-item-dark.list-group-item-action:focus,.list-group-item-dark.list-group-item-action:hover{background-color:#b3b6bb;color:#10151d}.list-group-item-dark.list-group-item-action.active{background-color:#10151d;border-color:#10151d;color:#fff}.close{color:#000;float:right;font-size:1.5rem;font-weight:600;line-height:1;opacity:.5;text-shadow:0 1px 0 #fff}.close:hover{color:#000;text-decoration:none}.close:not(:disabled):not(.disabled):focus,.close:not(:disabled):not(.disabled):hover{opacity:.75}button.close{background-color:transparent;border:0;padding:0}a.close.disabled{pointer-events:none}.toast{background-clip:padding-box;background-color:hsla(0,0%,100%,.85);border:1px solid rgba(0,0,0,.1);border-radius:.25rem;box-shadow:0 .25rem .75rem rgba(0,0,0,.1);flex-basis:350px;font-size:.875rem;max-width:350px;opacity:0}.toast:not(:last-child){margin-bottom:.75rem}.toast.showing{opacity:1}.toast.show{display:block;opacity:1}.toast.hide{display:none}.toast-header{align-items:center;background-clip:padding-box;background-color:hsla(0,0%,100%,.85);border-bottom:1px solid rgba(0,0,0,.05);border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px);color:#4b5563;display:flex;padding:.25rem .75rem}.toast-body{padding:.75rem}.modal-open{overflow:hidden}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal{display:none;height:100%;left:0;outline:0;overflow:hidden;position:fixed;top:0;width:100%;z-index:1050}.modal-dialog{margin:.5rem;pointer-events:none;position:relative;width:auto}.modal.fade .modal-dialog{transform:translateY(-50px);transition:transform .3s ease-out}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{transform:none}.modal.modal-static .modal-dialog{transform:scale(1.02)}.modal-dialog-scrollable{display:flex;max-height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 1rem);overflow:hidden}.modal-dialog-scrollable .modal-footer,.modal-dialog-scrollable .modal-header{flex-shrink:0}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{align-items:center;display:flex;min-height:calc(100% - 1rem)}.modal-dialog-centered:before{content:"";display:block;height:calc(100vh - 1rem);height:-moz-min-content;height:min-content}.modal-dialog-centered.modal-dialog-scrollable{flex-direction:column;height:100%;justify-content:center}.modal-dialog-centered.modal-dialog-scrollable .modal-content{max-height:none}.modal-dialog-centered.modal-dialog-scrollable:before{content:none}.modal-content{background-clip:padding-box;background-color:#1f2937;border:1px solid rgba(0,0,0,.2);border-radius:6px;display:flex;flex-direction:column;outline:0;pointer-events:auto;position:relative;width:100%}.modal-backdrop{background-color:#4b5563;height:100vh;left:0;position:fixed;top:0;width:100vw;z-index:1040}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{align-items:flex-start;border-bottom:1px solid #4b5563;border-top-left-radius:5px;border-top-right-radius:5px;display:flex;justify-content:space-between;padding:1rem}.modal-header .close{margin:-1rem -1rem -1rem auto;padding:1rem}.modal-title{line-height:1.5;margin-bottom:0}.modal-body{flex:1 1 auto;padding:1rem;position:relative}.modal-footer{align-items:center;border-bottom-left-radius:5px;border-bottom-right-radius:5px;border-top:1px solid #4b5563;display:flex;flex-wrap:wrap;justify-content:flex-end;padding:.75rem}.modal-footer>*{margin:.25rem}.modal-scrollbar-measure{height:50px;overflow:scroll;position:absolute;top:-9999px;width:50px}@media (min-width:2px){.modal-dialog{margin:1.75rem auto;max-width:500px}.modal-dialog-scrollable{max-height:calc(100% - 3.5rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-dialog-centered:before{height:calc(100vh - 3.5rem);height:-moz-min-content;height:min-content}.modal-sm{max-width:300px}}@media (min-width:9px){.modal-lg,.modal-xl{max-width:800px}}@media (min-width:10px){.modal-xl{max-width:1140px}}.tooltip{word-wrap:break-word;display:block;font-family:Figtree,sans-serif;font-size:.875rem;font-style:normal;font-weight:400;letter-spacing:normal;line-break:auto;line-height:1.5;margin:0;opacity:0;position:absolute;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;white-space:normal;word-break:normal;word-spacing:normal;z-index:1070}.tooltip.show{opacity:.9}.tooltip .arrow{display:block;height:.4rem;position:absolute;width:.8rem}.tooltip .arrow:before{border-color:transparent;border-style:solid;content:"";position:absolute}.bs-tooltip-auto[x-placement^=top],.bs-tooltip-top{padding:.4rem 0}.bs-tooltip-auto[x-placement^=top] .arrow,.bs-tooltip-top .arrow{bottom:0}.bs-tooltip-auto[x-placement^=top] .arrow:before,.bs-tooltip-top .arrow:before{border-top-color:#000;border-width:.4rem .4rem 0;top:0}.bs-tooltip-auto[x-placement^=right],.bs-tooltip-right{padding:0 .4rem}.bs-tooltip-auto[x-placement^=right] .arrow,.bs-tooltip-right .arrow{height:.8rem;left:0;width:.4rem}.bs-tooltip-auto[x-placement^=right] .arrow:before,.bs-tooltip-right .arrow:before{border-right-color:#000;border-width:.4rem .4rem .4rem 0;right:0}.bs-tooltip-auto[x-placement^=bottom],.bs-tooltip-bottom{padding:.4rem 0}.bs-tooltip-auto[x-placement^=bottom] .arrow,.bs-tooltip-bottom .arrow{top:0}.bs-tooltip-auto[x-placement^=bottom] .arrow:before,.bs-tooltip-bottom .arrow:before{border-bottom-color:#000;border-width:0 .4rem .4rem;bottom:0}.bs-tooltip-auto[x-placement^=left],.bs-tooltip-left{padding:0 .4rem}.bs-tooltip-auto[x-placement^=left] .arrow,.bs-tooltip-left .arrow{height:.8rem;right:0;width:.4rem}.bs-tooltip-auto[x-placement^=left] .arrow:before,.bs-tooltip-left .arrow:before{border-left-color:#000;border-width:.4rem 0 .4rem .4rem;left:0}.tooltip-inner{background-color:#000;border-radius:.25rem;color:#fff;max-width:200px;padding:.25rem .5rem;text-align:center}.popover{word-wrap:break-word;background-clip:padding-box;background-color:#fff;border:1px solid rgba(0,0,0,.2);border-radius:6px;font-family:Figtree,sans-serif;font-size:.875rem;font-style:normal;font-weight:400;left:0;letter-spacing:normal;line-break:auto;line-height:1.5;max-width:276px;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;top:0;white-space:normal;word-break:normal;word-spacing:normal;z-index:1060}.popover,.popover .arrow{display:block;position:absolute}.popover .arrow{height:.5rem;margin:0 6px;width:1rem}.popover .arrow:after,.popover .arrow:before{border-color:transparent;border-style:solid;content:"";display:block;position:absolute}.bs-popover-auto[x-placement^=top],.bs-popover-top{margin-bottom:.5rem}.bs-popover-auto[x-placement^=top]>.arrow,.bs-popover-top>.arrow{bottom:calc(-.5rem - 1px)}.bs-popover-auto[x-placement^=top]>.arrow:before,.bs-popover-top>.arrow:before{border-top-color:rgba(0,0,0,.25);border-width:.5rem .5rem 0;bottom:0}.bs-popover-auto[x-placement^=top]>.arrow:after,.bs-popover-top>.arrow:after{border-top-color:#fff;border-width:.5rem .5rem 0;bottom:1px}.bs-popover-auto[x-placement^=right],.bs-popover-right{margin-left:.5rem}.bs-popover-auto[x-placement^=right]>.arrow,.bs-popover-right>.arrow{height:1rem;left:calc(-.5rem - 1px);margin:6px 0;width:.5rem}.bs-popover-auto[x-placement^=right]>.arrow:before,.bs-popover-right>.arrow:before{border-right-color:rgba(0,0,0,.25);border-width:.5rem .5rem .5rem 0;left:0}.bs-popover-auto[x-placement^=right]>.arrow:after,.bs-popover-right>.arrow:after{border-right-color:#fff;border-width:.5rem .5rem .5rem 0;left:1px}.bs-popover-auto[x-placement^=bottom],.bs-popover-bottom{margin-top:.5rem}.bs-popover-auto[x-placement^=bottom]>.arrow,.bs-popover-bottom>.arrow{top:calc(-.5rem - 1px)}.bs-popover-auto[x-placement^=bottom]>.arrow:before,.bs-popover-bottom>.arrow:before{border-bottom-color:rgba(0,0,0,.25);border-width:0 .5rem .5rem;top:0}.bs-popover-auto[x-placement^=bottom]>.arrow:after,.bs-popover-bottom>.arrow:after{border-bottom-color:#fff;border-width:0 .5rem .5rem;top:1px}.bs-popover-auto[x-placement^=bottom] .popover-header:before,.bs-popover-bottom .popover-header:before{border-bottom:1px solid #f7f7f7;content:"";display:block;left:50%;margin-left:-.5rem;position:absolute;top:0;width:1rem}.bs-popover-auto[x-placement^=left],.bs-popover-left{margin-right:.5rem}.bs-popover-auto[x-placement^=left]>.arrow,.bs-popover-left>.arrow{height:1rem;margin:6px 0;right:calc(-.5rem - 1px);width:.5rem}.bs-popover-auto[x-placement^=left]>.arrow:before,.bs-popover-left>.arrow:before{border-left-color:rgba(0,0,0,.25);border-width:.5rem 0 .5rem .5rem;right:0}.bs-popover-auto[x-placement^=left]>.arrow:after,.bs-popover-left>.arrow:after{border-left-color:#fff;border-width:.5rem 0 .5rem .5rem;right:1px}.popover-header{background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-top-left-radius:5px;border-top-right-radius:5px;font-size:1rem;margin-bottom:0;padding:.5rem .75rem}.popover-header:empty{display:none}.popover-body{color:#f3f4f6;padding:.5rem .75rem}.carousel{position:relative}.carousel.pointer-event{touch-action:pan-y}.carousel-inner{overflow:hidden;position:relative;width:100%}.carousel-inner:after{clear:both;content:"";display:block}.carousel-item{backface-visibility:hidden;display:none;float:left;margin-right:-100%;position:relative;transition:transform .6s ease-in-out;width:100%}@media (prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.active.carousel-item-right,.carousel-item-next:not(.carousel-item-left){transform:translateX(100%)}.active.carousel-item-left,.carousel-item-prev:not(.carousel-item-right){transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transform:none;transition-property:opacity}.carousel-fade .carousel-item-next.carousel-item-left,.carousel-fade .carousel-item-prev.carousel-item-right,.carousel-fade .carousel-item.active{opacity:1;z-index:1}.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{opacity:0;transition:opacity 0s .6s;z-index:0}@media (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{transition:none}}.carousel-control-next,.carousel-control-prev{align-items:center;background:none;border:0;bottom:0;color:#fff;display:flex;justify-content:center;opacity:.5;padding:0;position:absolute;text-align:center;top:0;transition:opacity .15s ease;width:15%;z-index:1}@media (prefers-reduced-motion:reduce){.carousel-control-next,.carousel-control-prev{transition:none}}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;opacity:.9;outline:0;text-decoration:none}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{background:50%/100% 100% no-repeat;display:inline-block;height:20px;width:20px}.carousel-control-prev-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8'%3E%3Cpath d='m5.25 0-4 4 4 4 1.5-1.5L4.25 4l2.5-2.5L5.25 0z'/%3E%3C/svg%3E")}.carousel-control-next-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8'%3E%3Cpath d='m2.75 0-1.5 1.5L3.75 4l-2.5 2.5L2.75 8l4-4-4-4z'/%3E%3C/svg%3E")}.carousel-indicators{bottom:0;display:flex;justify-content:center;left:0;list-style:none;margin-left:15%;margin-right:15%;padding-left:0;position:absolute;right:0;z-index:15}.carousel-indicators li{background-clip:padding-box;background-color:#fff;border-bottom:10px solid transparent;border-top:10px solid transparent;box-sizing:content-box;cursor:pointer;flex:0 1 auto;height:3px;margin-left:3px;margin-right:3px;opacity:.5;text-indent:-999px;transition:opacity .6s ease;width:30px}@media (prefers-reduced-motion:reduce){.carousel-indicators li{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{bottom:20px;color:#fff;left:15%;padding-bottom:20px;padding-top:20px;position:absolute;right:15%;text-align:center;z-index:10}@keyframes spinner-border{to{transform:rotate(1turn)}}.spinner-border{animation:spinner-border .75s linear infinite;border:.25em solid;border-radius:50%;border-right:.25em solid transparent;display:inline-block;height:2rem;vertical-align:-.125em;width:2rem}.spinner-border-sm{border-width:.2em;height:1rem;width:1rem}@keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}.spinner-grow{animation:spinner-grow .75s linear infinite;background-color:currentcolor;border-radius:50%;display:inline-block;height:2rem;opacity:0;vertical-align:-.125em;width:2rem}.spinner-grow-sm{height:1rem;width:1rem}@media (prefers-reduced-motion:reduce){.spinner-border,.spinner-grow{animation-duration:1.5s}}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.bg-primary{background-color:#4040c8!important}a.bg-primary:focus,a.bg-primary:hover,button.bg-primary:focus,button.bg-primary:hover{background-color:#3030a5!important}.bg-secondary{background-color:#4b5563!important}a.bg-secondary:focus,a.bg-secondary:hover,button.bg-secondary:focus,button.bg-secondary:hover{background-color:#353c46!important}.bg-success{background-color:#059669!important}a.bg-success:focus,a.bg-success:hover,button.bg-success:focus,button.bg-success:hover{background-color:#036546!important}.bg-info{background-color:#2563eb!important}a.bg-info:focus,a.bg-info:hover,button.bg-info:focus,button.bg-info:hover{background-color:#134cca!important}.bg-warning{background-color:#d97706!important}a.bg-warning:focus,a.bg-warning:hover,button.bg-warning:focus,button.bg-warning:hover{background-color:#a75c05!important}.bg-danger{background-color:#dc2626!important}a.bg-danger:focus,a.bg-danger:hover,button.bg-danger:focus,button.bg-danger:hover{background-color:#b21d1d!important}.bg-light{background-color:#f3f4f6!important}a.bg-light:focus,a.bg-light:hover,button.bg-light:focus,button.bg-light:hover{background-color:#d6d9e0!important}.bg-dark{background-color:#1f2937!important}a.bg-dark:focus,a.bg-dark:hover,button.bg-dark:focus,button.bg-dark:hover{background-color:#0d1116!important}.bg-white{background-color:#fff!important}.bg-transparent{background-color:transparent!important}.border{border:1px solid #4b5563!important}.border-top{border-top:1px solid #4b5563!important}.border-right{border-right:1px solid #4b5563!important}.border-bottom{border-bottom:1px solid #4b5563!important}.border-left{border-left:1px solid #4b5563!important}.border-0{border:0!important}.border-top-0{border-top:0!important}.border-right-0{border-right:0!important}.border-bottom-0{border-bottom:0!important}.border-left-0{border-left:0!important}.border-primary{border-color:#4040c8!important}.border-secondary{border-color:#4b5563!important}.border-success{border-color:#059669!important}.border-info{border-color:#2563eb!important}.border-warning{border-color:#d97706!important}.border-danger{border-color:#dc2626!important}.border-light{border-color:#f3f4f6!important}.border-dark{border-color:#1f2937!important}.border-white{border-color:#fff!important}.rounded-sm{border-radius:.2rem!important}.rounded{border-radius:.25rem!important}.rounded-top{border-top-left-radius:.25rem!important}.rounded-right,.rounded-top{border-top-right-radius:.25rem!important}.rounded-bottom,.rounded-right{border-bottom-right-radius:.25rem!important}.rounded-bottom,.rounded-left{border-bottom-left-radius:.25rem!important}.rounded-left{border-top-left-radius:.25rem!important}.rounded-lg{border-radius:6px!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:50rem!important}.rounded-0{border-radius:0!important}.clearfix:after{clear:both;content:"";display:block}.d-none{display:none!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:flex!important}.d-inline-flex{display:inline-flex!important}@media (min-width:2px){.d-sm-none{display:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:flex!important}.d-sm-inline-flex{display:inline-flex!important}}@media (min-width:8px){.d-md-none{display:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:flex!important}.d-md-inline-flex{display:inline-flex!important}}@media (min-width:9px){.d-lg-none{display:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:flex!important}.d-lg-inline-flex{display:inline-flex!important}}@media (min-width:10px){.d-xl-none{display:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:flex!important}.d-xl-inline-flex{display:inline-flex!important}}@media print{.d-print-none{display:none!important}.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:flex!important}.d-print-inline-flex{display:inline-flex!important}}.embed-responsive{display:block;overflow:hidden;padding:0;position:relative;width:100%}.embed-responsive:before{content:"";display:block}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{border:0;bottom:0;height:100%;left:0;position:absolute;top:0;width:100%}.embed-responsive-21by9:before{padding-top:42.85714286%}.embed-responsive-16by9:before{padding-top:56.25%}.embed-responsive-4by3:before{padding-top:75%}.embed-responsive-1by1:before{padding-top:100%}.flex-row{flex-direction:row!important}.flex-column{flex-direction:column!important}.flex-row-reverse{flex-direction:row-reverse!important}.flex-column-reverse{flex-direction:column-reverse!important}.flex-wrap{flex-wrap:wrap!important}.flex-nowrap{flex-wrap:nowrap!important}.flex-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-fill{flex:1 1 auto!important}.flex-grow-0{flex-grow:0!important}.flex-grow-1{flex-grow:1!important}.flex-shrink-0{flex-shrink:0!important}.flex-shrink-1{flex-shrink:1!important}.justify-content-start{justify-content:flex-start!important}.justify-content-end{justify-content:flex-end!important}.justify-content-center{justify-content:center!important}.justify-content-between{justify-content:space-between!important}.justify-content-around{justify-content:space-around!important}.align-items-start{align-items:flex-start!important}.align-items-end{align-items:flex-end!important}.align-items-center{align-items:center!important}.align-items-baseline{align-items:baseline!important}.align-items-stretch{align-items:stretch!important}.align-content-start{align-content:flex-start!important}.align-content-end{align-content:flex-end!important}.align-content-center{align-content:center!important}.align-content-between{align-content:space-between!important}.align-content-around{align-content:space-around!important}.align-content-stretch{align-content:stretch!important}.align-self-auto{align-self:auto!important}.align-self-start{align-self:flex-start!important}.align-self-end{align-self:flex-end!important}.align-self-center{align-self:center!important}.align-self-baseline{align-self:baseline!important}.align-self-stretch{align-self:stretch!important}@media (min-width:2px){.flex-sm-row{flex-direction:row!important}.flex-sm-column{flex-direction:column!important}.flex-sm-row-reverse{flex-direction:row-reverse!important}.flex-sm-column-reverse{flex-direction:column-reverse!important}.flex-sm-wrap{flex-wrap:wrap!important}.flex-sm-nowrap{flex-wrap:nowrap!important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-sm-fill{flex:1 1 auto!important}.flex-sm-grow-0{flex-grow:0!important}.flex-sm-grow-1{flex-grow:1!important}.flex-sm-shrink-0{flex-shrink:0!important}.flex-sm-shrink-1{flex-shrink:1!important}.justify-content-sm-start{justify-content:flex-start!important}.justify-content-sm-end{justify-content:flex-end!important}.justify-content-sm-center{justify-content:center!important}.justify-content-sm-between{justify-content:space-between!important}.justify-content-sm-around{justify-content:space-around!important}.align-items-sm-start{align-items:flex-start!important}.align-items-sm-end{align-items:flex-end!important}.align-items-sm-center{align-items:center!important}.align-items-sm-baseline{align-items:baseline!important}.align-items-sm-stretch{align-items:stretch!important}.align-content-sm-start{align-content:flex-start!important}.align-content-sm-end{align-content:flex-end!important}.align-content-sm-center{align-content:center!important}.align-content-sm-between{align-content:space-between!important}.align-content-sm-around{align-content:space-around!important}.align-content-sm-stretch{align-content:stretch!important}.align-self-sm-auto{align-self:auto!important}.align-self-sm-start{align-self:flex-start!important}.align-self-sm-end{align-self:flex-end!important}.align-self-sm-center{align-self:center!important}.align-self-sm-baseline{align-self:baseline!important}.align-self-sm-stretch{align-self:stretch!important}}@media (min-width:8px){.flex-md-row{flex-direction:row!important}.flex-md-column{flex-direction:column!important}.flex-md-row-reverse{flex-direction:row-reverse!important}.flex-md-column-reverse{flex-direction:column-reverse!important}.flex-md-wrap{flex-wrap:wrap!important}.flex-md-nowrap{flex-wrap:nowrap!important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-md-fill{flex:1 1 auto!important}.flex-md-grow-0{flex-grow:0!important}.flex-md-grow-1{flex-grow:1!important}.flex-md-shrink-0{flex-shrink:0!important}.flex-md-shrink-1{flex-shrink:1!important}.justify-content-md-start{justify-content:flex-start!important}.justify-content-md-end{justify-content:flex-end!important}.justify-content-md-center{justify-content:center!important}.justify-content-md-between{justify-content:space-between!important}.justify-content-md-around{justify-content:space-around!important}.align-items-md-start{align-items:flex-start!important}.align-items-md-end{align-items:flex-end!important}.align-items-md-center{align-items:center!important}.align-items-md-baseline{align-items:baseline!important}.align-items-md-stretch{align-items:stretch!important}.align-content-md-start{align-content:flex-start!important}.align-content-md-end{align-content:flex-end!important}.align-content-md-center{align-content:center!important}.align-content-md-between{align-content:space-between!important}.align-content-md-around{align-content:space-around!important}.align-content-md-stretch{align-content:stretch!important}.align-self-md-auto{align-self:auto!important}.align-self-md-start{align-self:flex-start!important}.align-self-md-end{align-self:flex-end!important}.align-self-md-center{align-self:center!important}.align-self-md-baseline{align-self:baseline!important}.align-self-md-stretch{align-self:stretch!important}}@media (min-width:9px){.flex-lg-row{flex-direction:row!important}.flex-lg-column{flex-direction:column!important}.flex-lg-row-reverse{flex-direction:row-reverse!important}.flex-lg-column-reverse{flex-direction:column-reverse!important}.flex-lg-wrap{flex-wrap:wrap!important}.flex-lg-nowrap{flex-wrap:nowrap!important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-lg-fill{flex:1 1 auto!important}.flex-lg-grow-0{flex-grow:0!important}.flex-lg-grow-1{flex-grow:1!important}.flex-lg-shrink-0{flex-shrink:0!important}.flex-lg-shrink-1{flex-shrink:1!important}.justify-content-lg-start{justify-content:flex-start!important}.justify-content-lg-end{justify-content:flex-end!important}.justify-content-lg-center{justify-content:center!important}.justify-content-lg-between{justify-content:space-between!important}.justify-content-lg-around{justify-content:space-around!important}.align-items-lg-start{align-items:flex-start!important}.align-items-lg-end{align-items:flex-end!important}.align-items-lg-center{align-items:center!important}.align-items-lg-baseline{align-items:baseline!important}.align-items-lg-stretch{align-items:stretch!important}.align-content-lg-start{align-content:flex-start!important}.align-content-lg-end{align-content:flex-end!important}.align-content-lg-center{align-content:center!important}.align-content-lg-between{align-content:space-between!important}.align-content-lg-around{align-content:space-around!important}.align-content-lg-stretch{align-content:stretch!important}.align-self-lg-auto{align-self:auto!important}.align-self-lg-start{align-self:flex-start!important}.align-self-lg-end{align-self:flex-end!important}.align-self-lg-center{align-self:center!important}.align-self-lg-baseline{align-self:baseline!important}.align-self-lg-stretch{align-self:stretch!important}}@media (min-width:10px){.flex-xl-row{flex-direction:row!important}.flex-xl-column{flex-direction:column!important}.flex-xl-row-reverse{flex-direction:row-reverse!important}.flex-xl-column-reverse{flex-direction:column-reverse!important}.flex-xl-wrap{flex-wrap:wrap!important}.flex-xl-nowrap{flex-wrap:nowrap!important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-xl-fill{flex:1 1 auto!important}.flex-xl-grow-0{flex-grow:0!important}.flex-xl-grow-1{flex-grow:1!important}.flex-xl-shrink-0{flex-shrink:0!important}.flex-xl-shrink-1{flex-shrink:1!important}.justify-content-xl-start{justify-content:flex-start!important}.justify-content-xl-end{justify-content:flex-end!important}.justify-content-xl-center{justify-content:center!important}.justify-content-xl-between{justify-content:space-between!important}.justify-content-xl-around{justify-content:space-around!important}.align-items-xl-start{align-items:flex-start!important}.align-items-xl-end{align-items:flex-end!important}.align-items-xl-center{align-items:center!important}.align-items-xl-baseline{align-items:baseline!important}.align-items-xl-stretch{align-items:stretch!important}.align-content-xl-start{align-content:flex-start!important}.align-content-xl-end{align-content:flex-end!important}.align-content-xl-center{align-content:center!important}.align-content-xl-between{align-content:space-between!important}.align-content-xl-around{align-content:space-around!important}.align-content-xl-stretch{align-content:stretch!important}.align-self-xl-auto{align-self:auto!important}.align-self-xl-start{align-self:flex-start!important}.align-self-xl-end{align-self:flex-end!important}.align-self-xl-center{align-self:center!important}.align-self-xl-baseline{align-self:baseline!important}.align-self-xl-stretch{align-self:stretch!important}}.float-left{float:left!important}.float-right{float:right!important}.float-none{float:none!important}@media (min-width:2px){.float-sm-left{float:left!important}.float-sm-right{float:right!important}.float-sm-none{float:none!important}}@media (min-width:8px){.float-md-left{float:left!important}.float-md-right{float:right!important}.float-md-none{float:none!important}}@media (min-width:9px){.float-lg-left{float:left!important}.float-lg-right{float:right!important}.float-lg-none{float:none!important}}@media (min-width:10px){.float-xl-left{float:left!important}.float-xl-right{float:right!important}.float-xl-none{float:none!important}}.user-select-all{-webkit-user-select:all!important;-moz-user-select:all!important;user-select:all!important}.user-select-auto{-webkit-user-select:auto!important;-moz-user-select:auto!important;user-select:auto!important}.user-select-none{-webkit-user-select:none!important;-moz-user-select:none!important;user-select:none!important}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:sticky!important}.fixed-top{top:0}.fixed-bottom,.fixed-top{left:0;position:fixed;right:0;z-index:1030}.fixed-bottom{bottom:0}@supports (position:sticky){.sticky-top{position:sticky;top:0;z-index:1020}}.sr-only{clip:rect(0,0,0,0);border:0;height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;white-space:nowrap;width:1px}.sr-only-focusable:active,.sr-only-focusable:focus{clip:auto;height:auto;overflow:visible;position:static;white-space:normal;width:auto}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075)!important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15)!important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175)!important}.shadow-none{box-shadow:none!important}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mw-100{max-width:100%!important}.mh-100{max-height:100%!important}.min-vw-100{min-width:100vw!important}.min-vh-100{min-height:100vh!important}.vw-100{width:100vw!important}.vh-100{height:100vh!important}.m-0{margin:0!important}.mt-0,.my-0{margin-top:0!important}.mr-0,.mx-0{margin-right:0!important}.mb-0,.my-0{margin-bottom:0!important}.ml-0,.mx-0{margin-left:0!important}.m-1{margin:.25rem!important}.mt-1,.my-1{margin-top:.25rem!important}.mr-1,.mx-1{margin-right:.25rem!important}.mb-1,.my-1{margin-bottom:.25rem!important}.ml-1,.mx-1{margin-left:.25rem!important}.m-2{margin:.5rem!important}.mt-2,.my-2{margin-top:.5rem!important}.mr-2,.mx-2{margin-right:.5rem!important}.mb-2,.my-2{margin-bottom:.5rem!important}.ml-2,.mx-2{margin-left:.5rem!important}.m-3{margin:1rem!important}.mt-3,.my-3{margin-top:1rem!important}.mr-3,.mx-3{margin-right:1rem!important}.mb-3,.my-3{margin-bottom:1rem!important}.ml-3,.mx-3{margin-left:1rem!important}.m-4{margin:1.5rem!important}.mt-4,.my-4{margin-top:1.5rem!important}.mr-4,.mx-4{margin-right:1.5rem!important}.mb-4,.my-4{margin-bottom:1.5rem!important}.ml-4,.mx-4{margin-left:1.5rem!important}.m-5{margin:3rem!important}.mt-5,.my-5{margin-top:3rem!important}.mr-5,.mx-5{margin-right:3rem!important}.mb-5,.my-5{margin-bottom:3rem!important}.ml-5,.mx-5{margin-left:3rem!important}.p-0{padding:0!important}.pt-0,.py-0{padding-top:0!important}.pr-0,.px-0{padding-right:0!important}.pb-0,.py-0{padding-bottom:0!important}.pl-0,.px-0{padding-left:0!important}.p-1{padding:.25rem!important}.pt-1,.py-1{padding-top:.25rem!important}.pr-1,.px-1{padding-right:.25rem!important}.pb-1,.py-1{padding-bottom:.25rem!important}.pl-1,.px-1{padding-left:.25rem!important}.p-2{padding:.5rem!important}.pt-2,.py-2{padding-top:.5rem!important}.pr-2,.px-2{padding-right:.5rem!important}.pb-2,.py-2{padding-bottom:.5rem!important}.pl-2,.px-2{padding-left:.5rem!important}.p-3{padding:1rem!important}.pt-3,.py-3{padding-top:1rem!important}.pr-3,.px-3{padding-right:1rem!important}.pb-3,.py-3{padding-bottom:1rem!important}.pl-3,.px-3{padding-left:1rem!important}.p-4{padding:1.5rem!important}.pt-4,.py-4{padding-top:1.5rem!important}.pr-4,.px-4{padding-right:1.5rem!important}.pb-4,.py-4{padding-bottom:1.5rem!important}.pl-4,.px-4{padding-left:1.5rem!important}.p-5{padding:3rem!important}.pt-5,.py-5{padding-top:3rem!important}.pr-5,.px-5{padding-right:3rem!important}.pb-5,.py-5{padding-bottom:3rem!important}.pl-5,.px-5{padding-left:3rem!important}.m-n1{margin:-.25rem!important}.mt-n1,.my-n1{margin-top:-.25rem!important}.mr-n1,.mx-n1{margin-right:-.25rem!important}.mb-n1,.my-n1{margin-bottom:-.25rem!important}.ml-n1,.mx-n1{margin-left:-.25rem!important}.m-n2{margin:-.5rem!important}.mt-n2,.my-n2{margin-top:-.5rem!important}.mr-n2,.mx-n2{margin-right:-.5rem!important}.mb-n2,.my-n2{margin-bottom:-.5rem!important}.ml-n2,.mx-n2{margin-left:-.5rem!important}.m-n3{margin:-1rem!important}.mt-n3,.my-n3{margin-top:-1rem!important}.mr-n3,.mx-n3{margin-right:-1rem!important}.mb-n3,.my-n3{margin-bottom:-1rem!important}.ml-n3,.mx-n3{margin-left:-1rem!important}.m-n4{margin:-1.5rem!important}.mt-n4,.my-n4{margin-top:-1.5rem!important}.mr-n4,.mx-n4{margin-right:-1.5rem!important}.mb-n4,.my-n4{margin-bottom:-1.5rem!important}.ml-n4,.mx-n4{margin-left:-1.5rem!important}.m-n5{margin:-3rem!important}.mt-n5,.my-n5{margin-top:-3rem!important}.mr-n5,.mx-n5{margin-right:-3rem!important}.mb-n5,.my-n5{margin-bottom:-3rem!important}.ml-n5,.mx-n5{margin-left:-3rem!important}.m-auto{margin:auto!important}.mt-auto,.my-auto{margin-top:auto!important}.mr-auto,.mx-auto{margin-right:auto!important}.mb-auto,.my-auto{margin-bottom:auto!important}.ml-auto,.mx-auto{margin-left:auto!important}@media (min-width:2px){.m-sm-0{margin:0!important}.mt-sm-0,.my-sm-0{margin-top:0!important}.mr-sm-0,.mx-sm-0{margin-right:0!important}.mb-sm-0,.my-sm-0{margin-bottom:0!important}.ml-sm-0,.mx-sm-0{margin-left:0!important}.m-sm-1{margin:.25rem!important}.mt-sm-1,.my-sm-1{margin-top:.25rem!important}.mr-sm-1,.mx-sm-1{margin-right:.25rem!important}.mb-sm-1,.my-sm-1{margin-bottom:.25rem!important}.ml-sm-1,.mx-sm-1{margin-left:.25rem!important}.m-sm-2{margin:.5rem!important}.mt-sm-2,.my-sm-2{margin-top:.5rem!important}.mr-sm-2,.mx-sm-2{margin-right:.5rem!important}.mb-sm-2,.my-sm-2{margin-bottom:.5rem!important}.ml-sm-2,.mx-sm-2{margin-left:.5rem!important}.m-sm-3{margin:1rem!important}.mt-sm-3,.my-sm-3{margin-top:1rem!important}.mr-sm-3,.mx-sm-3{margin-right:1rem!important}.mb-sm-3,.my-sm-3{margin-bottom:1rem!important}.ml-sm-3,.mx-sm-3{margin-left:1rem!important}.m-sm-4{margin:1.5rem!important}.mt-sm-4,.my-sm-4{margin-top:1.5rem!important}.mr-sm-4,.mx-sm-4{margin-right:1.5rem!important}.mb-sm-4,.my-sm-4{margin-bottom:1.5rem!important}.ml-sm-4,.mx-sm-4{margin-left:1.5rem!important}.m-sm-5{margin:3rem!important}.mt-sm-5,.my-sm-5{margin-top:3rem!important}.mr-sm-5,.mx-sm-5{margin-right:3rem!important}.mb-sm-5,.my-sm-5{margin-bottom:3rem!important}.ml-sm-5,.mx-sm-5{margin-left:3rem!important}.p-sm-0{padding:0!important}.pt-sm-0,.py-sm-0{padding-top:0!important}.pr-sm-0,.px-sm-0{padding-right:0!important}.pb-sm-0,.py-sm-0{padding-bottom:0!important}.pl-sm-0,.px-sm-0{padding-left:0!important}.p-sm-1{padding:.25rem!important}.pt-sm-1,.py-sm-1{padding-top:.25rem!important}.pr-sm-1,.px-sm-1{padding-right:.25rem!important}.pb-sm-1,.py-sm-1{padding-bottom:.25rem!important}.pl-sm-1,.px-sm-1{padding-left:.25rem!important}.p-sm-2{padding:.5rem!important}.pt-sm-2,.py-sm-2{padding-top:.5rem!important}.pr-sm-2,.px-sm-2{padding-right:.5rem!important}.pb-sm-2,.py-sm-2{padding-bottom:.5rem!important}.pl-sm-2,.px-sm-2{padding-left:.5rem!important}.p-sm-3{padding:1rem!important}.pt-sm-3,.py-sm-3{padding-top:1rem!important}.pr-sm-3,.px-sm-3{padding-right:1rem!important}.pb-sm-3,.py-sm-3{padding-bottom:1rem!important}.pl-sm-3,.px-sm-3{padding-left:1rem!important}.p-sm-4{padding:1.5rem!important}.pt-sm-4,.py-sm-4{padding-top:1.5rem!important}.pr-sm-4,.px-sm-4{padding-right:1.5rem!important}.pb-sm-4,.py-sm-4{padding-bottom:1.5rem!important}.pl-sm-4,.px-sm-4{padding-left:1.5rem!important}.p-sm-5{padding:3rem!important}.pt-sm-5,.py-sm-5{padding-top:3rem!important}.pr-sm-5,.px-sm-5{padding-right:3rem!important}.pb-sm-5,.py-sm-5{padding-bottom:3rem!important}.pl-sm-5,.px-sm-5{padding-left:3rem!important}.m-sm-n1{margin:-.25rem!important}.mt-sm-n1,.my-sm-n1{margin-top:-.25rem!important}.mr-sm-n1,.mx-sm-n1{margin-right:-.25rem!important}.mb-sm-n1,.my-sm-n1{margin-bottom:-.25rem!important}.ml-sm-n1,.mx-sm-n1{margin-left:-.25rem!important}.m-sm-n2{margin:-.5rem!important}.mt-sm-n2,.my-sm-n2{margin-top:-.5rem!important}.mr-sm-n2,.mx-sm-n2{margin-right:-.5rem!important}.mb-sm-n2,.my-sm-n2{margin-bottom:-.5rem!important}.ml-sm-n2,.mx-sm-n2{margin-left:-.5rem!important}.m-sm-n3{margin:-1rem!important}.mt-sm-n3,.my-sm-n3{margin-top:-1rem!important}.mr-sm-n3,.mx-sm-n3{margin-right:-1rem!important}.mb-sm-n3,.my-sm-n3{margin-bottom:-1rem!important}.ml-sm-n3,.mx-sm-n3{margin-left:-1rem!important}.m-sm-n4{margin:-1.5rem!important}.mt-sm-n4,.my-sm-n4{margin-top:-1.5rem!important}.mr-sm-n4,.mx-sm-n4{margin-right:-1.5rem!important}.mb-sm-n4,.my-sm-n4{margin-bottom:-1.5rem!important}.ml-sm-n4,.mx-sm-n4{margin-left:-1.5rem!important}.m-sm-n5{margin:-3rem!important}.mt-sm-n5,.my-sm-n5{margin-top:-3rem!important}.mr-sm-n5,.mx-sm-n5{margin-right:-3rem!important}.mb-sm-n5,.my-sm-n5{margin-bottom:-3rem!important}.ml-sm-n5,.mx-sm-n5{margin-left:-3rem!important}.m-sm-auto{margin:auto!important}.mt-sm-auto,.my-sm-auto{margin-top:auto!important}.mr-sm-auto,.mx-sm-auto{margin-right:auto!important}.mb-sm-auto,.my-sm-auto{margin-bottom:auto!important}.ml-sm-auto,.mx-sm-auto{margin-left:auto!important}}@media (min-width:8px){.m-md-0{margin:0!important}.mt-md-0,.my-md-0{margin-top:0!important}.mr-md-0,.mx-md-0{margin-right:0!important}.mb-md-0,.my-md-0{margin-bottom:0!important}.ml-md-0,.mx-md-0{margin-left:0!important}.m-md-1{margin:.25rem!important}.mt-md-1,.my-md-1{margin-top:.25rem!important}.mr-md-1,.mx-md-1{margin-right:.25rem!important}.mb-md-1,.my-md-1{margin-bottom:.25rem!important}.ml-md-1,.mx-md-1{margin-left:.25rem!important}.m-md-2{margin:.5rem!important}.mt-md-2,.my-md-2{margin-top:.5rem!important}.mr-md-2,.mx-md-2{margin-right:.5rem!important}.mb-md-2,.my-md-2{margin-bottom:.5rem!important}.ml-md-2,.mx-md-2{margin-left:.5rem!important}.m-md-3{margin:1rem!important}.mt-md-3,.my-md-3{margin-top:1rem!important}.mr-md-3,.mx-md-3{margin-right:1rem!important}.mb-md-3,.my-md-3{margin-bottom:1rem!important}.ml-md-3,.mx-md-3{margin-left:1rem!important}.m-md-4{margin:1.5rem!important}.mt-md-4,.my-md-4{margin-top:1.5rem!important}.mr-md-4,.mx-md-4{margin-right:1.5rem!important}.mb-md-4,.my-md-4{margin-bottom:1.5rem!important}.ml-md-4,.mx-md-4{margin-left:1.5rem!important}.m-md-5{margin:3rem!important}.mt-md-5,.my-md-5{margin-top:3rem!important}.mr-md-5,.mx-md-5{margin-right:3rem!important}.mb-md-5,.my-md-5{margin-bottom:3rem!important}.ml-md-5,.mx-md-5{margin-left:3rem!important}.p-md-0{padding:0!important}.pt-md-0,.py-md-0{padding-top:0!important}.pr-md-0,.px-md-0{padding-right:0!important}.pb-md-0,.py-md-0{padding-bottom:0!important}.pl-md-0,.px-md-0{padding-left:0!important}.p-md-1{padding:.25rem!important}.pt-md-1,.py-md-1{padding-top:.25rem!important}.pr-md-1,.px-md-1{padding-right:.25rem!important}.pb-md-1,.py-md-1{padding-bottom:.25rem!important}.pl-md-1,.px-md-1{padding-left:.25rem!important}.p-md-2{padding:.5rem!important}.pt-md-2,.py-md-2{padding-top:.5rem!important}.pr-md-2,.px-md-2{padding-right:.5rem!important}.pb-md-2,.py-md-2{padding-bottom:.5rem!important}.pl-md-2,.px-md-2{padding-left:.5rem!important}.p-md-3{padding:1rem!important}.pt-md-3,.py-md-3{padding-top:1rem!important}.pr-md-3,.px-md-3{padding-right:1rem!important}.pb-md-3,.py-md-3{padding-bottom:1rem!important}.pl-md-3,.px-md-3{padding-left:1rem!important}.p-md-4{padding:1.5rem!important}.pt-md-4,.py-md-4{padding-top:1.5rem!important}.pr-md-4,.px-md-4{padding-right:1.5rem!important}.pb-md-4,.py-md-4{padding-bottom:1.5rem!important}.pl-md-4,.px-md-4{padding-left:1.5rem!important}.p-md-5{padding:3rem!important}.pt-md-5,.py-md-5{padding-top:3rem!important}.pr-md-5,.px-md-5{padding-right:3rem!important}.pb-md-5,.py-md-5{padding-bottom:3rem!important}.pl-md-5,.px-md-5{padding-left:3rem!important}.m-md-n1{margin:-.25rem!important}.mt-md-n1,.my-md-n1{margin-top:-.25rem!important}.mr-md-n1,.mx-md-n1{margin-right:-.25rem!important}.mb-md-n1,.my-md-n1{margin-bottom:-.25rem!important}.ml-md-n1,.mx-md-n1{margin-left:-.25rem!important}.m-md-n2{margin:-.5rem!important}.mt-md-n2,.my-md-n2{margin-top:-.5rem!important}.mr-md-n2,.mx-md-n2{margin-right:-.5rem!important}.mb-md-n2,.my-md-n2{margin-bottom:-.5rem!important}.ml-md-n2,.mx-md-n2{margin-left:-.5rem!important}.m-md-n3{margin:-1rem!important}.mt-md-n3,.my-md-n3{margin-top:-1rem!important}.mr-md-n3,.mx-md-n3{margin-right:-1rem!important}.mb-md-n3,.my-md-n3{margin-bottom:-1rem!important}.ml-md-n3,.mx-md-n3{margin-left:-1rem!important}.m-md-n4{margin:-1.5rem!important}.mt-md-n4,.my-md-n4{margin-top:-1.5rem!important}.mr-md-n4,.mx-md-n4{margin-right:-1.5rem!important}.mb-md-n4,.my-md-n4{margin-bottom:-1.5rem!important}.ml-md-n4,.mx-md-n4{margin-left:-1.5rem!important}.m-md-n5{margin:-3rem!important}.mt-md-n5,.my-md-n5{margin-top:-3rem!important}.mr-md-n5,.mx-md-n5{margin-right:-3rem!important}.mb-md-n5,.my-md-n5{margin-bottom:-3rem!important}.ml-md-n5,.mx-md-n5{margin-left:-3rem!important}.m-md-auto{margin:auto!important}.mt-md-auto,.my-md-auto{margin-top:auto!important}.mr-md-auto,.mx-md-auto{margin-right:auto!important}.mb-md-auto,.my-md-auto{margin-bottom:auto!important}.ml-md-auto,.mx-md-auto{margin-left:auto!important}}@media (min-width:9px){.m-lg-0{margin:0!important}.mt-lg-0,.my-lg-0{margin-top:0!important}.mr-lg-0,.mx-lg-0{margin-right:0!important}.mb-lg-0,.my-lg-0{margin-bottom:0!important}.ml-lg-0,.mx-lg-0{margin-left:0!important}.m-lg-1{margin:.25rem!important}.mt-lg-1,.my-lg-1{margin-top:.25rem!important}.mr-lg-1,.mx-lg-1{margin-right:.25rem!important}.mb-lg-1,.my-lg-1{margin-bottom:.25rem!important}.ml-lg-1,.mx-lg-1{margin-left:.25rem!important}.m-lg-2{margin:.5rem!important}.mt-lg-2,.my-lg-2{margin-top:.5rem!important}.mr-lg-2,.mx-lg-2{margin-right:.5rem!important}.mb-lg-2,.my-lg-2{margin-bottom:.5rem!important}.ml-lg-2,.mx-lg-2{margin-left:.5rem!important}.m-lg-3{margin:1rem!important}.mt-lg-3,.my-lg-3{margin-top:1rem!important}.mr-lg-3,.mx-lg-3{margin-right:1rem!important}.mb-lg-3,.my-lg-3{margin-bottom:1rem!important}.ml-lg-3,.mx-lg-3{margin-left:1rem!important}.m-lg-4{margin:1.5rem!important}.mt-lg-4,.my-lg-4{margin-top:1.5rem!important}.mr-lg-4,.mx-lg-4{margin-right:1.5rem!important}.mb-lg-4,.my-lg-4{margin-bottom:1.5rem!important}.ml-lg-4,.mx-lg-4{margin-left:1.5rem!important}.m-lg-5{margin:3rem!important}.mt-lg-5,.my-lg-5{margin-top:3rem!important}.mr-lg-5,.mx-lg-5{margin-right:3rem!important}.mb-lg-5,.my-lg-5{margin-bottom:3rem!important}.ml-lg-5,.mx-lg-5{margin-left:3rem!important}.p-lg-0{padding:0!important}.pt-lg-0,.py-lg-0{padding-top:0!important}.pr-lg-0,.px-lg-0{padding-right:0!important}.pb-lg-0,.py-lg-0{padding-bottom:0!important}.pl-lg-0,.px-lg-0{padding-left:0!important}.p-lg-1{padding:.25rem!important}.pt-lg-1,.py-lg-1{padding-top:.25rem!important}.pr-lg-1,.px-lg-1{padding-right:.25rem!important}.pb-lg-1,.py-lg-1{padding-bottom:.25rem!important}.pl-lg-1,.px-lg-1{padding-left:.25rem!important}.p-lg-2{padding:.5rem!important}.pt-lg-2,.py-lg-2{padding-top:.5rem!important}.pr-lg-2,.px-lg-2{padding-right:.5rem!important}.pb-lg-2,.py-lg-2{padding-bottom:.5rem!important}.pl-lg-2,.px-lg-2{padding-left:.5rem!important}.p-lg-3{padding:1rem!important}.pt-lg-3,.py-lg-3{padding-top:1rem!important}.pr-lg-3,.px-lg-3{padding-right:1rem!important}.pb-lg-3,.py-lg-3{padding-bottom:1rem!important}.pl-lg-3,.px-lg-3{padding-left:1rem!important}.p-lg-4{padding:1.5rem!important}.pt-lg-4,.py-lg-4{padding-top:1.5rem!important}.pr-lg-4,.px-lg-4{padding-right:1.5rem!important}.pb-lg-4,.py-lg-4{padding-bottom:1.5rem!important}.pl-lg-4,.px-lg-4{padding-left:1.5rem!important}.p-lg-5{padding:3rem!important}.pt-lg-5,.py-lg-5{padding-top:3rem!important}.pr-lg-5,.px-lg-5{padding-right:3rem!important}.pb-lg-5,.py-lg-5{padding-bottom:3rem!important}.pl-lg-5,.px-lg-5{padding-left:3rem!important}.m-lg-n1{margin:-.25rem!important}.mt-lg-n1,.my-lg-n1{margin-top:-.25rem!important}.mr-lg-n1,.mx-lg-n1{margin-right:-.25rem!important}.mb-lg-n1,.my-lg-n1{margin-bottom:-.25rem!important}.ml-lg-n1,.mx-lg-n1{margin-left:-.25rem!important}.m-lg-n2{margin:-.5rem!important}.mt-lg-n2,.my-lg-n2{margin-top:-.5rem!important}.mr-lg-n2,.mx-lg-n2{margin-right:-.5rem!important}.mb-lg-n2,.my-lg-n2{margin-bottom:-.5rem!important}.ml-lg-n2,.mx-lg-n2{margin-left:-.5rem!important}.m-lg-n3{margin:-1rem!important}.mt-lg-n3,.my-lg-n3{margin-top:-1rem!important}.mr-lg-n3,.mx-lg-n3{margin-right:-1rem!important}.mb-lg-n3,.my-lg-n3{margin-bottom:-1rem!important}.ml-lg-n3,.mx-lg-n3{margin-left:-1rem!important}.m-lg-n4{margin:-1.5rem!important}.mt-lg-n4,.my-lg-n4{margin-top:-1.5rem!important}.mr-lg-n4,.mx-lg-n4{margin-right:-1.5rem!important}.mb-lg-n4,.my-lg-n4{margin-bottom:-1.5rem!important}.ml-lg-n4,.mx-lg-n4{margin-left:-1.5rem!important}.m-lg-n5{margin:-3rem!important}.mt-lg-n5,.my-lg-n5{margin-top:-3rem!important}.mr-lg-n5,.mx-lg-n5{margin-right:-3rem!important}.mb-lg-n5,.my-lg-n5{margin-bottom:-3rem!important}.ml-lg-n5,.mx-lg-n5{margin-left:-3rem!important}.m-lg-auto{margin:auto!important}.mt-lg-auto,.my-lg-auto{margin-top:auto!important}.mr-lg-auto,.mx-lg-auto{margin-right:auto!important}.mb-lg-auto,.my-lg-auto{margin-bottom:auto!important}.ml-lg-auto,.mx-lg-auto{margin-left:auto!important}}@media (min-width:10px){.m-xl-0{margin:0!important}.mt-xl-0,.my-xl-0{margin-top:0!important}.mr-xl-0,.mx-xl-0{margin-right:0!important}.mb-xl-0,.my-xl-0{margin-bottom:0!important}.ml-xl-0,.mx-xl-0{margin-left:0!important}.m-xl-1{margin:.25rem!important}.mt-xl-1,.my-xl-1{margin-top:.25rem!important}.mr-xl-1,.mx-xl-1{margin-right:.25rem!important}.mb-xl-1,.my-xl-1{margin-bottom:.25rem!important}.ml-xl-1,.mx-xl-1{margin-left:.25rem!important}.m-xl-2{margin:.5rem!important}.mt-xl-2,.my-xl-2{margin-top:.5rem!important}.mr-xl-2,.mx-xl-2{margin-right:.5rem!important}.mb-xl-2,.my-xl-2{margin-bottom:.5rem!important}.ml-xl-2,.mx-xl-2{margin-left:.5rem!important}.m-xl-3{margin:1rem!important}.mt-xl-3,.my-xl-3{margin-top:1rem!important}.mr-xl-3,.mx-xl-3{margin-right:1rem!important}.mb-xl-3,.my-xl-3{margin-bottom:1rem!important}.ml-xl-3,.mx-xl-3{margin-left:1rem!important}.m-xl-4{margin:1.5rem!important}.mt-xl-4,.my-xl-4{margin-top:1.5rem!important}.mr-xl-4,.mx-xl-4{margin-right:1.5rem!important}.mb-xl-4,.my-xl-4{margin-bottom:1.5rem!important}.ml-xl-4,.mx-xl-4{margin-left:1.5rem!important}.m-xl-5{margin:3rem!important}.mt-xl-5,.my-xl-5{margin-top:3rem!important}.mr-xl-5,.mx-xl-5{margin-right:3rem!important}.mb-xl-5,.my-xl-5{margin-bottom:3rem!important}.ml-xl-5,.mx-xl-5{margin-left:3rem!important}.p-xl-0{padding:0!important}.pt-xl-0,.py-xl-0{padding-top:0!important}.pr-xl-0,.px-xl-0{padding-right:0!important}.pb-xl-0,.py-xl-0{padding-bottom:0!important}.pl-xl-0,.px-xl-0{padding-left:0!important}.p-xl-1{padding:.25rem!important}.pt-xl-1,.py-xl-1{padding-top:.25rem!important}.pr-xl-1,.px-xl-1{padding-right:.25rem!important}.pb-xl-1,.py-xl-1{padding-bottom:.25rem!important}.pl-xl-1,.px-xl-1{padding-left:.25rem!important}.p-xl-2{padding:.5rem!important}.pt-xl-2,.py-xl-2{padding-top:.5rem!important}.pr-xl-2,.px-xl-2{padding-right:.5rem!important}.pb-xl-2,.py-xl-2{padding-bottom:.5rem!important}.pl-xl-2,.px-xl-2{padding-left:.5rem!important}.p-xl-3{padding:1rem!important}.pt-xl-3,.py-xl-3{padding-top:1rem!important}.pr-xl-3,.px-xl-3{padding-right:1rem!important}.pb-xl-3,.py-xl-3{padding-bottom:1rem!important}.pl-xl-3,.px-xl-3{padding-left:1rem!important}.p-xl-4{padding:1.5rem!important}.pt-xl-4,.py-xl-4{padding-top:1.5rem!important}.pr-xl-4,.px-xl-4{padding-right:1.5rem!important}.pb-xl-4,.py-xl-4{padding-bottom:1.5rem!important}.pl-xl-4,.px-xl-4{padding-left:1.5rem!important}.p-xl-5{padding:3rem!important}.pt-xl-5,.py-xl-5{padding-top:3rem!important}.pr-xl-5,.px-xl-5{padding-right:3rem!important}.pb-xl-5,.py-xl-5{padding-bottom:3rem!important}.pl-xl-5,.px-xl-5{padding-left:3rem!important}.m-xl-n1{margin:-.25rem!important}.mt-xl-n1,.my-xl-n1{margin-top:-.25rem!important}.mr-xl-n1,.mx-xl-n1{margin-right:-.25rem!important}.mb-xl-n1,.my-xl-n1{margin-bottom:-.25rem!important}.ml-xl-n1,.mx-xl-n1{margin-left:-.25rem!important}.m-xl-n2{margin:-.5rem!important}.mt-xl-n2,.my-xl-n2{margin-top:-.5rem!important}.mr-xl-n2,.mx-xl-n2{margin-right:-.5rem!important}.mb-xl-n2,.my-xl-n2{margin-bottom:-.5rem!important}.ml-xl-n2,.mx-xl-n2{margin-left:-.5rem!important}.m-xl-n3{margin:-1rem!important}.mt-xl-n3,.my-xl-n3{margin-top:-1rem!important}.mr-xl-n3,.mx-xl-n3{margin-right:-1rem!important}.mb-xl-n3,.my-xl-n3{margin-bottom:-1rem!important}.ml-xl-n3,.mx-xl-n3{margin-left:-1rem!important}.m-xl-n4{margin:-1.5rem!important}.mt-xl-n4,.my-xl-n4{margin-top:-1.5rem!important}.mr-xl-n4,.mx-xl-n4{margin-right:-1.5rem!important}.mb-xl-n4,.my-xl-n4{margin-bottom:-1.5rem!important}.ml-xl-n4,.mx-xl-n4{margin-left:-1.5rem!important}.m-xl-n5{margin:-3rem!important}.mt-xl-n5,.my-xl-n5{margin-top:-3rem!important}.mr-xl-n5,.mx-xl-n5{margin-right:-3rem!important}.mb-xl-n5,.my-xl-n5{margin-bottom:-3rem!important}.ml-xl-n5,.mx-xl-n5{margin-left:-3rem!important}.m-xl-auto{margin:auto!important}.mt-xl-auto,.my-xl-auto{margin-top:auto!important}.mr-xl-auto,.mx-xl-auto{margin-right:auto!important}.mb-xl-auto,.my-xl-auto{margin-bottom:auto!important}.ml-xl-auto,.mx-xl-auto{margin-left:auto!important}}.stretched-link:after{background-color:transparent;bottom:0;content:"";left:0;pointer-events:auto;position:absolute;right:0;top:0;z-index:1}.text-monospace{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace!important}.text-justify{text-align:justify!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-left{text-align:left!important}.text-right{text-align:right!important}.text-center{text-align:center!important}@media (min-width:2px){.text-sm-left{text-align:left!important}.text-sm-right{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:8px){.text-md-left{text-align:left!important}.text-md-right{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:9px){.text-lg-left{text-align:left!important}.text-lg-right{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:10px){.text-xl-left{text-align:left!important}.text-xl-right{text-align:right!important}.text-xl-center{text-align:center!important}}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.font-weight-light{font-weight:300!important}.font-weight-lighter{font-weight:lighter!important}.font-weight-normal{font-weight:400!important}.font-weight-bold{font-weight:600!important}.font-weight-bolder{font-weight:bolder!important}.font-italic{font-style:italic!important}.text-white{color:#fff!important}.text-primary{color:#4040c8!important}a.text-primary:focus,a.text-primary:hover{color:#2a2a92!important}.text-secondary{color:#4b5563!important}a.text-secondary:focus,a.text-secondary:hover{color:#2a3037!important}.text-success{color:#059669!important}a.text-success:focus,a.text-success:hover{color:#034c35!important}.text-info{color:#2563eb!important}a.text-info:focus,a.text-info:hover{color:#1043b3!important}.text-warning{color:#d97706!important}a.text-warning:focus,a.text-warning:hover{color:#8f4e04!important}.text-danger{color:#dc2626!important}a.text-danger:focus,a.text-danger:hover{color:#9c1919!important}.text-light{color:#f3f4f6!important}a.text-light:focus,a.text-light:hover{color:#c7ccd5!important}.text-dark{color:#1f2937!important}a.text-dark:focus,a.text-dark:hover{color:#030506!important}.text-body{color:#f3f4f6!important}.text-muted{color:#9ca3af!important}.text-black-50{color:rgba(0,0,0,.5)!important}.text-white-50{color:hsla(0,0%,100%,.5)!important}.text-hide{background-color:transparent;border:0;color:transparent;font:0/0 a;text-shadow:none}.text-decoration-none{text-decoration:none!important}.text-break{word-wrap:break-word!important;word-break:break-word!important}.text-reset{color:inherit!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}@media print{*,:after,:before{box-shadow:none!important;text-shadow:none!important}a:not(.btn){text-decoration:underline}abbr[title]:after{content:" (" attr(title) ")"}pre{white-space:pre-wrap!important}blockquote,pre{border:1px solid #6b7280}blockquote,img,pre,tr{page-break-inside:avoid}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}@page{size:a3}.container,body{min-width:9px!important}.navbar{display:none}.badge{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #d1d5db!important}.table-dark{color:inherit}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#374151}.table .thead-dark th{border-color:#374151;color:inherit}}.vjs-tree{color:#bfc7d5!important;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.vjs-tree .vjs-tree__content{border-left:1px dotted hsla(0,0%,80%,.28)!important}.vjs-tree .vjs-tree__node{cursor:pointer}.vjs-tree .vjs-tree__node:hover{color:#20a0ff}.vjs-tree .vjs-checkbox{left:-30px;position:absolute}.vjs-tree .vjs-value__boolean,.vjs-tree .vjs-value__null,.vjs-tree .vjs-value__number{color:#a291f5!important}.vjs-tree .vjs-value__string{color:#c3e88d!important}.vjs-tree .vjs-key{color:#c3cbd3!important}.hljs-addition,.hljs-attr,.hljs-keyword,.hljs-selector-tag{color:#13ce66}.hljs-bullet,.hljs-meta,.hljs-name,.hljs-string,.hljs-symbol,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable{color:#c3e88d}.hljs-comment,.hljs-deletion,.hljs-quote{color:#bfcbd9}.hljs-literal,.hljs-number,.hljs-title{color:#a291f5!important}body{padding-bottom:20px}.container{max-width:1440px}html{min-width:1140px}[v-cloak]{display:none}svg.icon{height:1rem;width:1rem}.header{border-bottom:1px solid #374151}.header .logo{color:#e5e7eb;text-decoration:none}.header .logo svg{height:1.7rem;width:1.7rem}.sidebar .nav-item a{border-radius:6px;color:#9ca3af;margin-bottom:4px;padding:.5rem .75rem}.sidebar .nav-item a svg{fill:#6b7280;height:1.25rem;margin-right:15px;width:1.25rem}.sidebar .nav-item a:hover{background-color:#1f2937;color:#d1d5db}.sidebar .nav-item a.active{background-color:#1f2937;color:#818cf8}.sidebar .nav-item a.active svg{fill:#6366f1}.card{border:none;box-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -2px rgba(0,0,0,.1)}.card .bottom-radius{border-bottom-left-radius:6px;border-bottom-right-radius:6px}.card .card-header{background-color:#374151;border-bottom:none;min-height:60px;padding-bottom:.7rem;padding-top:.7rem}.card .card-header .btn-group .btn{padding:.2rem .5rem}.card .card-header .form-control-with-icon{position:relative}.card .card-header .form-control-with-icon .icon-wrapper{align-items:center;bottom:0;display:flex;justify-content:center;left:.75rem;position:absolute;top:0}.card .card-header .form-control-with-icon .icon-wrapper .icon{fill:#9ca3af}.card .card-header .form-control-with-icon .form-control{border-radius:9999px;font-size:.875rem;padding-left:2.25rem}.card .table td,.card .table th{padding:.75rem 1.25rem}.card .table th{background-color:#1f2937;border-bottom:0;font-size:.875rem;padding:.5rem 1.25rem}.card .table:not(.table-borderless) td{border-top:1px solid #374151}.card .table.penultimate-column-right td:nth-last-child(2),.card .table.penultimate-column-right th:nth-last-child(2){text-align:right}.card .table td.table-fit,.card .table th.table-fit{white-space:nowrap;width:1%}.fill-text-color{fill:#f3f4f6}.fill-danger{fill:#dc2626}.fill-warning{fill:#d97706}.fill-info{fill:#2563eb}.fill-success{fill:#059669}.fill-primary{fill:#4040c8}button:hover .fill-primary{fill:#fff}.btn-outline-primary.active .fill-primary{fill:#111827}.btn-outline-primary:not(:disabled):not(.disabled).active:focus{box-shadow:none!important}.btn-muted{background:#1f2937;color:#9ca3af}.btn-muted:focus,.btn-muted:hover{background:#374151;color:#d1d5db}.btn-muted.active{background:#4040c8;color:#fff}.badge-secondary{background:#d1d5db;color:#374151}.badge-success{background:#10b981;color:#fff}.badge-info{background:#3b82f6;color:#fff}.badge-warning{background:#f59e0b;color:#fff}.badge-danger{background:#ef4444;color:#fff}.control-action svg{fill:#6b7280;height:1.2rem;width:1.2rem}.control-action svg:hover{fill:#818cf8}@keyframes spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.spin{animation:spin 2s linear infinite}.card .nav-pills{background:#374151}.card .nav-pills .nav-link{border-radius:0;color:#9ca3af;font-size:.9rem;padding:.75rem 1.25rem}.card .nav-pills .nav-link:focus,.card .nav-pills .nav-link:hover{color:#e5e7eb}.card .nav-pills .nav-link.active{background:none;border-bottom:2px solid #a5b4fc;color:#a5b4fc}.list-enter-active:not(.dontanimate){transition:background 1s linear}.list-enter:not(.dontanimate),.list-leave-to:not(.dontanimate){background:#312e81}.code-bg .list-enter:not(.dontanimate),.code-bg .list-leave-to:not(.dontanimate){background:#4b5563}#indexScreen td{vertical-align:middle!important}.card-bg-secondary{background:#1f2937}.code-bg{background:#292d3e}.disabled-watcher{background:#dc2626;color:#fff;padding:.75rem}.copy-to-clipboard{--tw-text-opacity:1;color:rgb(231 232 242/var(--tw-text-opacity));opacity:.7;outline:2px solid transparent;outline-offset:2px;position:absolute;right:0;top:0;z-index:10} diff --git a/public/vendor/telescope/app.css b/public/vendor/telescope/app.css new file mode 100644 index 000000000..1f6375bf1 --- /dev/null +++ b/public/vendor/telescope/app.css @@ -0,0 +1,7 @@ +@charset "UTF-8"; +/*! + * Bootstrap v4.6.2 (https://getbootstrap.com/) + * Copyright 2011-2022 The Bootstrap Authors + * Copyright 2011-2022 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + */:root{--blue:#007bff;--indigo:#6610f2;--purple:#6f42c1;--pink:#e83e8c;--red:#dc3545;--orange:#fd7e14;--yellow:#ffc107;--green:#28a745;--teal:#20c997;--cyan:#17a2b8;--white:#fff;--gray:#4b5563;--gray-dark:#1f2937;--primary:#4040c8;--secondary:#4b5563;--success:#059669;--info:#2563eb;--warning:#d97706;--danger:#dc2626;--light:#f3f4f6;--dark:#1f2937;--breakpoint-xs:0;--breakpoint-sm:2px;--breakpoint-md:8px;--breakpoint-lg:9px;--breakpoint-xl:10px;--font-family-sans-serif:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-family-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}*,:after,:before{box-sizing:border-box}html{-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:rgba(0,0,0,0);font-family:sans-serif;line-height:1.15}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{background-color:#f3f4f6;color:#111827;font-family:Figtree,sans-serif;font-size:1rem;font-weight:400;line-height:1.5;margin:0;text-align:left}[tabindex="-1"]:focus:not(:focus-visible){outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-bottom:.5rem;margin-top:0}p{margin-bottom:1rem;margin-top:0}abbr[data-original-title],abbr[title]{border-bottom:0;cursor:help;text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{font-style:normal;line-height:inherit}address,dl,ol,ul{margin-bottom:1rem}dl,ol,ul{margin-top:0}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:600}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{background-color:transparent;color:#6366f1;text-decoration:none}a:hover{color:#4f46e5;text-decoration:underline}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}pre{-ms-overflow-style:scrollbar;margin-bottom:1rem;margin-top:0;overflow:auto}figure{margin:0 0 1rem}img{border-style:none}img,svg{vertical-align:middle}svg{overflow:hidden}table{border-collapse:collapse}caption{caption-side:bottom;color:#6b7280;padding-bottom:.75rem;padding-top:.75rem;text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit;margin:0}button,input{overflow:visible}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}textarea{overflow:auto;resize:vertical}fieldset{border:0;margin:0;min-width:0;padding:0}legend{color:inherit;display:block;font-size:1.5rem;line-height:inherit;margin-bottom:.5rem;max-width:100%;padding:0;white-space:normal;width:100%}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:none;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}output{display:inline-block}summary{cursor:pointer;display:list-item}template{display:none}[hidden]{display:none!important}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{font-weight:500;line-height:1.2;margin-bottom:.5rem}.h1,h1{font-size:2.5rem}.h2,h2{font-size:2rem}.h3,h3{font-size:1.75rem}.h4,h4{font-size:1.5rem}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:6rem}.display-1,.display-2{font-weight:300;line-height:1.2}.display-2{font-size:5.5rem}.display-3{font-size:4.5rem}.display-3,.display-4{font-weight:300;line-height:1.2}.display-4{font-size:3.5rem}hr{border:0;border-top:1px solid rgba(0,0,0,.1);margin-bottom:1rem;margin-top:1rem}.small,small{font-size:.875em;font-weight:400}.mark,mark{background-color:#fcf8e3;padding:.2em}.list-inline,.list-unstyled{list-style:none;padding-left:0}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:90%;text-transform:uppercase}.blockquote{font-size:1.25rem;margin-bottom:1rem}.blockquote-footer{color:#4b5563;display:block;font-size:.875em}.blockquote-footer:before{content:"— "}.img-fluid,.img-thumbnail{height:auto;max-width:100%}.img-thumbnail{background-color:#f3f4f6;border:1px solid #d1d5db;border-radius:.25rem;padding:.25rem}.figure{display:inline-block}.figure-img{line-height:1;margin-bottom:.5rem}.figure-caption{color:#4b5563;font-size:90%}code{word-wrap:break-word;color:#e83e8c;font-size:87.5%}a>code{color:inherit}kbd{background-color:#111827;border-radius:.2rem;color:#fff;font-size:87.5%;padding:.2rem .4rem}kbd kbd{font-size:100%;font-weight:600;padding:0}pre{color:#111827;display:block;font-size:87.5%}pre code{color:inherit;font-size:inherit;word-break:normal}.pre-scrollable{max-height:340px;overflow-y:scroll}.container,.container-fluid,.container-lg,.container-md,.container-sm,.container-xl{margin-left:auto;margin-right:auto;padding-left:15px;padding-right:15px;width:100%}@media (min-width:2px){.container,.container-sm{max-width:1137px}}@media (min-width:8px){.container,.container-md,.container-sm{max-width:1138px}}@media (min-width:9px){.container,.container-lg,.container-md,.container-sm{max-width:1139px}}@media (min-width:10px){.container,.container-lg,.container-md,.container-sm,.container-xl{max-width:1140px}}.row{display:flex;flex-wrap:wrap;margin-left:-15px;margin-right:-15px}.no-gutters{margin-left:0;margin-right:0}.no-gutters>.col,.no-gutters>[class*=col-]{padding-left:0;padding-right:0}.col,.col-1,.col-10,.col-11,.col-12,.col-2,.col-3,.col-4,.col-5,.col-6,.col-7,.col-8,.col-9,.col-auto,.col-lg,.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto,.col-md,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto,.col-sm,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto,.col-xl,.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{padding-left:15px;padding-right:15px;position:relative;width:100%}.col{flex-basis:0;flex-grow:1;max-width:100%}.row-cols-1>*{flex:0 0 100%;max-width:100%}.row-cols-2>*{flex:0 0 50%;max-width:50%}.row-cols-3>*{flex:0 0 33.3333333333%;max-width:33.3333333333%}.row-cols-4>*{flex:0 0 25%;max-width:25%}.row-cols-5>*{flex:0 0 20%;max-width:20%}.row-cols-6>*{flex:0 0 16.6666666667%;max-width:16.6666666667%}.col-auto{flex:0 0 auto;max-width:100%;width:auto}.col-1{flex:0 0 8.33333333%;max-width:8.33333333%}.col-2{flex:0 0 16.66666667%;max-width:16.66666667%}.col-3{flex:0 0 25%;max-width:25%}.col-4{flex:0 0 33.33333333%;max-width:33.33333333%}.col-5{flex:0 0 41.66666667%;max-width:41.66666667%}.col-6{flex:0 0 50%;max-width:50%}.col-7{flex:0 0 58.33333333%;max-width:58.33333333%}.col-8{flex:0 0 66.66666667%;max-width:66.66666667%}.col-9{flex:0 0 75%;max-width:75%}.col-10{flex:0 0 83.33333333%;max-width:83.33333333%}.col-11{flex:0 0 91.66666667%;max-width:91.66666667%}.col-12{flex:0 0 100%;max-width:100%}.order-first{order:-1}.order-last{order:13}.order-0{order:0}.order-1{order:1}.order-2{order:2}.order-3{order:3}.order-4{order:4}.order-5{order:5}.order-6{order:6}.order-7{order:7}.order-8{order:8}.order-9{order:9}.order-10{order:10}.order-11{order:11}.order-12{order:12}.offset-1{margin-left:8.33333333%}.offset-2{margin-left:16.66666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.33333333%}.offset-5{margin-left:41.66666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.33333333%}.offset-8{margin-left:66.66666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.33333333%}.offset-11{margin-left:91.66666667%}@media (min-width:2px){.col-sm{flex-basis:0;flex-grow:1;max-width:100%}.row-cols-sm-1>*{flex:0 0 100%;max-width:100%}.row-cols-sm-2>*{flex:0 0 50%;max-width:50%}.row-cols-sm-3>*{flex:0 0 33.3333333333%;max-width:33.3333333333%}.row-cols-sm-4>*{flex:0 0 25%;max-width:25%}.row-cols-sm-5>*{flex:0 0 20%;max-width:20%}.row-cols-sm-6>*{flex:0 0 16.6666666667%;max-width:16.6666666667%}.col-sm-auto{flex:0 0 auto;max-width:100%;width:auto}.col-sm-1{flex:0 0 8.33333333%;max-width:8.33333333%}.col-sm-2{flex:0 0 16.66666667%;max-width:16.66666667%}.col-sm-3{flex:0 0 25%;max-width:25%}.col-sm-4{flex:0 0 33.33333333%;max-width:33.33333333%}.col-sm-5{flex:0 0 41.66666667%;max-width:41.66666667%}.col-sm-6{flex:0 0 50%;max-width:50%}.col-sm-7{flex:0 0 58.33333333%;max-width:58.33333333%}.col-sm-8{flex:0 0 66.66666667%;max-width:66.66666667%}.col-sm-9{flex:0 0 75%;max-width:75%}.col-sm-10{flex:0 0 83.33333333%;max-width:83.33333333%}.col-sm-11{flex:0 0 91.66666667%;max-width:91.66666667%}.col-sm-12{flex:0 0 100%;max-width:100%}.order-sm-first{order:-1}.order-sm-last{order:13}.order-sm-0{order:0}.order-sm-1{order:1}.order-sm-2{order:2}.order-sm-3{order:3}.order-sm-4{order:4}.order-sm-5{order:5}.order-sm-6{order:6}.order-sm-7{order:7}.order-sm-8{order:8}.order-sm-9{order:9}.order-sm-10{order:10}.order-sm-11{order:11}.order-sm-12{order:12}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.33333333%}.offset-sm-2{margin-left:16.66666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.33333333%}.offset-sm-5{margin-left:41.66666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.33333333%}.offset-sm-8{margin-left:66.66666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.33333333%}.offset-sm-11{margin-left:91.66666667%}}@media (min-width:8px){.col-md{flex-basis:0;flex-grow:1;max-width:100%}.row-cols-md-1>*{flex:0 0 100%;max-width:100%}.row-cols-md-2>*{flex:0 0 50%;max-width:50%}.row-cols-md-3>*{flex:0 0 33.3333333333%;max-width:33.3333333333%}.row-cols-md-4>*{flex:0 0 25%;max-width:25%}.row-cols-md-5>*{flex:0 0 20%;max-width:20%}.row-cols-md-6>*{flex:0 0 16.6666666667%;max-width:16.6666666667%}.col-md-auto{flex:0 0 auto;max-width:100%;width:auto}.col-md-1{flex:0 0 8.33333333%;max-width:8.33333333%}.col-md-2{flex:0 0 16.66666667%;max-width:16.66666667%}.col-md-3{flex:0 0 25%;max-width:25%}.col-md-4{flex:0 0 33.33333333%;max-width:33.33333333%}.col-md-5{flex:0 0 41.66666667%;max-width:41.66666667%}.col-md-6{flex:0 0 50%;max-width:50%}.col-md-7{flex:0 0 58.33333333%;max-width:58.33333333%}.col-md-8{flex:0 0 66.66666667%;max-width:66.66666667%}.col-md-9{flex:0 0 75%;max-width:75%}.col-md-10{flex:0 0 83.33333333%;max-width:83.33333333%}.col-md-11{flex:0 0 91.66666667%;max-width:91.66666667%}.col-md-12{flex:0 0 100%;max-width:100%}.order-md-first{order:-1}.order-md-last{order:13}.order-md-0{order:0}.order-md-1{order:1}.order-md-2{order:2}.order-md-3{order:3}.order-md-4{order:4}.order-md-5{order:5}.order-md-6{order:6}.order-md-7{order:7}.order-md-8{order:8}.order-md-9{order:9}.order-md-10{order:10}.order-md-11{order:11}.order-md-12{order:12}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.33333333%}.offset-md-2{margin-left:16.66666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.33333333%}.offset-md-5{margin-left:41.66666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.33333333%}.offset-md-8{margin-left:66.66666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.33333333%}.offset-md-11{margin-left:91.66666667%}}@media (min-width:9px){.col-lg{flex-basis:0;flex-grow:1;max-width:100%}.row-cols-lg-1>*{flex:0 0 100%;max-width:100%}.row-cols-lg-2>*{flex:0 0 50%;max-width:50%}.row-cols-lg-3>*{flex:0 0 33.3333333333%;max-width:33.3333333333%}.row-cols-lg-4>*{flex:0 0 25%;max-width:25%}.row-cols-lg-5>*{flex:0 0 20%;max-width:20%}.row-cols-lg-6>*{flex:0 0 16.6666666667%;max-width:16.6666666667%}.col-lg-auto{flex:0 0 auto;max-width:100%;width:auto}.col-lg-1{flex:0 0 8.33333333%;max-width:8.33333333%}.col-lg-2{flex:0 0 16.66666667%;max-width:16.66666667%}.col-lg-3{flex:0 0 25%;max-width:25%}.col-lg-4{flex:0 0 33.33333333%;max-width:33.33333333%}.col-lg-5{flex:0 0 41.66666667%;max-width:41.66666667%}.col-lg-6{flex:0 0 50%;max-width:50%}.col-lg-7{flex:0 0 58.33333333%;max-width:58.33333333%}.col-lg-8{flex:0 0 66.66666667%;max-width:66.66666667%}.col-lg-9{flex:0 0 75%;max-width:75%}.col-lg-10{flex:0 0 83.33333333%;max-width:83.33333333%}.col-lg-11{flex:0 0 91.66666667%;max-width:91.66666667%}.col-lg-12{flex:0 0 100%;max-width:100%}.order-lg-first{order:-1}.order-lg-last{order:13}.order-lg-0{order:0}.order-lg-1{order:1}.order-lg-2{order:2}.order-lg-3{order:3}.order-lg-4{order:4}.order-lg-5{order:5}.order-lg-6{order:6}.order-lg-7{order:7}.order-lg-8{order:8}.order-lg-9{order:9}.order-lg-10{order:10}.order-lg-11{order:11}.order-lg-12{order:12}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.33333333%}.offset-lg-2{margin-left:16.66666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.33333333%}.offset-lg-5{margin-left:41.66666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.33333333%}.offset-lg-8{margin-left:66.66666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.33333333%}.offset-lg-11{margin-left:91.66666667%}}@media (min-width:10px){.col-xl{flex-basis:0;flex-grow:1;max-width:100%}.row-cols-xl-1>*{flex:0 0 100%;max-width:100%}.row-cols-xl-2>*{flex:0 0 50%;max-width:50%}.row-cols-xl-3>*{flex:0 0 33.3333333333%;max-width:33.3333333333%}.row-cols-xl-4>*{flex:0 0 25%;max-width:25%}.row-cols-xl-5>*{flex:0 0 20%;max-width:20%}.row-cols-xl-6>*{flex:0 0 16.6666666667%;max-width:16.6666666667%}.col-xl-auto{flex:0 0 auto;max-width:100%;width:auto}.col-xl-1{flex:0 0 8.33333333%;max-width:8.33333333%}.col-xl-2{flex:0 0 16.66666667%;max-width:16.66666667%}.col-xl-3{flex:0 0 25%;max-width:25%}.col-xl-4{flex:0 0 33.33333333%;max-width:33.33333333%}.col-xl-5{flex:0 0 41.66666667%;max-width:41.66666667%}.col-xl-6{flex:0 0 50%;max-width:50%}.col-xl-7{flex:0 0 58.33333333%;max-width:58.33333333%}.col-xl-8{flex:0 0 66.66666667%;max-width:66.66666667%}.col-xl-9{flex:0 0 75%;max-width:75%}.col-xl-10{flex:0 0 83.33333333%;max-width:83.33333333%}.col-xl-11{flex:0 0 91.66666667%;max-width:91.66666667%}.col-xl-12{flex:0 0 100%;max-width:100%}.order-xl-first{order:-1}.order-xl-last{order:13}.order-xl-0{order:0}.order-xl-1{order:1}.order-xl-2{order:2}.order-xl-3{order:3}.order-xl-4{order:4}.order-xl-5{order:5}.order-xl-6{order:6}.order-xl-7{order:7}.order-xl-8{order:8}.order-xl-9{order:9}.order-xl-10{order:10}.order-xl-11{order:11}.order-xl-12{order:12}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.33333333%}.offset-xl-2{margin-left:16.66666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.33333333%}.offset-xl-5{margin-left:41.66666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.33333333%}.offset-xl-8{margin-left:66.66666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.33333333%}.offset-xl-11{margin-left:91.66666667%}}.table{color:#111827;margin-bottom:1rem;width:100%}.table td,.table th{border-top:1px solid #e5e7eb;padding:.75rem;vertical-align:top}.table thead th{border-bottom:2px solid #e5e7eb;vertical-align:bottom}.table tbody+tbody{border-top:2px solid #e5e7eb}.table-sm td,.table-sm th{padding:.3rem}.table-bordered,.table-bordered td,.table-bordered th{border:1px solid #e5e7eb}.table-bordered thead td,.table-bordered thead th{border-bottom-width:2px}.table-borderless tbody+tbody,.table-borderless td,.table-borderless th,.table-borderless thead th{border:0}.table-striped tbody tr:nth-of-type(odd){background-color:rgba(0,0,0,.05)}.table-hover tbody tr:hover{background-color:#f3f4f6;color:#111827}.table-primary,.table-primary>td,.table-primary>th{background-color:#cacaf0}.table-primary tbody+tbody,.table-primary td,.table-primary th,.table-primary thead th{border-color:#9c9ce2}.table-hover .table-primary:hover,.table-hover .table-primary:hover>td,.table-hover .table-primary:hover>th{background-color:#b6b6ea}.table-secondary,.table-secondary>td,.table-secondary>th{background-color:#cdcfd3}.table-secondary tbody+tbody,.table-secondary td,.table-secondary th,.table-secondary thead th{border-color:#a1a7ae}.table-hover .table-secondary:hover,.table-hover .table-secondary:hover>td,.table-hover .table-secondary:hover>th{background-color:#bfc2c7}.table-success,.table-success>td,.table-success>th{background-color:#b9e2d5}.table-success tbody+tbody,.table-success td,.table-success th,.table-success thead th{border-color:#7dc8b1}.table-hover .table-success:hover,.table-hover .table-success:hover>td,.table-hover .table-success:hover>th{background-color:#a7dbca}.table-info,.table-info>td,.table-info>th{background-color:#c2d3f9}.table-info tbody+tbody,.table-info td,.table-info th,.table-info thead th{border-color:#8eaef5}.table-hover .table-info:hover,.table-hover .table-info:hover>td,.table-hover .table-info:hover>th{background-color:#abc2f7}.table-warning,.table-warning>td,.table-warning>th{background-color:#f4d9b9}.table-warning tbody+tbody,.table-warning td,.table-warning th,.table-warning thead th{border-color:#ebb87e}.table-hover .table-warning:hover,.table-hover .table-warning:hover>td,.table-hover .table-warning:hover>th{background-color:#f1cda3}.table-danger,.table-danger>td,.table-danger>th{background-color:#f5c2c2}.table-danger tbody+tbody,.table-danger td,.table-danger th,.table-danger thead th{border-color:#ed8e8e}.table-hover .table-danger:hover,.table-hover .table-danger:hover>td,.table-hover .table-danger:hover>th{background-color:#f1acac}.table-light,.table-light>td,.table-light>th{background-color:#fcfcfc}.table-light tbody+tbody,.table-light td,.table-light th,.table-light thead th{border-color:#f9f9fa}.table-hover .table-light:hover,.table-hover .table-light:hover>td,.table-hover .table-light:hover>th{background-color:#efefef}.table-dark,.table-dark>td,.table-dark>th{background-color:#c0c3c7}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#8b9097}.table-hover .table-dark:hover,.table-hover .table-dark:hover>td,.table-hover .table-dark:hover>th{background-color:#b3b6bb}.table-active,.table-active>td,.table-active>th{background-color:#f3f4f6}.table-hover .table-active:hover,.table-hover .table-active:hover>td,.table-hover .table-active:hover>th{background-color:#e4e7eb}.table .thead-dark th{background-color:#1f2937;border-color:#2d3b4f;color:#fff}.table .thead-light th{background-color:#e5e7eb;border-color:#e5e7eb;color:#374151}.table-dark{background-color:#1f2937;color:#fff}.table-dark td,.table-dark th,.table-dark thead th{border-color:#2d3b4f}.table-dark.table-bordered{border:0}.table-dark.table-striped tbody tr:nth-of-type(odd){background-color:hsla(0,0%,100%,.05)}.table-dark.table-hover tbody tr:hover{background-color:hsla(0,0%,100%,.075);color:#fff}@media (max-width:1.98px){.table-responsive-sm{-webkit-overflow-scrolling:touch;display:block;overflow-x:auto;width:100%}.table-responsive-sm>.table-bordered{border:0}}@media (max-width:7.98px){.table-responsive-md{-webkit-overflow-scrolling:touch;display:block;overflow-x:auto;width:100%}.table-responsive-md>.table-bordered{border:0}}@media (max-width:8.98px){.table-responsive-lg{-webkit-overflow-scrolling:touch;display:block;overflow-x:auto;width:100%}.table-responsive-lg>.table-bordered{border:0}}@media (max-width:9.98px){.table-responsive-xl{-webkit-overflow-scrolling:touch;display:block;overflow-x:auto;width:100%}.table-responsive-xl>.table-bordered{border:0}}.table-responsive{-webkit-overflow-scrolling:touch;display:block;overflow-x:auto;width:100%}.table-responsive>.table-bordered{border:0}.form-control{background-clip:padding-box;background-color:#fff;border:1px solid #d1d5db;border-radius:.25rem;color:#1f2937;display:block;font-size:1rem;font-weight:400;height:calc(1.5em + .75rem + 2px);line-height:1.5;padding:.375rem .75rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;width:100%}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control::-ms-expand{background-color:transparent;border:0}.form-control:focus{background-color:#fff;border-color:#a3a3e5;box-shadow:0 0 0 .2rem rgba(64,64,200,.25);color:#1f2937;outline:0}.form-control::-moz-placeholder{color:#4b5563;opacity:1}.form-control::placeholder{color:#4b5563;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e5e7eb;opacity:1}input[type=date].form-control,input[type=datetime-local].form-control,input[type=month].form-control,input[type=time].form-control{-webkit-appearance:none;-moz-appearance:none;appearance:none}select.form-control:-moz-focusring{color:transparent;text-shadow:0 0 0 #1f2937}select.form-control:focus::-ms-value{background-color:#fff;color:#1f2937}.form-control-file,.form-control-range{display:block;width:100%}.col-form-label{font-size:inherit;line-height:1.5;margin-bottom:0;padding-bottom:calc(.375rem + 1px);padding-top:calc(.375rem + 1px)}.col-form-label-lg{font-size:1.25rem;line-height:1.5;padding-bottom:calc(.5rem + 1px);padding-top:calc(.5rem + 1px)}.col-form-label-sm{font-size:.875rem;line-height:1.5;padding-bottom:calc(.25rem + 1px);padding-top:calc(.25rem + 1px)}.form-control-plaintext{background-color:transparent;border:solid transparent;border-width:1px 0;color:#111827;display:block;font-size:1rem;line-height:1.5;margin-bottom:0;padding:.375rem 0;width:100%}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-left:0;padding-right:0}.form-control-sm{border-radius:.2rem;font-size:.875rem;height:calc(1.5em + .5rem + 2px);line-height:1.5;padding:.25rem .5rem}.form-control-lg{border-radius:6px;font-size:1.25rem;height:calc(1.5em + 1rem + 2px);line-height:1.5;padding:.5rem 1rem}select.form-control[multiple],select.form-control[size],textarea.form-control{height:auto}.form-group{margin-bottom:1rem}.form-text{display:block;margin-top:.25rem}.form-row{display:flex;flex-wrap:wrap;margin-left:-5px;margin-right:-5px}.form-row>.col,.form-row>[class*=col-]{padding-left:5px;padding-right:5px}.form-check{display:block;padding-left:1.25rem;position:relative}.form-check-input{margin-left:-1.25rem;margin-top:.3rem;position:absolute}.form-check-input:disabled~.form-check-label,.form-check-input[disabled]~.form-check-label{color:#6b7280}.form-check-label{margin-bottom:0}.form-check-inline{align-items:center;display:inline-flex;margin-right:.75rem;padding-left:0}.form-check-inline .form-check-input{margin-left:0;margin-right:.3125rem;margin-top:0;position:static}.valid-feedback{color:#059669;display:none;font-size:.875em;margin-top:.25rem;width:100%}.valid-tooltip{background-color:rgba(5,150,105,.9);border-radius:.25rem;color:#fff;display:none;font-size:.875rem;left:0;line-height:1.5;margin-top:.1rem;max-width:100%;padding:.25rem .5rem;position:absolute;top:100%;z-index:5}.form-row>.col>.valid-tooltip,.form-row>[class*=col-]>.valid-tooltip{left:5px}.is-valid~.valid-feedback,.is-valid~.valid-tooltip,.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip{display:block}.form-control.is-valid,.was-validated .form-control:valid{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8'%3E%3Cpath fill='%23059669' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3E%3C/svg%3E");background-position:right calc(.375em + .1875rem) center;background-repeat:no-repeat;background-size:calc(.75em + .375rem) calc(.75em + .375rem);border-color:#059669;padding-right:calc(1.5em + .75rem)!important}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:#059669;box-shadow:0 0 0 .2rem rgba(5,150,105,.25)}.was-validated select.form-control:valid,select.form-control.is-valid{background-position:right 1.5rem center;padding-right:3rem!important}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem);padding-right:calc(1.5em + .75rem)}.custom-select.is-valid,.was-validated .custom-select:valid{background:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5'%3E%3Cpath fill='%231f2937' d='M2 0 0 2h4zm0 5L0 3h4z'/%3E%3C/svg%3E") right .75rem center/8px 10px no-repeat,#fff url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8'%3E%3Cpath fill='%23059669' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3E%3C/svg%3E") center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem) no-repeat;border-color:#059669;padding-right:calc(.75em + 2.3125rem)!important}.custom-select.is-valid:focus,.was-validated .custom-select:valid:focus{border-color:#059669;box-shadow:0 0 0 .2rem rgba(5,150,105,.25)}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:#059669}.form-check-input.is-valid~.valid-feedback,.form-check-input.is-valid~.valid-tooltip,.was-validated .form-check-input:valid~.valid-feedback,.was-validated .form-check-input:valid~.valid-tooltip{display:block}.custom-control-input.is-valid~.custom-control-label,.was-validated .custom-control-input:valid~.custom-control-label{color:#059669}.custom-control-input.is-valid~.custom-control-label:before,.was-validated .custom-control-input:valid~.custom-control-label:before{border-color:#059669}.custom-control-input.is-valid:checked~.custom-control-label:before,.was-validated .custom-control-input:valid:checked~.custom-control-label:before{background-color:#07c78c;border-color:#07c78c}.custom-control-input.is-valid:focus~.custom-control-label:before,.was-validated .custom-control-input:valid:focus~.custom-control-label:before{box-shadow:0 0 0 .2rem rgba(5,150,105,.25)}.custom-control-input.is-valid:focus:not(:checked)~.custom-control-label:before,.was-validated .custom-control-input:valid:focus:not(:checked)~.custom-control-label:before{border-color:#059669}.custom-file-input.is-valid~.custom-file-label,.was-validated .custom-file-input:valid~.custom-file-label{border-color:#059669}.custom-file-input.is-valid:focus~.custom-file-label,.was-validated .custom-file-input:valid:focus~.custom-file-label{border-color:#059669;box-shadow:0 0 0 .2rem rgba(5,150,105,.25)}.invalid-feedback{color:#dc2626;display:none;font-size:.875em;margin-top:.25rem;width:100%}.invalid-tooltip{background-color:rgba(220,38,38,.9);border-radius:.25rem;color:#fff;display:none;font-size:.875rem;left:0;line-height:1.5;margin-top:.1rem;max-width:100%;padding:.25rem .5rem;position:absolute;top:100%;z-index:5}.form-row>.col>.invalid-tooltip,.form-row>[class*=col-]>.invalid-tooltip{left:5px}.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip,.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip{display:block}.form-control.is-invalid,.was-validated .form-control:invalid{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc2626'%3E%3Ccircle cx='6' cy='6' r='4.5'/%3E%3Cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3E%3Ccircle cx='6' cy='8.2' r='.6' fill='%23dc2626' stroke='none'/%3E%3C/svg%3E");background-position:right calc(.375em + .1875rem) center;background-repeat:no-repeat;background-size:calc(.75em + .375rem) calc(.75em + .375rem);border-color:#dc2626;padding-right:calc(1.5em + .75rem)!important}.form-control.is-invalid:focus,.was-validated .form-control:invalid:focus{border-color:#dc2626;box-shadow:0 0 0 .2rem rgba(220,38,38,.25)}.was-validated select.form-control:invalid,select.form-control.is-invalid{background-position:right 1.5rem center;padding-right:3rem!important}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem);padding-right:calc(1.5em + .75rem)}.custom-select.is-invalid,.was-validated .custom-select:invalid{background:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5'%3E%3Cpath fill='%231f2937' d='M2 0 0 2h4zm0 5L0 3h4z'/%3E%3C/svg%3E") right .75rem center/8px 10px no-repeat,#fff url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc2626'%3E%3Ccircle cx='6' cy='6' r='4.5'/%3E%3Cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3E%3Ccircle cx='6' cy='8.2' r='.6' fill='%23dc2626' stroke='none'/%3E%3C/svg%3E") center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem) no-repeat;border-color:#dc2626;padding-right:calc(.75em + 2.3125rem)!important}.custom-select.is-invalid:focus,.was-validated .custom-select:invalid:focus{border-color:#dc2626;box-shadow:0 0 0 .2rem rgba(220,38,38,.25)}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:#dc2626}.form-check-input.is-invalid~.invalid-feedback,.form-check-input.is-invalid~.invalid-tooltip,.was-validated .form-check-input:invalid~.invalid-feedback,.was-validated .form-check-input:invalid~.invalid-tooltip{display:block}.custom-control-input.is-invalid~.custom-control-label,.was-validated .custom-control-input:invalid~.custom-control-label{color:#dc2626}.custom-control-input.is-invalid~.custom-control-label:before,.was-validated .custom-control-input:invalid~.custom-control-label:before{border-color:#dc2626}.custom-control-input.is-invalid:checked~.custom-control-label:before,.was-validated .custom-control-input:invalid:checked~.custom-control-label:before{background-color:#e35252;border-color:#e35252}.custom-control-input.is-invalid:focus~.custom-control-label:before,.was-validated .custom-control-input:invalid:focus~.custom-control-label:before{box-shadow:0 0 0 .2rem rgba(220,38,38,.25)}.custom-control-input.is-invalid:focus:not(:checked)~.custom-control-label:before,.was-validated .custom-control-input:invalid:focus:not(:checked)~.custom-control-label:before{border-color:#dc2626}.custom-file-input.is-invalid~.custom-file-label,.was-validated .custom-file-input:invalid~.custom-file-label{border-color:#dc2626}.custom-file-input.is-invalid:focus~.custom-file-label,.was-validated .custom-file-input:invalid:focus~.custom-file-label{border-color:#dc2626;box-shadow:0 0 0 .2rem rgba(220,38,38,.25)}.form-inline{align-items:center;display:flex;flex-flow:row wrap}.form-inline .form-check{width:100%}@media (min-width:2px){.form-inline label{justify-content:center}.form-inline .form-group,.form-inline label{align-items:center;display:flex;margin-bottom:0}.form-inline .form-group{flex:0 0 auto;flex-flow:row wrap}.form-inline .form-control{display:inline-block;vertical-align:middle;width:auto}.form-inline .form-control-plaintext{display:inline-block}.form-inline .custom-select,.form-inline .input-group{width:auto}.form-inline .form-check{align-items:center;display:flex;justify-content:center;padding-left:0;width:auto}.form-inline .form-check-input{flex-shrink:0;margin-left:0;margin-right:.25rem;margin-top:0;position:relative}.form-inline .custom-control{align-items:center;justify-content:center}.form-inline .custom-control-label{margin-bottom:0}}.btn{background-color:transparent;border:1px solid transparent;border-radius:.25rem;color:#111827;display:inline-block;font-size:1rem;font-weight:400;line-height:1.5;padding:.375rem .75rem;text-align:center;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-user-select:none;-moz-user-select:none;user-select:none;vertical-align:middle}@media (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:#111827;text-decoration:none}.btn.focus,.btn:focus{box-shadow:0 0 0 .2rem rgba(64,64,200,.25);outline:0}.btn.disabled,.btn:disabled{opacity:.65}.btn:not(:disabled):not(.disabled){cursor:pointer}a.btn.disabled,fieldset:disabled a.btn{pointer-events:none}.btn-primary{background-color:#4040c8;border-color:#4040c8;color:#fff}.btn-primary.focus,.btn-primary:focus,.btn-primary:hover{background-color:#3232af;border-color:#3030a5;color:#fff}.btn-primary.focus,.btn-primary:focus{box-shadow:0 0 0 0 rgba(93,93,208,.5)}.btn-primary.disabled,.btn-primary:disabled{background-color:#4040c8;border-color:#4040c8;color:#fff}.btn-primary:not(:disabled):not(.disabled).active,.btn-primary:not(:disabled):not(.disabled):active,.show>.btn-primary.dropdown-toggle{background-color:#3030a5;border-color:#2d2d9b;color:#fff}.btn-primary:not(:disabled):not(.disabled).active:focus,.btn-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(93,93,208,.5)}.btn-secondary{background-color:#4b5563;border-color:#4b5563;color:#fff}.btn-secondary.focus,.btn-secondary:focus,.btn-secondary:hover{background-color:#3b424d;border-color:#353c46;color:#fff}.btn-secondary.focus,.btn-secondary:focus{box-shadow:0 0 0 0 hsla(213,9%,44%,.5)}.btn-secondary.disabled,.btn-secondary:disabled{background-color:#4b5563;border-color:#4b5563;color:#fff}.btn-secondary:not(:disabled):not(.disabled).active,.btn-secondary:not(:disabled):not(.disabled):active,.show>.btn-secondary.dropdown-toggle{background-color:#353c46;border-color:#30363f;color:#fff}.btn-secondary:not(:disabled):not(.disabled).active:focus,.btn-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 0 hsla(213,9%,44%,.5)}.btn-success{background-color:#059669;border-color:#059669;color:#fff}.btn-success.focus,.btn-success:focus,.btn-success:hover{background-color:#04714f;border-color:#036546;color:#fff}.btn-success.focus,.btn-success:focus{box-shadow:0 0 0 0 rgba(43,166,128,.5)}.btn-success.disabled,.btn-success:disabled{background-color:#059669;border-color:#059669;color:#fff}.btn-success:not(:disabled):not(.disabled).active,.btn-success:not(:disabled):not(.disabled):active,.show>.btn-success.dropdown-toggle{background-color:#036546;border-color:#03583e;color:#fff}.btn-success:not(:disabled):not(.disabled).active:focus,.btn-success:not(:disabled):not(.disabled):active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(43,166,128,.5)}.btn-info{background-color:#2563eb;border-color:#2563eb;color:#fff}.btn-info.focus,.btn-info:focus,.btn-info:hover{background-color:#1451d6;border-color:#134cca;color:#fff}.btn-info.focus,.btn-info:focus{box-shadow:0 0 0 0 rgba(70,122,238,.5)}.btn-info.disabled,.btn-info:disabled{background-color:#2563eb;border-color:#2563eb;color:#fff}.btn-info:not(:disabled):not(.disabled).active,.btn-info:not(:disabled):not(.disabled):active,.show>.btn-info.dropdown-toggle{background-color:#134cca;border-color:#1248bf;color:#fff}.btn-info:not(:disabled):not(.disabled).active:focus,.btn-info:not(:disabled):not(.disabled):active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(70,122,238,.5)}.btn-warning{background-color:#d97706;border-color:#d97706;color:#fff}.btn-warning.focus,.btn-warning:focus,.btn-warning:hover{background-color:#b46305;border-color:#a75c05;color:#fff}.btn-warning.focus,.btn-warning:focus{box-shadow:0 0 0 0 rgba(223,139,43,.5)}.btn-warning.disabled,.btn-warning:disabled{background-color:#d97706;border-color:#d97706;color:#fff}.btn-warning:not(:disabled):not(.disabled).active,.btn-warning:not(:disabled):not(.disabled):active,.show>.btn-warning.dropdown-toggle{background-color:#a75c05;border-color:#9b5504;color:#fff}.btn-warning:not(:disabled):not(.disabled).active:focus,.btn-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(223,139,43,.5)}.btn-danger{background-color:#dc2626;border-color:#dc2626;color:#fff}.btn-danger.focus,.btn-danger:focus,.btn-danger:hover{background-color:#bd1f1f;border-color:#b21d1d;color:#fff}.btn-danger.focus,.btn-danger:focus{box-shadow:0 0 0 0 rgba(225,71,71,.5)}.btn-danger.disabled,.btn-danger:disabled{background-color:#dc2626;border-color:#dc2626;color:#fff}.btn-danger:not(:disabled):not(.disabled).active,.btn-danger:not(:disabled):not(.disabled):active,.show>.btn-danger.dropdown-toggle{background-color:#b21d1d;border-color:#a71b1b;color:#fff}.btn-danger:not(:disabled):not(.disabled).active:focus,.btn-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(225,71,71,.5)}.btn-light{background-color:#f3f4f6;border-color:#f3f4f6;color:#111827}.btn-light.focus,.btn-light:focus,.btn-light:hover{background-color:#dde0e6;border-color:#d6d9e0;color:#111827}.btn-light.focus,.btn-light:focus{box-shadow:0 0 0 0 hsla(220,7%,83%,.5)}.btn-light.disabled,.btn-light:disabled{background-color:#f3f4f6;border-color:#f3f4f6;color:#111827}.btn-light:not(:disabled):not(.disabled).active,.btn-light:not(:disabled):not(.disabled):active,.show>.btn-light.dropdown-toggle{background-color:#d6d9e0;border-color:#cfd3db;color:#111827}.btn-light:not(:disabled):not(.disabled).active:focus,.btn-light:not(:disabled):not(.disabled):active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 0 hsla(220,7%,83%,.5)}.btn-dark{background-color:#1f2937;border-color:#1f2937;color:#fff}.btn-dark.focus,.btn-dark:focus,.btn-dark:hover{background-color:#11171f;border-color:#0d1116;color:#fff}.btn-dark.focus,.btn-dark:focus{box-shadow:0 0 0 0 rgba(65,73,85,.5)}.btn-dark.disabled,.btn-dark:disabled{background-color:#1f2937;border-color:#1f2937;color:#fff}.btn-dark:not(:disabled):not(.disabled).active,.btn-dark:not(:disabled):not(.disabled):active,.show>.btn-dark.dropdown-toggle{background-color:#0d1116;border-color:#080b0e;color:#fff}.btn-dark:not(:disabled):not(.disabled).active:focus,.btn-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(65,73,85,.5)}.btn-outline-primary{border-color:#4040c8;color:#4040c8}.btn-outline-primary:hover{background-color:#4040c8;border-color:#4040c8;color:#fff}.btn-outline-primary.focus,.btn-outline-primary:focus{box-shadow:0 0 0 0 rgba(64,64,200,.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{background-color:transparent;color:#4040c8}.btn-outline-primary:not(:disabled):not(.disabled).active,.btn-outline-primary:not(:disabled):not(.disabled):active,.show>.btn-outline-primary.dropdown-toggle{background-color:#4040c8;border-color:#4040c8;color:#fff}.btn-outline-primary:not(:disabled):not(.disabled).active:focus,.btn-outline-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-primary.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(64,64,200,.5)}.btn-outline-secondary{border-color:#4b5563;color:#4b5563}.btn-outline-secondary:hover{background-color:#4b5563;border-color:#4b5563;color:#fff}.btn-outline-secondary.focus,.btn-outline-secondary:focus{box-shadow:0 0 0 0 rgba(75,85,99,.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{background-color:transparent;color:#4b5563}.btn-outline-secondary:not(:disabled):not(.disabled).active,.btn-outline-secondary:not(:disabled):not(.disabled):active,.show>.btn-outline-secondary.dropdown-toggle{background-color:#4b5563;border-color:#4b5563;color:#fff}.btn-outline-secondary:not(:disabled):not(.disabled).active:focus,.btn-outline-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-secondary.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(75,85,99,.5)}.btn-outline-success{border-color:#059669;color:#059669}.btn-outline-success:hover{background-color:#059669;border-color:#059669;color:#fff}.btn-outline-success.focus,.btn-outline-success:focus{box-shadow:0 0 0 0 rgba(5,150,105,.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{background-color:transparent;color:#059669}.btn-outline-success:not(:disabled):not(.disabled).active,.btn-outline-success:not(:disabled):not(.disabled):active,.show>.btn-outline-success.dropdown-toggle{background-color:#059669;border-color:#059669;color:#fff}.btn-outline-success:not(:disabled):not(.disabled).active:focus,.btn-outline-success:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-success.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(5,150,105,.5)}.btn-outline-info{border-color:#2563eb;color:#2563eb}.btn-outline-info:hover{background-color:#2563eb;border-color:#2563eb;color:#fff}.btn-outline-info.focus,.btn-outline-info:focus{box-shadow:0 0 0 0 rgba(37,99,235,.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{background-color:transparent;color:#2563eb}.btn-outline-info:not(:disabled):not(.disabled).active,.btn-outline-info:not(:disabled):not(.disabled):active,.show>.btn-outline-info.dropdown-toggle{background-color:#2563eb;border-color:#2563eb;color:#fff}.btn-outline-info:not(:disabled):not(.disabled).active:focus,.btn-outline-info:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-info.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(37,99,235,.5)}.btn-outline-warning{border-color:#d97706;color:#d97706}.btn-outline-warning:hover{background-color:#d97706;border-color:#d97706;color:#fff}.btn-outline-warning.focus,.btn-outline-warning:focus{box-shadow:0 0 0 0 rgba(217,119,6,.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{background-color:transparent;color:#d97706}.btn-outline-warning:not(:disabled):not(.disabled).active,.btn-outline-warning:not(:disabled):not(.disabled):active,.show>.btn-outline-warning.dropdown-toggle{background-color:#d97706;border-color:#d97706;color:#fff}.btn-outline-warning:not(:disabled):not(.disabled).active:focus,.btn-outline-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-warning.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(217,119,6,.5)}.btn-outline-danger{border-color:#dc2626;color:#dc2626}.btn-outline-danger:hover{background-color:#dc2626;border-color:#dc2626;color:#fff}.btn-outline-danger.focus,.btn-outline-danger:focus{box-shadow:0 0 0 0 rgba(220,38,38,.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{background-color:transparent;color:#dc2626}.btn-outline-danger:not(:disabled):not(.disabled).active,.btn-outline-danger:not(:disabled):not(.disabled):active,.show>.btn-outline-danger.dropdown-toggle{background-color:#dc2626;border-color:#dc2626;color:#fff}.btn-outline-danger:not(:disabled):not(.disabled).active:focus,.btn-outline-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-danger.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(220,38,38,.5)}.btn-outline-light{border-color:#f3f4f6;color:#f3f4f6}.btn-outline-light:hover{background-color:#f3f4f6;border-color:#f3f4f6;color:#111827}.btn-outline-light.focus,.btn-outline-light:focus{box-shadow:0 0 0 0 rgba(243,244,246,.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{background-color:transparent;color:#f3f4f6}.btn-outline-light:not(:disabled):not(.disabled).active,.btn-outline-light:not(:disabled):not(.disabled):active,.show>.btn-outline-light.dropdown-toggle{background-color:#f3f4f6;border-color:#f3f4f6;color:#111827}.btn-outline-light:not(:disabled):not(.disabled).active:focus,.btn-outline-light:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-light.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(243,244,246,.5)}.btn-outline-dark{border-color:#1f2937;color:#1f2937}.btn-outline-dark:hover{background-color:#1f2937;border-color:#1f2937;color:#fff}.btn-outline-dark.focus,.btn-outline-dark:focus{box-shadow:0 0 0 0 rgba(31,41,55,.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{background-color:transparent;color:#1f2937}.btn-outline-dark:not(:disabled):not(.disabled).active,.btn-outline-dark:not(:disabled):not(.disabled):active,.show>.btn-outline-dark.dropdown-toggle{background-color:#1f2937;border-color:#1f2937;color:#fff}.btn-outline-dark:not(:disabled):not(.disabled).active:focus,.btn-outline-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-dark.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(31,41,55,.5)}.btn-link{color:#6366f1;font-weight:400;text-decoration:none}.btn-link:hover{color:#4f46e5}.btn-link.focus,.btn-link:focus,.btn-link:hover{text-decoration:underline}.btn-link.disabled,.btn-link:disabled{color:#4b5563;pointer-events:none}.btn-group-lg>.btn,.btn-lg{border-radius:6px;font-size:1.25rem;line-height:1.5;padding:.5rem 1rem}.btn-group-sm>.btn,.btn-sm{border-radius:.2rem;font-size:.875rem;line-height:1.5;padding:.25rem .5rem}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:.5rem}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{height:0;overflow:hidden;position:relative;transition:height .35s ease}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.collapsing.width{height:auto;transition:width .35s ease;width:0}@media (prefers-reduced-motion:reduce){.collapsing.width{transition:none}}.dropdown,.dropleft,.dropright,.dropup{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle:after{border-bottom:0;border-left:.3em solid transparent;border-right:.3em solid transparent;border-top:.3em solid;content:"";display:inline-block;margin-left:.255em;vertical-align:.255em}.dropdown-toggle:empty:after{margin-left:0}.dropdown-menu{background-clip:padding-box;background-color:#fff;border:1px solid rgba(0,0,0,.15);border-radius:.25rem;color:#111827;display:none;float:left;font-size:1rem;left:0;list-style:none;margin:.125rem 0 0;min-width:10rem;padding:.5rem 0;position:absolute;text-align:left;top:100%;z-index:1000}.dropdown-menu-left{left:0;right:auto}.dropdown-menu-right{left:auto;right:0}@media (min-width:2px){.dropdown-menu-sm-left{left:0;right:auto}.dropdown-menu-sm-right{left:auto;right:0}}@media (min-width:8px){.dropdown-menu-md-left{left:0;right:auto}.dropdown-menu-md-right{left:auto;right:0}}@media (min-width:9px){.dropdown-menu-lg-left{left:0;right:auto}.dropdown-menu-lg-right{left:auto;right:0}}@media (min-width:10px){.dropdown-menu-xl-left{left:0;right:auto}.dropdown-menu-xl-right{left:auto;right:0}}.dropup .dropdown-menu{bottom:100%;margin-bottom:.125rem;margin-top:0;top:auto}.dropup .dropdown-toggle:after{border-bottom:.3em solid;border-left:.3em solid transparent;border-right:.3em solid transparent;border-top:0;content:"";display:inline-block;margin-left:.255em;vertical-align:.255em}.dropup .dropdown-toggle:empty:after{margin-left:0}.dropright .dropdown-menu{left:100%;margin-left:.125rem;margin-top:0;right:auto;top:0}.dropright .dropdown-toggle:after{border-bottom:.3em solid transparent;border-left:.3em solid;border-right:0;border-top:.3em solid transparent;content:"";display:inline-block;margin-left:.255em;vertical-align:.255em}.dropright .dropdown-toggle:empty:after{margin-left:0}.dropright .dropdown-toggle:after{vertical-align:0}.dropleft .dropdown-menu{left:auto;margin-right:.125rem;margin-top:0;right:100%;top:0}.dropleft .dropdown-toggle:after{content:"";display:inline-block;display:none;margin-left:.255em;vertical-align:.255em}.dropleft .dropdown-toggle:before{border-bottom:.3em solid transparent;border-right:.3em solid;border-top:.3em solid transparent;content:"";display:inline-block;margin-right:.255em;vertical-align:.255em}.dropleft .dropdown-toggle:empty:after{margin-left:0}.dropleft .dropdown-toggle:before{vertical-align:0}.dropdown-menu[x-placement^=bottom],.dropdown-menu[x-placement^=left],.dropdown-menu[x-placement^=right],.dropdown-menu[x-placement^=top]{bottom:auto;right:auto}.dropdown-divider{border-top:1px solid #e5e7eb;height:0;margin:.5rem 0;overflow:hidden}.dropdown-item{background-color:transparent;border:0;clear:both;color:#374151;display:block;font-weight:400;padding:.25rem 1.5rem;text-align:inherit;white-space:nowrap;width:100%}.dropdown-item:focus,.dropdown-item:hover{background-color:#e5e7eb;color:#090d15;text-decoration:none}.dropdown-item.active,.dropdown-item:active{background-color:#4040c8;color:#fff;text-decoration:none}.dropdown-item.disabled,.dropdown-item:disabled{background-color:transparent;color:#6b7280;pointer-events:none}.dropdown-menu.show{display:block}.dropdown-header{color:#4b5563;display:block;font-size:.875rem;margin-bottom:0;padding:.5rem 1.5rem;white-space:nowrap}.dropdown-item-text{color:#374151;display:block;padding:.25rem 1.5rem}.btn-group,.btn-group-vertical{display:inline-flex;position:relative;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{flex:1 1 auto;position:relative}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:1}.btn-toolbar{display:flex;flex-wrap:wrap;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn-group:not(:first-child),.btn-group>.btn:not(:first-child){margin-left:-1px}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-top-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.dropdown-toggle-split{padding-left:.5625rem;padding-right:.5625rem}.dropdown-toggle-split:after,.dropright .dropdown-toggle-split:after,.dropup .dropdown-toggle-split:after{margin-left:0}.dropleft .dropdown-toggle-split:before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-left:.375rem;padding-right:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-left:.75rem;padding-right:.75rem}.btn-group-vertical{align-items:flex-start;flex-direction:column;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-left-radius:0;border-bottom-right-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn:not(:first-child){border-top-left-radius:0;border-top-right-radius:0}.btn-group-toggle>.btn,.btn-group-toggle>.btn-group>.btn{margin-bottom:0}.btn-group-toggle>.btn input[type=checkbox],.btn-group-toggle>.btn input[type=radio],.btn-group-toggle>.btn-group>.btn input[type=checkbox],.btn-group-toggle>.btn-group>.btn input[type=radio]{clip:rect(0,0,0,0);pointer-events:none;position:absolute}.input-group{align-items:stretch;display:flex;flex-wrap:wrap;position:relative;width:100%}.input-group>.custom-file,.input-group>.custom-select,.input-group>.form-control,.input-group>.form-control-plaintext{flex:1 1 auto;margin-bottom:0;min-width:0;position:relative;width:1%}.input-group>.custom-file+.custom-file,.input-group>.custom-file+.custom-select,.input-group>.custom-file+.form-control,.input-group>.custom-select+.custom-file,.input-group>.custom-select+.custom-select,.input-group>.custom-select+.form-control,.input-group>.form-control+.custom-file,.input-group>.form-control+.custom-select,.input-group>.form-control+.form-control,.input-group>.form-control-plaintext+.custom-file,.input-group>.form-control-plaintext+.custom-select,.input-group>.form-control-plaintext+.form-control{margin-left:-1px}.input-group>.custom-file .custom-file-input:focus~.custom-file-label,.input-group>.custom-select:focus,.input-group>.form-control:focus{z-index:3}.input-group>.custom-file .custom-file-input:focus{z-index:4}.input-group>.custom-select:not(:first-child),.input-group>.form-control:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.input-group>.custom-file{align-items:center;display:flex}.input-group>.custom-file:not(:last-child) .custom-file-label,.input-group>.custom-file:not(:last-child) .custom-file-label:after{border-bottom-right-radius:0;border-top-right-radius:0}.input-group>.custom-file:not(:first-child) .custom-file-label{border-bottom-left-radius:0;border-top-left-radius:0}.input-group.has-validation>.custom-file:nth-last-child(n+3) .custom-file-label,.input-group.has-validation>.custom-file:nth-last-child(n+3) .custom-file-label:after,.input-group.has-validation>.custom-select:nth-last-child(n+3),.input-group.has-validation>.form-control:nth-last-child(n+3),.input-group:not(.has-validation)>.custom-file:not(:last-child) .custom-file-label,.input-group:not(.has-validation)>.custom-file:not(:last-child) .custom-file-label:after,.input-group:not(.has-validation)>.custom-select:not(:last-child),.input-group:not(.has-validation)>.form-control:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.input-group-append,.input-group-prepend{display:flex}.input-group-append .btn,.input-group-prepend .btn{position:relative;z-index:2}.input-group-append .btn:focus,.input-group-prepend .btn:focus{z-index:3}.input-group-append .btn+.btn,.input-group-append .btn+.input-group-text,.input-group-append .input-group-text+.btn,.input-group-append .input-group-text+.input-group-text,.input-group-prepend .btn+.btn,.input-group-prepend .btn+.input-group-text,.input-group-prepend .input-group-text+.btn,.input-group-prepend .input-group-text+.input-group-text{margin-left:-1px}.input-group-prepend{margin-right:-1px}.input-group-append{margin-left:-1px}.input-group-text{align-items:center;background-color:#e5e7eb;border:1px solid #d1d5db;border-radius:.25rem;color:#1f2937;display:flex;font-size:1rem;font-weight:400;line-height:1.5;margin-bottom:0;padding:.375rem .75rem;text-align:center;white-space:nowrap}.input-group-text input[type=checkbox],.input-group-text input[type=radio]{margin-top:0}.input-group-lg>.custom-select,.input-group-lg>.form-control:not(textarea){height:calc(1.5em + 1rem + 2px)}.input-group-lg>.custom-select,.input-group-lg>.form-control,.input-group-lg>.input-group-append>.btn,.input-group-lg>.input-group-append>.input-group-text,.input-group-lg>.input-group-prepend>.btn,.input-group-lg>.input-group-prepend>.input-group-text{border-radius:6px;font-size:1.25rem;line-height:1.5;padding:.5rem 1rem}.input-group-sm>.custom-select,.input-group-sm>.form-control:not(textarea){height:calc(1.5em + .5rem + 2px)}.input-group-sm>.custom-select,.input-group-sm>.form-control,.input-group-sm>.input-group-append>.btn,.input-group-sm>.input-group-append>.input-group-text,.input-group-sm>.input-group-prepend>.btn,.input-group-sm>.input-group-prepend>.input-group-text{border-radius:.2rem;font-size:.875rem;line-height:1.5;padding:.25rem .5rem}.input-group-lg>.custom-select,.input-group-sm>.custom-select{padding-right:1.75rem}.input-group.has-validation>.input-group-append:nth-last-child(n+3)>.btn,.input-group.has-validation>.input-group-append:nth-last-child(n+3)>.input-group-text,.input-group:not(.has-validation)>.input-group-append:not(:last-child)>.btn,.input-group:not(.has-validation)>.input-group-append:not(:last-child)>.input-group-text,.input-group>.input-group-append:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group>.input-group-append:last-child>.input-group-text:not(:last-child),.input-group>.input-group-prepend>.btn,.input-group>.input-group-prepend>.input-group-text{border-bottom-right-radius:0;border-top-right-radius:0}.input-group>.input-group-append>.btn,.input-group>.input-group-append>.input-group-text,.input-group>.input-group-prepend:first-child>.btn:not(:first-child),.input-group>.input-group-prepend:first-child>.input-group-text:not(:first-child),.input-group>.input-group-prepend:not(:first-child)>.btn,.input-group>.input-group-prepend:not(:first-child)>.input-group-text{border-bottom-left-radius:0;border-top-left-radius:0}.custom-control{display:block;min-height:1.5rem;padding-left:1.5rem;position:relative;-webkit-print-color-adjust:exact;print-color-adjust:exact;z-index:1}.custom-control-inline{display:inline-flex;margin-right:1rem}.custom-control-input{height:1.25rem;left:0;opacity:0;position:absolute;width:1rem;z-index:-1}.custom-control-input:checked~.custom-control-label:before{background-color:#4040c8;border-color:#4040c8;color:#fff}.custom-control-input:focus~.custom-control-label:before{box-shadow:0 0 0 .2rem rgba(64,64,200,.25)}.custom-control-input:focus:not(:checked)~.custom-control-label:before{border-color:#a3a3e5}.custom-control-input:not(:disabled):active~.custom-control-label:before{background-color:#cbcbf0;border-color:#cbcbf0;color:#fff}.custom-control-input:disabled~.custom-control-label,.custom-control-input[disabled]~.custom-control-label{color:#4b5563}.custom-control-input:disabled~.custom-control-label:before,.custom-control-input[disabled]~.custom-control-label:before{background-color:#e5e7eb}.custom-control-label{margin-bottom:0;position:relative;vertical-align:top}.custom-control-label:before{background-color:#fff;border:1px solid #6b7280;pointer-events:none}.custom-control-label:after,.custom-control-label:before{content:"";display:block;height:1rem;left:-1.5rem;position:absolute;top:.25rem;width:1rem}.custom-control-label:after{background:50%/50% 50% no-repeat}.custom-checkbox .custom-control-label:before{border-radius:.25rem}.custom-checkbox .custom-control-input:checked~.custom-control-label:after{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8'%3E%3Cpath fill='%23fff' d='m6.564.75-3.59 3.612-1.538-1.55L0 4.26l2.974 2.99L8 2.193z'/%3E%3C/svg%3E")}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label:before{background-color:#4040c8;border-color:#4040c8}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label:after{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='4' height='4'%3E%3Cpath stroke='%23fff' d='M0 2h4'/%3E%3C/svg%3E")}.custom-checkbox .custom-control-input:disabled:checked~.custom-control-label:before{background-color:rgba(64,64,200,.5)}.custom-checkbox .custom-control-input:disabled:indeterminate~.custom-control-label:before{background-color:rgba(64,64,200,.5)}.custom-radio .custom-control-label:before{border-radius:50%}.custom-radio .custom-control-input:checked~.custom-control-label:after{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='-4 -4 8 8'%3E%3Ccircle r='3' fill='%23fff'/%3E%3C/svg%3E")}.custom-radio .custom-control-input:disabled:checked~.custom-control-label:before{background-color:rgba(64,64,200,.5)}.custom-switch{padding-left:2.25rem}.custom-switch .custom-control-label:before{border-radius:.5rem;left:-2.25rem;pointer-events:all;width:1.75rem}.custom-switch .custom-control-label:after{background-color:#6b7280;border-radius:.5rem;height:calc(1rem - 4px);left:calc(-2.25rem + 2px);top:calc(.25rem + 2px);transition:transform .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;width:calc(1rem - 4px)}@media (prefers-reduced-motion:reduce){.custom-switch .custom-control-label:after{transition:none}}.custom-switch .custom-control-input:checked~.custom-control-label:after{background-color:#fff;transform:translateX(.75rem)}.custom-switch .custom-control-input:disabled:checked~.custom-control-label:before{background-color:rgba(64,64,200,.5)}.custom-select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5'%3E%3Cpath fill='%231f2937' d='M2 0 0 2h4zm0 5L0 3h4z'/%3E%3C/svg%3E") right .75rem center/8px 10px no-repeat;border:1px solid #d1d5db;border-radius:.25rem;color:#1f2937;display:inline-block;font-size:1rem;font-weight:400;height:calc(1.5em + .75rem + 2px);line-height:1.5;padding:.375rem 1.75rem .375rem .75rem;vertical-align:middle;width:100%}.custom-select:focus{border-color:#a3a3e5;box-shadow:0 0 0 .2rem rgba(64,64,200,.25);outline:0}.custom-select:focus::-ms-value{background-color:#fff;color:#1f2937}.custom-select[multiple],.custom-select[size]:not([size="1"]){background-image:none;height:auto;padding-right:.75rem}.custom-select:disabled{background-color:#e5e7eb;color:#4b5563}.custom-select::-ms-expand{display:none}.custom-select:-moz-focusring{color:transparent;text-shadow:0 0 0 #1f2937}.custom-select-sm{font-size:.875rem;height:calc(1.5em + .5rem + 2px);padding-bottom:.25rem;padding-left:.5rem;padding-top:.25rem}.custom-select-lg{font-size:1.25rem;height:calc(1.5em + 1rem + 2px);padding-bottom:.5rem;padding-left:1rem;padding-top:.5rem}.custom-file{display:inline-block;margin-bottom:0}.custom-file,.custom-file-input{height:calc(1.5em + .75rem + 2px);position:relative;width:100%}.custom-file-input{margin:0;opacity:0;overflow:hidden;z-index:2}.custom-file-input:focus~.custom-file-label{border-color:#a3a3e5;box-shadow:0 0 0 .2rem rgba(64,64,200,.25)}.custom-file-input:disabled~.custom-file-label,.custom-file-input[disabled]~.custom-file-label{background-color:#e5e7eb}.custom-file-input:lang(en)~.custom-file-label:after{content:"Browse"}.custom-file-input~.custom-file-label[data-browse]:after{content:attr(data-browse)}.custom-file-label{background-color:#fff;border:1px solid #d1d5db;border-radius:.25rem;font-weight:400;height:calc(1.5em + .75rem + 2px);left:0;overflow:hidden;z-index:1}.custom-file-label,.custom-file-label:after{color:#1f2937;line-height:1.5;padding:.375rem .75rem;position:absolute;right:0;top:0}.custom-file-label:after{background-color:#e5e7eb;border-left:inherit;border-radius:0 .25rem .25rem 0;bottom:0;content:"Browse";display:block;height:calc(1.5em + .75rem);z-index:3}.custom-range{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:transparent;height:1.4rem;padding:0;width:100%}.custom-range:focus{outline:0}.custom-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #f3f4f6,0 0 0 .2rem rgba(64,64,200,.25)}.custom-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #f3f4f6,0 0 0 .2rem rgba(64,64,200,.25)}.custom-range:focus::-ms-thumb{box-shadow:0 0 0 1px #f3f4f6,0 0 0 .2rem rgba(64,64,200,.25)}.custom-range::-moz-focus-outer{border:0}.custom-range::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;background-color:#4040c8;border:0;border-radius:1rem;height:1rem;margin-top:-.25rem;-webkit-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;width:1rem}@media (prefers-reduced-motion:reduce){.custom-range::-webkit-slider-thumb{-webkit-transition:none;transition:none}}.custom-range::-webkit-slider-thumb:active{background-color:#cbcbf0}.custom-range::-webkit-slider-runnable-track{background-color:#d1d5db;border-color:transparent;border-radius:1rem;color:transparent;cursor:pointer;height:.5rem;width:100%}.custom-range::-moz-range-thumb{-moz-appearance:none;appearance:none;background-color:#4040c8;border:0;border-radius:1rem;height:1rem;-moz-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;width:1rem}@media (prefers-reduced-motion:reduce){.custom-range::-moz-range-thumb{-moz-transition:none;transition:none}}.custom-range::-moz-range-thumb:active{background-color:#cbcbf0}.custom-range::-moz-range-track{background-color:#d1d5db;border-color:transparent;border-radius:1rem;color:transparent;cursor:pointer;height:.5rem;width:100%}.custom-range::-ms-thumb{appearance:none;background-color:#4040c8;border:0;border-radius:1rem;height:1rem;margin-left:.2rem;margin-right:.2rem;margin-top:0;-ms-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;width:1rem}@media (prefers-reduced-motion:reduce){.custom-range::-ms-thumb{-ms-transition:none;transition:none}}.custom-range::-ms-thumb:active{background-color:#cbcbf0}.custom-range::-ms-track{background-color:transparent;border-color:transparent;border-width:.5rem;color:transparent;cursor:pointer;height:.5rem;width:100%}.custom-range::-ms-fill-lower,.custom-range::-ms-fill-upper{background-color:#d1d5db;border-radius:1rem}.custom-range::-ms-fill-upper{margin-right:15px}.custom-range:disabled::-webkit-slider-thumb{background-color:#6b7280}.custom-range:disabled::-webkit-slider-runnable-track{cursor:default}.custom-range:disabled::-moz-range-thumb{background-color:#6b7280}.custom-range:disabled::-moz-range-track{cursor:default}.custom-range:disabled::-ms-thumb{background-color:#6b7280}.custom-control-label:before,.custom-file-label,.custom-select{transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.custom-control-label:before,.custom-file-label,.custom-select{transition:none}}.nav{display:flex;flex-wrap:wrap;list-style:none;margin-bottom:0;padding-left:0}.nav-link{display:block;padding:.5rem 1rem}.nav-link:focus,.nav-link:hover{text-decoration:none}.nav-link.disabled{color:#4b5563;cursor:default;pointer-events:none}.nav-tabs{border-bottom:1px solid #d1d5db}.nav-tabs .nav-link{background-color:transparent;border:1px solid transparent;border-top-left-radius:.25rem;border-top-right-radius:.25rem;margin-bottom:-1px}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{border-color:#e5e7eb #e5e7eb #d1d5db;isolation:isolate}.nav-tabs .nav-link.disabled{background-color:transparent;border-color:transparent;color:#4b5563}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{background-color:#f3f4f6;border-color:#d1d5db #d1d5db #f3f4f6;color:#374151}.nav-tabs .dropdown-menu{border-top-left-radius:0;border-top-right-radius:0;margin-top:-1px}.nav-pills .nav-link{background:none;border:0;border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{background-color:#e5e7eb;color:#fff}.nav-fill .nav-item,.nav-fill>.nav-link{flex:1 1 auto;text-align:center}.nav-justified .nav-item,.nav-justified>.nav-link{flex-basis:0;flex-grow:1;text-align:center}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{padding:.5rem 1rem;position:relative}.navbar,.navbar .container,.navbar .container-fluid,.navbar .container-lg,.navbar .container-md,.navbar .container-sm,.navbar .container-xl{align-items:center;display:flex;flex-wrap:wrap;justify-content:space-between}.navbar-brand{display:inline-block;font-size:1.25rem;line-height:inherit;margin-right:1rem;padding-bottom:.3125rem;padding-top:.3125rem;white-space:nowrap}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-nav{display:flex;flex-direction:column;list-style:none;margin-bottom:0;padding-left:0}.navbar-nav .nav-link{padding-left:0;padding-right:0}.navbar-nav .dropdown-menu{float:none;position:static}.navbar-text{display:inline-block;padding-bottom:.5rem;padding-top:.5rem}.navbar-collapse{align-items:center;flex-basis:100%;flex-grow:1}.navbar-toggler{background-color:transparent;border:1px solid transparent;border-radius:.25rem;font-size:1.25rem;line-height:1;padding:.25rem .75rem}.navbar-toggler:focus,.navbar-toggler:hover{text-decoration:none}.navbar-toggler-icon{background:50%/100% 100% no-repeat;content:"";display:inline-block;height:1.5em;vertical-align:middle;width:1.5em}.navbar-nav-scroll{max-height:75vh;overflow-y:auto}@media (max-width:1.98px){.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid,.navbar-expand-sm>.container-lg,.navbar-expand-sm>.container-md,.navbar-expand-sm>.container-sm,.navbar-expand-sm>.container-xl{padding-left:0;padding-right:0}}@media (min-width:2px){.navbar-expand-sm{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand-sm .navbar-nav{flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-left:.5rem;padding-right:.5rem}.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid,.navbar-expand-sm>.container-lg,.navbar-expand-sm>.container-md,.navbar-expand-sm>.container-sm,.navbar-expand-sm>.container-xl{flex-wrap:nowrap}.navbar-expand-sm .navbar-nav-scroll{overflow:visible}.navbar-expand-sm .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}}@media (max-width:7.98px){.navbar-expand-md>.container,.navbar-expand-md>.container-fluid,.navbar-expand-md>.container-lg,.navbar-expand-md>.container-md,.navbar-expand-md>.container-sm,.navbar-expand-md>.container-xl{padding-left:0;padding-right:0}}@media (min-width:8px){.navbar-expand-md{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand-md .navbar-nav{flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-left:.5rem;padding-right:.5rem}.navbar-expand-md>.container,.navbar-expand-md>.container-fluid,.navbar-expand-md>.container-lg,.navbar-expand-md>.container-md,.navbar-expand-md>.container-sm,.navbar-expand-md>.container-xl{flex-wrap:nowrap}.navbar-expand-md .navbar-nav-scroll{overflow:visible}.navbar-expand-md .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}}@media (max-width:8.98px){.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid,.navbar-expand-lg>.container-lg,.navbar-expand-lg>.container-md,.navbar-expand-lg>.container-sm,.navbar-expand-lg>.container-xl{padding-left:0;padding-right:0}}@media (min-width:9px){.navbar-expand-lg{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand-lg .navbar-nav{flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-left:.5rem;padding-right:.5rem}.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid,.navbar-expand-lg>.container-lg,.navbar-expand-lg>.container-md,.navbar-expand-lg>.container-sm,.navbar-expand-lg>.container-xl{flex-wrap:nowrap}.navbar-expand-lg .navbar-nav-scroll{overflow:visible}.navbar-expand-lg .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}}@media (max-width:9.98px){.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid,.navbar-expand-xl>.container-lg,.navbar-expand-xl>.container-md,.navbar-expand-xl>.container-sm,.navbar-expand-xl>.container-xl{padding-left:0;padding-right:0}}@media (min-width:10px){.navbar-expand-xl{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand-xl .navbar-nav{flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-left:.5rem;padding-right:.5rem}.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid,.navbar-expand-xl>.container-lg,.navbar-expand-xl>.container-md,.navbar-expand-xl>.container-sm,.navbar-expand-xl>.container-xl{flex-wrap:nowrap}.navbar-expand-xl .navbar-nav-scroll{overflow:visible}.navbar-expand-xl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}}.navbar-expand{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand>.container,.navbar-expand>.container-fluid,.navbar-expand>.container-lg,.navbar-expand>.container-md,.navbar-expand>.container-sm,.navbar-expand>.container-xl{padding-left:0;padding-right:0}.navbar-expand .navbar-nav{flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-left:.5rem;padding-right:.5rem}.navbar-expand>.container,.navbar-expand>.container-fluid,.navbar-expand>.container-lg,.navbar-expand>.container-md,.navbar-expand>.container-sm,.navbar-expand>.container-xl{flex-wrap:nowrap}.navbar-expand .navbar-nav-scroll{overflow:visible}.navbar-expand .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-light .navbar-brand,.navbar-light .navbar-brand:focus,.navbar-light .navbar-brand:hover{color:rgba(0,0,0,.9)}.navbar-light .navbar-nav .nav-link{color:rgba(0,0,0,.5)}.navbar-light .navbar-nav .nav-link:focus,.navbar-light .navbar-nav .nav-link:hover{color:rgba(0,0,0,.7)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(0,0,0,.3)}.navbar-light .navbar-nav .active>.nav-link,.navbar-light .navbar-nav .nav-link.active,.navbar-light .navbar-nav .nav-link.show,.navbar-light .navbar-nav .show>.nav-link{color:rgba(0,0,0,.9)}.navbar-light .navbar-toggler{border-color:rgba(0,0,0,.1);color:rgba(0,0,0,.5)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30'%3E%3Cpath stroke='rgba(0, 0, 0, 0.5)' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E")}.navbar-light .navbar-text{color:rgba(0,0,0,.5)}.navbar-light .navbar-text a,.navbar-light .navbar-text a:focus,.navbar-light .navbar-text a:hover{color:rgba(0,0,0,.9)}.navbar-dark .navbar-brand,.navbar-dark .navbar-brand:focus,.navbar-dark .navbar-brand:hover{color:#fff}.navbar-dark .navbar-nav .nav-link{color:hsla(0,0%,100%,.5)}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:hsla(0,0%,100%,.75)}.navbar-dark .navbar-nav .nav-link.disabled{color:hsla(0,0%,100%,.25)}.navbar-dark .navbar-nav .active>.nav-link,.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .nav-link.show,.navbar-dark .navbar-nav .show>.nav-link{color:#fff}.navbar-dark .navbar-toggler{border-color:hsla(0,0%,100%,.1);color:hsla(0,0%,100%,.5)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30'%3E%3Cpath stroke='rgba(255, 255, 255, 0.5)' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E")}.navbar-dark .navbar-text{color:hsla(0,0%,100%,.5)}.navbar-dark .navbar-text a,.navbar-dark .navbar-text a:focus,.navbar-dark .navbar-text a:hover{color:#fff}.card{word-wrap:break-word;background-clip:border-box;background-color:#fff;border:1px solid rgba(0,0,0,.125);border-radius:6px;display:flex;flex-direction:column;min-width:0;position:relative}.card>hr{margin-left:0;margin-right:0}.card>.list-group{border-bottom:inherit;border-top:inherit}.card>.list-group:first-child{border-top-left-radius:5px;border-top-right-radius:5px;border-top-width:0}.card>.list-group:last-child{border-bottom-left-radius:5px;border-bottom-right-radius:5px;border-bottom-width:0}.card>.card-header+.list-group,.card>.list-group+.card-footer{border-top:0}.card-body{flex:1 1 auto;min-height:1px;padding:1.25rem}.card-title{margin-bottom:.75rem}.card-subtitle{margin-top:-.375rem}.card-subtitle,.card-text:last-child{margin-bottom:0}.card-link:hover{text-decoration:none}.card-link+.card-link{margin-left:1.25rem}.card-header{background-color:#fff;border-bottom:1px solid rgba(0,0,0,.125);margin-bottom:0;padding:.75rem 1.25rem}.card-header:first-child{border-radius:5px 5px 0 0}.card-footer{background-color:#fff;border-top:1px solid rgba(0,0,0,.125);padding:.75rem 1.25rem}.card-footer:last-child{border-radius:0 0 5px 5px}.card-header-tabs{border-bottom:0;margin-bottom:-.75rem}.card-header-pills,.card-header-tabs{margin-left:-.625rem;margin-right:-.625rem}.card-img-overlay{border-radius:5px;bottom:0;left:0;padding:1.25rem;position:absolute;right:0;top:0}.card-img,.card-img-bottom,.card-img-top{flex-shrink:0;width:100%}.card-img,.card-img-top{border-top-left-radius:5px;border-top-right-radius:5px}.card-img,.card-img-bottom{border-bottom-left-radius:5px;border-bottom-right-radius:5px}.card-deck .card{margin-bottom:15px}@media (min-width:2px){.card-deck{display:flex;flex-flow:row wrap;margin-left:-15px;margin-right:-15px}.card-deck .card{flex:1 0 0%;margin-bottom:0;margin-left:15px;margin-right:15px}}.card-group>.card{margin-bottom:15px}@media (min-width:2px){.card-group{display:flex;flex-flow:row wrap}.card-group>.card{flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{border-left:0;margin-left:0}.card-group>.card:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.card-group>.card:not(:last-child) .card-header,.card-group>.card:not(:last-child) .card-img-top{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-footer,.card-group>.card:not(:last-child) .card-img-bottom{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.card-group>.card:not(:first-child) .card-header,.card-group>.card:not(:first-child) .card-img-top{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-footer,.card-group>.card:not(:first-child) .card-img-bottom{border-bottom-left-radius:0}}.card-columns .card{margin-bottom:.75rem}@media (min-width:2px){.card-columns{-moz-column-count:3;column-count:3;-moz-column-gap:1.25rem;column-gap:1.25rem;orphans:1;widows:1}.card-columns .card{display:inline-block;width:100%}}.accordion{overflow-anchor:none}.accordion>.card{overflow:hidden}.accordion>.card:not(:last-of-type){border-bottom:0;border-bottom-left-radius:0;border-bottom-right-radius:0}.accordion>.card:not(:first-of-type){border-top-left-radius:0;border-top-right-radius:0}.accordion>.card>.card-header{border-radius:0;margin-bottom:-1px}.breadcrumb{background-color:#e5e7eb;border-radius:.25rem;display:flex;flex-wrap:wrap;list-style:none;margin-bottom:1rem;padding:.75rem 1rem}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item:before{color:#4b5563;content:"/";float:left;padding-right:.5rem}.breadcrumb-item+.breadcrumb-item:hover:before{text-decoration:underline;text-decoration:none}.breadcrumb-item.active{color:#4b5563}.pagination{border-radius:.25rem;display:flex;list-style:none;padding-left:0}.page-link{background-color:#fff;border:1px solid #d1d5db;color:#6366f1;display:block;line-height:1.25;margin-left:-1px;padding:.5rem .75rem;position:relative}.page-link:hover{background-color:#e5e7eb;border-color:#d1d5db;color:#4f46e5;text-decoration:none;z-index:2}.page-link:focus{box-shadow:0 0 0 .2rem rgba(64,64,200,.25);outline:0;z-index:3}.page-item:first-child .page-link{border-bottom-left-radius:.25rem;border-top-left-radius:.25rem;margin-left:0}.page-item:last-child .page-link{border-bottom-right-radius:.25rem;border-top-right-radius:.25rem}.page-item.active .page-link{background-color:#4040c8;border-color:#4040c8;color:#fff;z-index:3}.page-item.disabled .page-link{background-color:#fff;border-color:#d1d5db;color:#4b5563;cursor:auto;pointer-events:none}.pagination-lg .page-link{font-size:1.25rem;line-height:1.5;padding:.75rem 1.5rem}.pagination-lg .page-item:first-child .page-link{border-bottom-left-radius:6px;border-top-left-radius:6px}.pagination-lg .page-item:last-child .page-link{border-bottom-right-radius:6px;border-top-right-radius:6px}.pagination-sm .page-link{font-size:.875rem;line-height:1.5;padding:.25rem .5rem}.pagination-sm .page-item:first-child .page-link{border-bottom-left-radius:.2rem;border-top-left-radius:.2rem}.pagination-sm .page-item:last-child .page-link{border-bottom-right-radius:.2rem;border-top-right-radius:.2rem}.badge{border-radius:.25rem;display:inline-block;font-size:.875rem;font-weight:600;line-height:1;padding:.25em .4em;text-align:center;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;vertical-align:baseline;white-space:nowrap}@media (prefers-reduced-motion:reduce){.badge{transition:none}}a.badge:focus,a.badge:hover{text-decoration:none}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.badge-pill{border-radius:10rem;padding-left:.6em;padding-right:.6em}.badge-primary{background-color:#4040c8;color:#fff}a.badge-primary:focus,a.badge-primary:hover{background-color:#3030a5;color:#fff}a.badge-primary.focus,a.badge-primary:focus{box-shadow:0 0 0 .2rem rgba(64,64,200,.5);outline:0}.badge-secondary{background-color:#4b5563;color:#fff}a.badge-secondary:focus,a.badge-secondary:hover{background-color:#353c46;color:#fff}a.badge-secondary.focus,a.badge-secondary:focus{box-shadow:0 0 0 .2rem rgba(75,85,99,.5);outline:0}.badge-success{background-color:#059669;color:#fff}a.badge-success:focus,a.badge-success:hover{background-color:#036546;color:#fff}a.badge-success.focus,a.badge-success:focus{box-shadow:0 0 0 .2rem rgba(5,150,105,.5);outline:0}.badge-info{background-color:#2563eb;color:#fff}a.badge-info:focus,a.badge-info:hover{background-color:#134cca;color:#fff}a.badge-info.focus,a.badge-info:focus{box-shadow:0 0 0 .2rem rgba(37,99,235,.5);outline:0}.badge-warning{background-color:#d97706;color:#fff}a.badge-warning:focus,a.badge-warning:hover{background-color:#a75c05;color:#fff}a.badge-warning.focus,a.badge-warning:focus{box-shadow:0 0 0 .2rem rgba(217,119,6,.5);outline:0}.badge-danger{background-color:#dc2626;color:#fff}a.badge-danger:focus,a.badge-danger:hover{background-color:#b21d1d;color:#fff}a.badge-danger.focus,a.badge-danger:focus{box-shadow:0 0 0 .2rem rgba(220,38,38,.5);outline:0}.badge-light{background-color:#f3f4f6;color:#111827}a.badge-light:focus,a.badge-light:hover{background-color:#d6d9e0;color:#111827}a.badge-light.focus,a.badge-light:focus{box-shadow:0 0 0 .2rem rgba(243,244,246,.5);outline:0}.badge-dark{background-color:#1f2937;color:#fff}a.badge-dark:focus,a.badge-dark:hover{background-color:#0d1116;color:#fff}a.badge-dark.focus,a.badge-dark:focus{box-shadow:0 0 0 .2rem rgba(31,41,55,.5);outline:0}.jumbotron{background-color:#e5e7eb;border-radius:6px;margin-bottom:2rem;padding:2rem 1rem}@media (min-width:2px){.jumbotron{padding:4rem 2rem}}.jumbotron-fluid{border-radius:0;padding-left:0;padding-right:0}.alert{border:1px solid transparent;border-radius:.25rem;margin-bottom:1rem;padding:.75rem 1.25rem;position:relative}.alert-heading{color:inherit}.alert-link{font-weight:600}.alert-dismissible{padding-right:4rem}.alert-dismissible .close{color:inherit;padding:.75rem 1.25rem;position:absolute;right:0;top:0;z-index:2}.alert-primary{background-color:#d9d9f4;border-color:#cacaf0;color:#212168}.alert-primary hr{border-top-color:#b6b6ea}.alert-primary .alert-link{color:#151541}.alert-secondary{background-color:#dbdde0;border-color:#cdcfd3;color:#272c33}.alert-secondary hr{border-top-color:#bfc2c7}.alert-secondary .alert-link{color:#111316}.alert-success{background-color:#cdeae1;border-color:#b9e2d5;color:#034e37}.alert-success hr{border-top-color:#a7dbca}.alert-success .alert-link{color:#011d14}.alert-info{background-color:#d3e0fb;border-color:#c2d3f9;color:#13337a}.alert-info hr{border-top-color:#abc2f7}.alert-info .alert-link{color:#0c214e}.alert-warning{background-color:#f7e4cd;border-color:#f4d9b9;color:#713e03}.alert-warning hr{border-top-color:#f1cda3}.alert-warning .alert-link{color:#3f2302}.alert-danger{background-color:#f8d4d4;border-color:#f5c2c2;color:#721414}.alert-danger hr{border-top-color:#f1acac}.alert-danger .alert-link{color:#470c0c}.alert-light{background-color:#fdfdfd;border-color:#fcfcfc;color:#7e7f80}.alert-light hr{border-top-color:#efefef}.alert-light .alert-link{color:#656666}.alert-dark{background-color:#d2d4d7;border-color:#c0c3c7;color:#10151d}.alert-dark hr{border-top-color:#b3b6bb}.alert-dark .alert-link{color:#000}@keyframes progress-bar-stripes{0%{background-position:1rem 0}to{background-position:0 0}}.progress{background-color:#e5e7eb;border-radius:.25rem;font-size:.75rem;height:1rem;line-height:0}.progress,.progress-bar{display:flex;overflow:hidden}.progress-bar{background-color:#4040c8;color:#fff;flex-direction:column;justify-content:center;text-align:center;transition:width .6s ease;white-space:nowrap}@media (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,hsla(0,0%,100%,.15) 25%,transparent 0,transparent 50%,hsla(0,0%,100%,.15) 0,hsla(0,0%,100%,.15) 75%,transparent 0,transparent);background-size:1rem 1rem}.progress-bar-animated{animation:progress-bar-stripes 1s linear infinite}@media (prefers-reduced-motion:reduce){.progress-bar-animated{animation:none}}.media{align-items:flex-start;display:flex}.media-body{flex:1}.list-group{border-radius:.25rem;display:flex;flex-direction:column;margin-bottom:0;padding-left:0}.list-group-item-action{color:#374151;text-align:inherit;width:100%}.list-group-item-action:focus,.list-group-item-action:hover{background-color:#f3f4f6;color:#374151;text-decoration:none;z-index:1}.list-group-item-action:active{background-color:#e5e7eb;color:#111827}.list-group-item{background-color:#fff;border:1px solid rgba(0,0,0,.125);display:block;padding:.75rem 1.25rem;position:relative}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-left-radius:inherit;border-bottom-right-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{background-color:#fff;color:#4b5563;pointer-events:none}.list-group-item.active{background-color:#4040c8;border-color:#4040c8;color:#fff;z-index:2}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{border-top-width:1px;margin-top:-1px}.list-group-horizontal{flex-direction:row}.list-group-horizontal>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child{border-bottom-left-radius:0;border-top-right-radius:.25rem}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-left-width:0;border-top-width:1px}.list-group-horizontal>.list-group-item+.list-group-item.active{border-left-width:1px;margin-left:-1px}@media (min-width:2px){.list-group-horizontal-sm{flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child{border-bottom-left-radius:0;border-top-right-radius:.25rem}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-left-width:0;border-top-width:1px}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{border-left-width:1px;margin-left:-1px}}@media (min-width:8px){.list-group-horizontal-md{flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child{border-bottom-left-radius:0;border-top-right-radius:.25rem}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-left-width:0;border-top-width:1px}.list-group-horizontal-md>.list-group-item+.list-group-item.active{border-left-width:1px;margin-left:-1px}}@media (min-width:9px){.list-group-horizontal-lg{flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child{border-bottom-left-radius:0;border-top-right-radius:.25rem}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-left-width:0;border-top-width:1px}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{border-left-width:1px;margin-left:-1px}}@media (min-width:10px){.list-group-horizontal-xl{flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child{border-bottom-left-radius:0;border-top-right-radius:.25rem}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-left-width:0;border-top-width:1px}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{border-left-width:1px;margin-left:-1px}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 1px}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-primary{background-color:#cacaf0;color:#212168}.list-group-item-primary.list-group-item-action:focus,.list-group-item-primary.list-group-item-action:hover{background-color:#b6b6ea;color:#212168}.list-group-item-primary.list-group-item-action.active{background-color:#212168;border-color:#212168;color:#fff}.list-group-item-secondary{background-color:#cdcfd3;color:#272c33}.list-group-item-secondary.list-group-item-action:focus,.list-group-item-secondary.list-group-item-action:hover{background-color:#bfc2c7;color:#272c33}.list-group-item-secondary.list-group-item-action.active{background-color:#272c33;border-color:#272c33;color:#fff}.list-group-item-success{background-color:#b9e2d5;color:#034e37}.list-group-item-success.list-group-item-action:focus,.list-group-item-success.list-group-item-action:hover{background-color:#a7dbca;color:#034e37}.list-group-item-success.list-group-item-action.active{background-color:#034e37;border-color:#034e37;color:#fff}.list-group-item-info{background-color:#c2d3f9;color:#13337a}.list-group-item-info.list-group-item-action:focus,.list-group-item-info.list-group-item-action:hover{background-color:#abc2f7;color:#13337a}.list-group-item-info.list-group-item-action.active{background-color:#13337a;border-color:#13337a;color:#fff}.list-group-item-warning{background-color:#f4d9b9;color:#713e03}.list-group-item-warning.list-group-item-action:focus,.list-group-item-warning.list-group-item-action:hover{background-color:#f1cda3;color:#713e03}.list-group-item-warning.list-group-item-action.active{background-color:#713e03;border-color:#713e03;color:#fff}.list-group-item-danger{background-color:#f5c2c2;color:#721414}.list-group-item-danger.list-group-item-action:focus,.list-group-item-danger.list-group-item-action:hover{background-color:#f1acac;color:#721414}.list-group-item-danger.list-group-item-action.active{background-color:#721414;border-color:#721414;color:#fff}.list-group-item-light{background-color:#fcfcfc;color:#7e7f80}.list-group-item-light.list-group-item-action:focus,.list-group-item-light.list-group-item-action:hover{background-color:#efefef;color:#7e7f80}.list-group-item-light.list-group-item-action.active{background-color:#7e7f80;border-color:#7e7f80;color:#fff}.list-group-item-dark{background-color:#c0c3c7;color:#10151d}.list-group-item-dark.list-group-item-action:focus,.list-group-item-dark.list-group-item-action:hover{background-color:#b3b6bb;color:#10151d}.list-group-item-dark.list-group-item-action.active{background-color:#10151d;border-color:#10151d;color:#fff}.close{color:#000;float:right;font-size:1.5rem;font-weight:600;line-height:1;opacity:.5;text-shadow:0 1px 0 #fff}.close:hover{color:#000;text-decoration:none}.close:not(:disabled):not(.disabled):focus,.close:not(:disabled):not(.disabled):hover{opacity:.75}button.close{background-color:transparent;border:0;padding:0}a.close.disabled{pointer-events:none}.toast{background-clip:padding-box;background-color:hsla(0,0%,100%,.85);border:1px solid rgba(0,0,0,.1);border-radius:.25rem;box-shadow:0 .25rem .75rem rgba(0,0,0,.1);flex-basis:350px;font-size:.875rem;max-width:350px;opacity:0}.toast:not(:last-child){margin-bottom:.75rem}.toast.showing{opacity:1}.toast.show{display:block;opacity:1}.toast.hide{display:none}.toast-header{align-items:center;background-clip:padding-box;background-color:hsla(0,0%,100%,.85);border-bottom:1px solid rgba(0,0,0,.05);border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px);color:#4b5563;display:flex;padding:.25rem .75rem}.toast-body{padding:.75rem}.modal-open{overflow:hidden}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal{display:none;height:100%;left:0;outline:0;overflow:hidden;position:fixed;top:0;width:100%;z-index:1050}.modal-dialog{margin:.5rem;pointer-events:none;position:relative;width:auto}.modal.fade .modal-dialog{transform:translateY(-50px);transition:transform .3s ease-out}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{transform:none}.modal.modal-static .modal-dialog{transform:scale(1.02)}.modal-dialog-scrollable{display:flex;max-height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 1rem);overflow:hidden}.modal-dialog-scrollable .modal-footer,.modal-dialog-scrollable .modal-header{flex-shrink:0}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{align-items:center;display:flex;min-height:calc(100% - 1rem)}.modal-dialog-centered:before{content:"";display:block;height:calc(100vh - 1rem);height:-moz-min-content;height:min-content}.modal-dialog-centered.modal-dialog-scrollable{flex-direction:column;height:100%;justify-content:center}.modal-dialog-centered.modal-dialog-scrollable .modal-content{max-height:none}.modal-dialog-centered.modal-dialog-scrollable:before{content:none}.modal-content{background-clip:padding-box;background-color:#fff;border:1px solid rgba(0,0,0,.2);border-radius:6px;display:flex;flex-direction:column;outline:0;pointer-events:auto;position:relative;width:100%}.modal-backdrop{background-color:#000;height:100vh;left:0;position:fixed;top:0;width:100vw;z-index:1040}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{align-items:flex-start;border-bottom:1px solid #d1d5db;border-top-left-radius:5px;border-top-right-radius:5px;display:flex;justify-content:space-between;padding:1rem}.modal-header .close{margin:-1rem -1rem -1rem auto;padding:1rem}.modal-title{line-height:1.5;margin-bottom:0}.modal-body{flex:1 1 auto;padding:1rem;position:relative}.modal-footer{align-items:center;border-bottom-left-radius:5px;border-bottom-right-radius:5px;border-top:1px solid #d1d5db;display:flex;flex-wrap:wrap;justify-content:flex-end;padding:.75rem}.modal-footer>*{margin:.25rem}.modal-scrollbar-measure{height:50px;overflow:scroll;position:absolute;top:-9999px;width:50px}@media (min-width:2px){.modal-dialog{margin:1.75rem auto;max-width:500px}.modal-dialog-scrollable{max-height:calc(100% - 3.5rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-dialog-centered:before{height:calc(100vh - 3.5rem);height:-moz-min-content;height:min-content}.modal-sm{max-width:300px}}@media (min-width:9px){.modal-lg,.modal-xl{max-width:800px}}@media (min-width:10px){.modal-xl{max-width:1140px}}.tooltip{word-wrap:break-word;display:block;font-family:Figtree,sans-serif;font-size:.875rem;font-style:normal;font-weight:400;letter-spacing:normal;line-break:auto;line-height:1.5;margin:0;opacity:0;position:absolute;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;white-space:normal;word-break:normal;word-spacing:normal;z-index:1070}.tooltip.show{opacity:.9}.tooltip .arrow{display:block;height:.4rem;position:absolute;width:.8rem}.tooltip .arrow:before{border-color:transparent;border-style:solid;content:"";position:absolute}.bs-tooltip-auto[x-placement^=top],.bs-tooltip-top{padding:.4rem 0}.bs-tooltip-auto[x-placement^=top] .arrow,.bs-tooltip-top .arrow{bottom:0}.bs-tooltip-auto[x-placement^=top] .arrow:before,.bs-tooltip-top .arrow:before{border-top-color:#000;border-width:.4rem .4rem 0;top:0}.bs-tooltip-auto[x-placement^=right],.bs-tooltip-right{padding:0 .4rem}.bs-tooltip-auto[x-placement^=right] .arrow,.bs-tooltip-right .arrow{height:.8rem;left:0;width:.4rem}.bs-tooltip-auto[x-placement^=right] .arrow:before,.bs-tooltip-right .arrow:before{border-right-color:#000;border-width:.4rem .4rem .4rem 0;right:0}.bs-tooltip-auto[x-placement^=bottom],.bs-tooltip-bottom{padding:.4rem 0}.bs-tooltip-auto[x-placement^=bottom] .arrow,.bs-tooltip-bottom .arrow{top:0}.bs-tooltip-auto[x-placement^=bottom] .arrow:before,.bs-tooltip-bottom .arrow:before{border-bottom-color:#000;border-width:0 .4rem .4rem;bottom:0}.bs-tooltip-auto[x-placement^=left],.bs-tooltip-left{padding:0 .4rem}.bs-tooltip-auto[x-placement^=left] .arrow,.bs-tooltip-left .arrow{height:.8rem;right:0;width:.4rem}.bs-tooltip-auto[x-placement^=left] .arrow:before,.bs-tooltip-left .arrow:before{border-left-color:#000;border-width:.4rem 0 .4rem .4rem;left:0}.tooltip-inner{background-color:#000;border-radius:.25rem;color:#fff;max-width:200px;padding:.25rem .5rem;text-align:center}.popover{word-wrap:break-word;background-clip:padding-box;background-color:#fff;border:1px solid rgba(0,0,0,.2);border-radius:6px;font-family:Figtree,sans-serif;font-size:.875rem;font-style:normal;font-weight:400;left:0;letter-spacing:normal;line-break:auto;line-height:1.5;max-width:276px;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;top:0;white-space:normal;word-break:normal;word-spacing:normal;z-index:1060}.popover,.popover .arrow{display:block;position:absolute}.popover .arrow{height:.5rem;margin:0 6px;width:1rem}.popover .arrow:after,.popover .arrow:before{border-color:transparent;border-style:solid;content:"";display:block;position:absolute}.bs-popover-auto[x-placement^=top],.bs-popover-top{margin-bottom:.5rem}.bs-popover-auto[x-placement^=top]>.arrow,.bs-popover-top>.arrow{bottom:calc(-.5rem - 1px)}.bs-popover-auto[x-placement^=top]>.arrow:before,.bs-popover-top>.arrow:before{border-top-color:rgba(0,0,0,.25);border-width:.5rem .5rem 0;bottom:0}.bs-popover-auto[x-placement^=top]>.arrow:after,.bs-popover-top>.arrow:after{border-top-color:#fff;border-width:.5rem .5rem 0;bottom:1px}.bs-popover-auto[x-placement^=right],.bs-popover-right{margin-left:.5rem}.bs-popover-auto[x-placement^=right]>.arrow,.bs-popover-right>.arrow{height:1rem;left:calc(-.5rem - 1px);margin:6px 0;width:.5rem}.bs-popover-auto[x-placement^=right]>.arrow:before,.bs-popover-right>.arrow:before{border-right-color:rgba(0,0,0,.25);border-width:.5rem .5rem .5rem 0;left:0}.bs-popover-auto[x-placement^=right]>.arrow:after,.bs-popover-right>.arrow:after{border-right-color:#fff;border-width:.5rem .5rem .5rem 0;left:1px}.bs-popover-auto[x-placement^=bottom],.bs-popover-bottom{margin-top:.5rem}.bs-popover-auto[x-placement^=bottom]>.arrow,.bs-popover-bottom>.arrow{top:calc(-.5rem - 1px)}.bs-popover-auto[x-placement^=bottom]>.arrow:before,.bs-popover-bottom>.arrow:before{border-bottom-color:rgba(0,0,0,.25);border-width:0 .5rem .5rem;top:0}.bs-popover-auto[x-placement^=bottom]>.arrow:after,.bs-popover-bottom>.arrow:after{border-bottom-color:#fff;border-width:0 .5rem .5rem;top:1px}.bs-popover-auto[x-placement^=bottom] .popover-header:before,.bs-popover-bottom .popover-header:before{border-bottom:1px solid #f7f7f7;content:"";display:block;left:50%;margin-left:-.5rem;position:absolute;top:0;width:1rem}.bs-popover-auto[x-placement^=left],.bs-popover-left{margin-right:.5rem}.bs-popover-auto[x-placement^=left]>.arrow,.bs-popover-left>.arrow{height:1rem;margin:6px 0;right:calc(-.5rem - 1px);width:.5rem}.bs-popover-auto[x-placement^=left]>.arrow:before,.bs-popover-left>.arrow:before{border-left-color:rgba(0,0,0,.25);border-width:.5rem 0 .5rem .5rem;right:0}.bs-popover-auto[x-placement^=left]>.arrow:after,.bs-popover-left>.arrow:after{border-left-color:#fff;border-width:.5rem 0 .5rem .5rem;right:1px}.popover-header{background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-top-left-radius:5px;border-top-right-radius:5px;font-size:1rem;margin-bottom:0;padding:.5rem .75rem}.popover-header:empty{display:none}.popover-body{color:#111827;padding:.5rem .75rem}.carousel{position:relative}.carousel.pointer-event{touch-action:pan-y}.carousel-inner{overflow:hidden;position:relative;width:100%}.carousel-inner:after{clear:both;content:"";display:block}.carousel-item{backface-visibility:hidden;display:none;float:left;margin-right:-100%;position:relative;transition:transform .6s ease-in-out;width:100%}@media (prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.active.carousel-item-right,.carousel-item-next:not(.carousel-item-left){transform:translateX(100%)}.active.carousel-item-left,.carousel-item-prev:not(.carousel-item-right){transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transform:none;transition-property:opacity}.carousel-fade .carousel-item-next.carousel-item-left,.carousel-fade .carousel-item-prev.carousel-item-right,.carousel-fade .carousel-item.active{opacity:1;z-index:1}.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{opacity:0;transition:opacity 0s .6s;z-index:0}@media (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{transition:none}}.carousel-control-next,.carousel-control-prev{align-items:center;background:none;border:0;bottom:0;color:#fff;display:flex;justify-content:center;opacity:.5;padding:0;position:absolute;text-align:center;top:0;transition:opacity .15s ease;width:15%;z-index:1}@media (prefers-reduced-motion:reduce){.carousel-control-next,.carousel-control-prev{transition:none}}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;opacity:.9;outline:0;text-decoration:none}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{background:50%/100% 100% no-repeat;display:inline-block;height:20px;width:20px}.carousel-control-prev-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8'%3E%3Cpath d='m5.25 0-4 4 4 4 1.5-1.5L4.25 4l2.5-2.5L5.25 0z'/%3E%3C/svg%3E")}.carousel-control-next-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8'%3E%3Cpath d='m2.75 0-1.5 1.5L3.75 4l-2.5 2.5L2.75 8l4-4-4-4z'/%3E%3C/svg%3E")}.carousel-indicators{bottom:0;display:flex;justify-content:center;left:0;list-style:none;margin-left:15%;margin-right:15%;padding-left:0;position:absolute;right:0;z-index:15}.carousel-indicators li{background-clip:padding-box;background-color:#fff;border-bottom:10px solid transparent;border-top:10px solid transparent;box-sizing:content-box;cursor:pointer;flex:0 1 auto;height:3px;margin-left:3px;margin-right:3px;opacity:.5;text-indent:-999px;transition:opacity .6s ease;width:30px}@media (prefers-reduced-motion:reduce){.carousel-indicators li{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{bottom:20px;color:#fff;left:15%;padding-bottom:20px;padding-top:20px;position:absolute;right:15%;text-align:center;z-index:10}@keyframes spinner-border{to{transform:rotate(1turn)}}.spinner-border{animation:spinner-border .75s linear infinite;border:.25em solid;border-radius:50%;border-right:.25em solid transparent;display:inline-block;height:2rem;vertical-align:-.125em;width:2rem}.spinner-border-sm{border-width:.2em;height:1rem;width:1rem}@keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}.spinner-grow{animation:spinner-grow .75s linear infinite;background-color:currentcolor;border-radius:50%;display:inline-block;height:2rem;opacity:0;vertical-align:-.125em;width:2rem}.spinner-grow-sm{height:1rem;width:1rem}@media (prefers-reduced-motion:reduce){.spinner-border,.spinner-grow{animation-duration:1.5s}}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.bg-primary{background-color:#4040c8!important}a.bg-primary:focus,a.bg-primary:hover,button.bg-primary:focus,button.bg-primary:hover{background-color:#3030a5!important}.bg-secondary{background-color:#4b5563!important}a.bg-secondary:focus,a.bg-secondary:hover,button.bg-secondary:focus,button.bg-secondary:hover{background-color:#353c46!important}.bg-success{background-color:#059669!important}a.bg-success:focus,a.bg-success:hover,button.bg-success:focus,button.bg-success:hover{background-color:#036546!important}.bg-info{background-color:#2563eb!important}a.bg-info:focus,a.bg-info:hover,button.bg-info:focus,button.bg-info:hover{background-color:#134cca!important}.bg-warning{background-color:#d97706!important}a.bg-warning:focus,a.bg-warning:hover,button.bg-warning:focus,button.bg-warning:hover{background-color:#a75c05!important}.bg-danger{background-color:#dc2626!important}a.bg-danger:focus,a.bg-danger:hover,button.bg-danger:focus,button.bg-danger:hover{background-color:#b21d1d!important}.bg-light{background-color:#f3f4f6!important}a.bg-light:focus,a.bg-light:hover,button.bg-light:focus,button.bg-light:hover{background-color:#d6d9e0!important}.bg-dark{background-color:#1f2937!important}a.bg-dark:focus,a.bg-dark:hover,button.bg-dark:focus,button.bg-dark:hover{background-color:#0d1116!important}.bg-white{background-color:#fff!important}.bg-transparent{background-color:transparent!important}.border{border:1px solid #d1d5db!important}.border-top{border-top:1px solid #d1d5db!important}.border-right{border-right:1px solid #d1d5db!important}.border-bottom{border-bottom:1px solid #d1d5db!important}.border-left{border-left:1px solid #d1d5db!important}.border-0{border:0!important}.border-top-0{border-top:0!important}.border-right-0{border-right:0!important}.border-bottom-0{border-bottom:0!important}.border-left-0{border-left:0!important}.border-primary{border-color:#4040c8!important}.border-secondary{border-color:#4b5563!important}.border-success{border-color:#059669!important}.border-info{border-color:#2563eb!important}.border-warning{border-color:#d97706!important}.border-danger{border-color:#dc2626!important}.border-light{border-color:#f3f4f6!important}.border-dark{border-color:#1f2937!important}.border-white{border-color:#fff!important}.rounded-sm{border-radius:.2rem!important}.rounded{border-radius:.25rem!important}.rounded-top{border-top-left-radius:.25rem!important}.rounded-right,.rounded-top{border-top-right-radius:.25rem!important}.rounded-bottom,.rounded-right{border-bottom-right-radius:.25rem!important}.rounded-bottom,.rounded-left{border-bottom-left-radius:.25rem!important}.rounded-left{border-top-left-radius:.25rem!important}.rounded-lg{border-radius:6px!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:50rem!important}.rounded-0{border-radius:0!important}.clearfix:after{clear:both;content:"";display:block}.d-none{display:none!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:flex!important}.d-inline-flex{display:inline-flex!important}@media (min-width:2px){.d-sm-none{display:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:flex!important}.d-sm-inline-flex{display:inline-flex!important}}@media (min-width:8px){.d-md-none{display:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:flex!important}.d-md-inline-flex{display:inline-flex!important}}@media (min-width:9px){.d-lg-none{display:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:flex!important}.d-lg-inline-flex{display:inline-flex!important}}@media (min-width:10px){.d-xl-none{display:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:flex!important}.d-xl-inline-flex{display:inline-flex!important}}@media print{.d-print-none{display:none!important}.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:flex!important}.d-print-inline-flex{display:inline-flex!important}}.embed-responsive{display:block;overflow:hidden;padding:0;position:relative;width:100%}.embed-responsive:before{content:"";display:block}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{border:0;bottom:0;height:100%;left:0;position:absolute;top:0;width:100%}.embed-responsive-21by9:before{padding-top:42.85714286%}.embed-responsive-16by9:before{padding-top:56.25%}.embed-responsive-4by3:before{padding-top:75%}.embed-responsive-1by1:before{padding-top:100%}.flex-row{flex-direction:row!important}.flex-column{flex-direction:column!important}.flex-row-reverse{flex-direction:row-reverse!important}.flex-column-reverse{flex-direction:column-reverse!important}.flex-wrap{flex-wrap:wrap!important}.flex-nowrap{flex-wrap:nowrap!important}.flex-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-fill{flex:1 1 auto!important}.flex-grow-0{flex-grow:0!important}.flex-grow-1{flex-grow:1!important}.flex-shrink-0{flex-shrink:0!important}.flex-shrink-1{flex-shrink:1!important}.justify-content-start{justify-content:flex-start!important}.justify-content-end{justify-content:flex-end!important}.justify-content-center{justify-content:center!important}.justify-content-between{justify-content:space-between!important}.justify-content-around{justify-content:space-around!important}.align-items-start{align-items:flex-start!important}.align-items-end{align-items:flex-end!important}.align-items-center{align-items:center!important}.align-items-baseline{align-items:baseline!important}.align-items-stretch{align-items:stretch!important}.align-content-start{align-content:flex-start!important}.align-content-end{align-content:flex-end!important}.align-content-center{align-content:center!important}.align-content-between{align-content:space-between!important}.align-content-around{align-content:space-around!important}.align-content-stretch{align-content:stretch!important}.align-self-auto{align-self:auto!important}.align-self-start{align-self:flex-start!important}.align-self-end{align-self:flex-end!important}.align-self-center{align-self:center!important}.align-self-baseline{align-self:baseline!important}.align-self-stretch{align-self:stretch!important}@media (min-width:2px){.flex-sm-row{flex-direction:row!important}.flex-sm-column{flex-direction:column!important}.flex-sm-row-reverse{flex-direction:row-reverse!important}.flex-sm-column-reverse{flex-direction:column-reverse!important}.flex-sm-wrap{flex-wrap:wrap!important}.flex-sm-nowrap{flex-wrap:nowrap!important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-sm-fill{flex:1 1 auto!important}.flex-sm-grow-0{flex-grow:0!important}.flex-sm-grow-1{flex-grow:1!important}.flex-sm-shrink-0{flex-shrink:0!important}.flex-sm-shrink-1{flex-shrink:1!important}.justify-content-sm-start{justify-content:flex-start!important}.justify-content-sm-end{justify-content:flex-end!important}.justify-content-sm-center{justify-content:center!important}.justify-content-sm-between{justify-content:space-between!important}.justify-content-sm-around{justify-content:space-around!important}.align-items-sm-start{align-items:flex-start!important}.align-items-sm-end{align-items:flex-end!important}.align-items-sm-center{align-items:center!important}.align-items-sm-baseline{align-items:baseline!important}.align-items-sm-stretch{align-items:stretch!important}.align-content-sm-start{align-content:flex-start!important}.align-content-sm-end{align-content:flex-end!important}.align-content-sm-center{align-content:center!important}.align-content-sm-between{align-content:space-between!important}.align-content-sm-around{align-content:space-around!important}.align-content-sm-stretch{align-content:stretch!important}.align-self-sm-auto{align-self:auto!important}.align-self-sm-start{align-self:flex-start!important}.align-self-sm-end{align-self:flex-end!important}.align-self-sm-center{align-self:center!important}.align-self-sm-baseline{align-self:baseline!important}.align-self-sm-stretch{align-self:stretch!important}}@media (min-width:8px){.flex-md-row{flex-direction:row!important}.flex-md-column{flex-direction:column!important}.flex-md-row-reverse{flex-direction:row-reverse!important}.flex-md-column-reverse{flex-direction:column-reverse!important}.flex-md-wrap{flex-wrap:wrap!important}.flex-md-nowrap{flex-wrap:nowrap!important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-md-fill{flex:1 1 auto!important}.flex-md-grow-0{flex-grow:0!important}.flex-md-grow-1{flex-grow:1!important}.flex-md-shrink-0{flex-shrink:0!important}.flex-md-shrink-1{flex-shrink:1!important}.justify-content-md-start{justify-content:flex-start!important}.justify-content-md-end{justify-content:flex-end!important}.justify-content-md-center{justify-content:center!important}.justify-content-md-between{justify-content:space-between!important}.justify-content-md-around{justify-content:space-around!important}.align-items-md-start{align-items:flex-start!important}.align-items-md-end{align-items:flex-end!important}.align-items-md-center{align-items:center!important}.align-items-md-baseline{align-items:baseline!important}.align-items-md-stretch{align-items:stretch!important}.align-content-md-start{align-content:flex-start!important}.align-content-md-end{align-content:flex-end!important}.align-content-md-center{align-content:center!important}.align-content-md-between{align-content:space-between!important}.align-content-md-around{align-content:space-around!important}.align-content-md-stretch{align-content:stretch!important}.align-self-md-auto{align-self:auto!important}.align-self-md-start{align-self:flex-start!important}.align-self-md-end{align-self:flex-end!important}.align-self-md-center{align-self:center!important}.align-self-md-baseline{align-self:baseline!important}.align-self-md-stretch{align-self:stretch!important}}@media (min-width:9px){.flex-lg-row{flex-direction:row!important}.flex-lg-column{flex-direction:column!important}.flex-lg-row-reverse{flex-direction:row-reverse!important}.flex-lg-column-reverse{flex-direction:column-reverse!important}.flex-lg-wrap{flex-wrap:wrap!important}.flex-lg-nowrap{flex-wrap:nowrap!important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-lg-fill{flex:1 1 auto!important}.flex-lg-grow-0{flex-grow:0!important}.flex-lg-grow-1{flex-grow:1!important}.flex-lg-shrink-0{flex-shrink:0!important}.flex-lg-shrink-1{flex-shrink:1!important}.justify-content-lg-start{justify-content:flex-start!important}.justify-content-lg-end{justify-content:flex-end!important}.justify-content-lg-center{justify-content:center!important}.justify-content-lg-between{justify-content:space-between!important}.justify-content-lg-around{justify-content:space-around!important}.align-items-lg-start{align-items:flex-start!important}.align-items-lg-end{align-items:flex-end!important}.align-items-lg-center{align-items:center!important}.align-items-lg-baseline{align-items:baseline!important}.align-items-lg-stretch{align-items:stretch!important}.align-content-lg-start{align-content:flex-start!important}.align-content-lg-end{align-content:flex-end!important}.align-content-lg-center{align-content:center!important}.align-content-lg-between{align-content:space-between!important}.align-content-lg-around{align-content:space-around!important}.align-content-lg-stretch{align-content:stretch!important}.align-self-lg-auto{align-self:auto!important}.align-self-lg-start{align-self:flex-start!important}.align-self-lg-end{align-self:flex-end!important}.align-self-lg-center{align-self:center!important}.align-self-lg-baseline{align-self:baseline!important}.align-self-lg-stretch{align-self:stretch!important}}@media (min-width:10px){.flex-xl-row{flex-direction:row!important}.flex-xl-column{flex-direction:column!important}.flex-xl-row-reverse{flex-direction:row-reverse!important}.flex-xl-column-reverse{flex-direction:column-reverse!important}.flex-xl-wrap{flex-wrap:wrap!important}.flex-xl-nowrap{flex-wrap:nowrap!important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-xl-fill{flex:1 1 auto!important}.flex-xl-grow-0{flex-grow:0!important}.flex-xl-grow-1{flex-grow:1!important}.flex-xl-shrink-0{flex-shrink:0!important}.flex-xl-shrink-1{flex-shrink:1!important}.justify-content-xl-start{justify-content:flex-start!important}.justify-content-xl-end{justify-content:flex-end!important}.justify-content-xl-center{justify-content:center!important}.justify-content-xl-between{justify-content:space-between!important}.justify-content-xl-around{justify-content:space-around!important}.align-items-xl-start{align-items:flex-start!important}.align-items-xl-end{align-items:flex-end!important}.align-items-xl-center{align-items:center!important}.align-items-xl-baseline{align-items:baseline!important}.align-items-xl-stretch{align-items:stretch!important}.align-content-xl-start{align-content:flex-start!important}.align-content-xl-end{align-content:flex-end!important}.align-content-xl-center{align-content:center!important}.align-content-xl-between{align-content:space-between!important}.align-content-xl-around{align-content:space-around!important}.align-content-xl-stretch{align-content:stretch!important}.align-self-xl-auto{align-self:auto!important}.align-self-xl-start{align-self:flex-start!important}.align-self-xl-end{align-self:flex-end!important}.align-self-xl-center{align-self:center!important}.align-self-xl-baseline{align-self:baseline!important}.align-self-xl-stretch{align-self:stretch!important}}.float-left{float:left!important}.float-right{float:right!important}.float-none{float:none!important}@media (min-width:2px){.float-sm-left{float:left!important}.float-sm-right{float:right!important}.float-sm-none{float:none!important}}@media (min-width:8px){.float-md-left{float:left!important}.float-md-right{float:right!important}.float-md-none{float:none!important}}@media (min-width:9px){.float-lg-left{float:left!important}.float-lg-right{float:right!important}.float-lg-none{float:none!important}}@media (min-width:10px){.float-xl-left{float:left!important}.float-xl-right{float:right!important}.float-xl-none{float:none!important}}.user-select-all{-webkit-user-select:all!important;-moz-user-select:all!important;user-select:all!important}.user-select-auto{-webkit-user-select:auto!important;-moz-user-select:auto!important;user-select:auto!important}.user-select-none{-webkit-user-select:none!important;-moz-user-select:none!important;user-select:none!important}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:sticky!important}.fixed-top{top:0}.fixed-bottom,.fixed-top{left:0;position:fixed;right:0;z-index:1030}.fixed-bottom{bottom:0}@supports (position:sticky){.sticky-top{position:sticky;top:0;z-index:1020}}.sr-only{clip:rect(0,0,0,0);border:0;height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;white-space:nowrap;width:1px}.sr-only-focusable:active,.sr-only-focusable:focus{clip:auto;height:auto;overflow:visible;position:static;white-space:normal;width:auto}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075)!important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15)!important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175)!important}.shadow-none{box-shadow:none!important}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mw-100{max-width:100%!important}.mh-100{max-height:100%!important}.min-vw-100{min-width:100vw!important}.min-vh-100{min-height:100vh!important}.vw-100{width:100vw!important}.vh-100{height:100vh!important}.m-0{margin:0!important}.mt-0,.my-0{margin-top:0!important}.mr-0,.mx-0{margin-right:0!important}.mb-0,.my-0{margin-bottom:0!important}.ml-0,.mx-0{margin-left:0!important}.m-1{margin:.25rem!important}.mt-1,.my-1{margin-top:.25rem!important}.mr-1,.mx-1{margin-right:.25rem!important}.mb-1,.my-1{margin-bottom:.25rem!important}.ml-1,.mx-1{margin-left:.25rem!important}.m-2{margin:.5rem!important}.mt-2,.my-2{margin-top:.5rem!important}.mr-2,.mx-2{margin-right:.5rem!important}.mb-2,.my-2{margin-bottom:.5rem!important}.ml-2,.mx-2{margin-left:.5rem!important}.m-3{margin:1rem!important}.mt-3,.my-3{margin-top:1rem!important}.mr-3,.mx-3{margin-right:1rem!important}.mb-3,.my-3{margin-bottom:1rem!important}.ml-3,.mx-3{margin-left:1rem!important}.m-4{margin:1.5rem!important}.mt-4,.my-4{margin-top:1.5rem!important}.mr-4,.mx-4{margin-right:1.5rem!important}.mb-4,.my-4{margin-bottom:1.5rem!important}.ml-4,.mx-4{margin-left:1.5rem!important}.m-5{margin:3rem!important}.mt-5,.my-5{margin-top:3rem!important}.mr-5,.mx-5{margin-right:3rem!important}.mb-5,.my-5{margin-bottom:3rem!important}.ml-5,.mx-5{margin-left:3rem!important}.p-0{padding:0!important}.pt-0,.py-0{padding-top:0!important}.pr-0,.px-0{padding-right:0!important}.pb-0,.py-0{padding-bottom:0!important}.pl-0,.px-0{padding-left:0!important}.p-1{padding:.25rem!important}.pt-1,.py-1{padding-top:.25rem!important}.pr-1,.px-1{padding-right:.25rem!important}.pb-1,.py-1{padding-bottom:.25rem!important}.pl-1,.px-1{padding-left:.25rem!important}.p-2{padding:.5rem!important}.pt-2,.py-2{padding-top:.5rem!important}.pr-2,.px-2{padding-right:.5rem!important}.pb-2,.py-2{padding-bottom:.5rem!important}.pl-2,.px-2{padding-left:.5rem!important}.p-3{padding:1rem!important}.pt-3,.py-3{padding-top:1rem!important}.pr-3,.px-3{padding-right:1rem!important}.pb-3,.py-3{padding-bottom:1rem!important}.pl-3,.px-3{padding-left:1rem!important}.p-4{padding:1.5rem!important}.pt-4,.py-4{padding-top:1.5rem!important}.pr-4,.px-4{padding-right:1.5rem!important}.pb-4,.py-4{padding-bottom:1.5rem!important}.pl-4,.px-4{padding-left:1.5rem!important}.p-5{padding:3rem!important}.pt-5,.py-5{padding-top:3rem!important}.pr-5,.px-5{padding-right:3rem!important}.pb-5,.py-5{padding-bottom:3rem!important}.pl-5,.px-5{padding-left:3rem!important}.m-n1{margin:-.25rem!important}.mt-n1,.my-n1{margin-top:-.25rem!important}.mr-n1,.mx-n1{margin-right:-.25rem!important}.mb-n1,.my-n1{margin-bottom:-.25rem!important}.ml-n1,.mx-n1{margin-left:-.25rem!important}.m-n2{margin:-.5rem!important}.mt-n2,.my-n2{margin-top:-.5rem!important}.mr-n2,.mx-n2{margin-right:-.5rem!important}.mb-n2,.my-n2{margin-bottom:-.5rem!important}.ml-n2,.mx-n2{margin-left:-.5rem!important}.m-n3{margin:-1rem!important}.mt-n3,.my-n3{margin-top:-1rem!important}.mr-n3,.mx-n3{margin-right:-1rem!important}.mb-n3,.my-n3{margin-bottom:-1rem!important}.ml-n3,.mx-n3{margin-left:-1rem!important}.m-n4{margin:-1.5rem!important}.mt-n4,.my-n4{margin-top:-1.5rem!important}.mr-n4,.mx-n4{margin-right:-1.5rem!important}.mb-n4,.my-n4{margin-bottom:-1.5rem!important}.ml-n4,.mx-n4{margin-left:-1.5rem!important}.m-n5{margin:-3rem!important}.mt-n5,.my-n5{margin-top:-3rem!important}.mr-n5,.mx-n5{margin-right:-3rem!important}.mb-n5,.my-n5{margin-bottom:-3rem!important}.ml-n5,.mx-n5{margin-left:-3rem!important}.m-auto{margin:auto!important}.mt-auto,.my-auto{margin-top:auto!important}.mr-auto,.mx-auto{margin-right:auto!important}.mb-auto,.my-auto{margin-bottom:auto!important}.ml-auto,.mx-auto{margin-left:auto!important}@media (min-width:2px){.m-sm-0{margin:0!important}.mt-sm-0,.my-sm-0{margin-top:0!important}.mr-sm-0,.mx-sm-0{margin-right:0!important}.mb-sm-0,.my-sm-0{margin-bottom:0!important}.ml-sm-0,.mx-sm-0{margin-left:0!important}.m-sm-1{margin:.25rem!important}.mt-sm-1,.my-sm-1{margin-top:.25rem!important}.mr-sm-1,.mx-sm-1{margin-right:.25rem!important}.mb-sm-1,.my-sm-1{margin-bottom:.25rem!important}.ml-sm-1,.mx-sm-1{margin-left:.25rem!important}.m-sm-2{margin:.5rem!important}.mt-sm-2,.my-sm-2{margin-top:.5rem!important}.mr-sm-2,.mx-sm-2{margin-right:.5rem!important}.mb-sm-2,.my-sm-2{margin-bottom:.5rem!important}.ml-sm-2,.mx-sm-2{margin-left:.5rem!important}.m-sm-3{margin:1rem!important}.mt-sm-3,.my-sm-3{margin-top:1rem!important}.mr-sm-3,.mx-sm-3{margin-right:1rem!important}.mb-sm-3,.my-sm-3{margin-bottom:1rem!important}.ml-sm-3,.mx-sm-3{margin-left:1rem!important}.m-sm-4{margin:1.5rem!important}.mt-sm-4,.my-sm-4{margin-top:1.5rem!important}.mr-sm-4,.mx-sm-4{margin-right:1.5rem!important}.mb-sm-4,.my-sm-4{margin-bottom:1.5rem!important}.ml-sm-4,.mx-sm-4{margin-left:1.5rem!important}.m-sm-5{margin:3rem!important}.mt-sm-5,.my-sm-5{margin-top:3rem!important}.mr-sm-5,.mx-sm-5{margin-right:3rem!important}.mb-sm-5,.my-sm-5{margin-bottom:3rem!important}.ml-sm-5,.mx-sm-5{margin-left:3rem!important}.p-sm-0{padding:0!important}.pt-sm-0,.py-sm-0{padding-top:0!important}.pr-sm-0,.px-sm-0{padding-right:0!important}.pb-sm-0,.py-sm-0{padding-bottom:0!important}.pl-sm-0,.px-sm-0{padding-left:0!important}.p-sm-1{padding:.25rem!important}.pt-sm-1,.py-sm-1{padding-top:.25rem!important}.pr-sm-1,.px-sm-1{padding-right:.25rem!important}.pb-sm-1,.py-sm-1{padding-bottom:.25rem!important}.pl-sm-1,.px-sm-1{padding-left:.25rem!important}.p-sm-2{padding:.5rem!important}.pt-sm-2,.py-sm-2{padding-top:.5rem!important}.pr-sm-2,.px-sm-2{padding-right:.5rem!important}.pb-sm-2,.py-sm-2{padding-bottom:.5rem!important}.pl-sm-2,.px-sm-2{padding-left:.5rem!important}.p-sm-3{padding:1rem!important}.pt-sm-3,.py-sm-3{padding-top:1rem!important}.pr-sm-3,.px-sm-3{padding-right:1rem!important}.pb-sm-3,.py-sm-3{padding-bottom:1rem!important}.pl-sm-3,.px-sm-3{padding-left:1rem!important}.p-sm-4{padding:1.5rem!important}.pt-sm-4,.py-sm-4{padding-top:1.5rem!important}.pr-sm-4,.px-sm-4{padding-right:1.5rem!important}.pb-sm-4,.py-sm-4{padding-bottom:1.5rem!important}.pl-sm-4,.px-sm-4{padding-left:1.5rem!important}.p-sm-5{padding:3rem!important}.pt-sm-5,.py-sm-5{padding-top:3rem!important}.pr-sm-5,.px-sm-5{padding-right:3rem!important}.pb-sm-5,.py-sm-5{padding-bottom:3rem!important}.pl-sm-5,.px-sm-5{padding-left:3rem!important}.m-sm-n1{margin:-.25rem!important}.mt-sm-n1,.my-sm-n1{margin-top:-.25rem!important}.mr-sm-n1,.mx-sm-n1{margin-right:-.25rem!important}.mb-sm-n1,.my-sm-n1{margin-bottom:-.25rem!important}.ml-sm-n1,.mx-sm-n1{margin-left:-.25rem!important}.m-sm-n2{margin:-.5rem!important}.mt-sm-n2,.my-sm-n2{margin-top:-.5rem!important}.mr-sm-n2,.mx-sm-n2{margin-right:-.5rem!important}.mb-sm-n2,.my-sm-n2{margin-bottom:-.5rem!important}.ml-sm-n2,.mx-sm-n2{margin-left:-.5rem!important}.m-sm-n3{margin:-1rem!important}.mt-sm-n3,.my-sm-n3{margin-top:-1rem!important}.mr-sm-n3,.mx-sm-n3{margin-right:-1rem!important}.mb-sm-n3,.my-sm-n3{margin-bottom:-1rem!important}.ml-sm-n3,.mx-sm-n3{margin-left:-1rem!important}.m-sm-n4{margin:-1.5rem!important}.mt-sm-n4,.my-sm-n4{margin-top:-1.5rem!important}.mr-sm-n4,.mx-sm-n4{margin-right:-1.5rem!important}.mb-sm-n4,.my-sm-n4{margin-bottom:-1.5rem!important}.ml-sm-n4,.mx-sm-n4{margin-left:-1.5rem!important}.m-sm-n5{margin:-3rem!important}.mt-sm-n5,.my-sm-n5{margin-top:-3rem!important}.mr-sm-n5,.mx-sm-n5{margin-right:-3rem!important}.mb-sm-n5,.my-sm-n5{margin-bottom:-3rem!important}.ml-sm-n5,.mx-sm-n5{margin-left:-3rem!important}.m-sm-auto{margin:auto!important}.mt-sm-auto,.my-sm-auto{margin-top:auto!important}.mr-sm-auto,.mx-sm-auto{margin-right:auto!important}.mb-sm-auto,.my-sm-auto{margin-bottom:auto!important}.ml-sm-auto,.mx-sm-auto{margin-left:auto!important}}@media (min-width:8px){.m-md-0{margin:0!important}.mt-md-0,.my-md-0{margin-top:0!important}.mr-md-0,.mx-md-0{margin-right:0!important}.mb-md-0,.my-md-0{margin-bottom:0!important}.ml-md-0,.mx-md-0{margin-left:0!important}.m-md-1{margin:.25rem!important}.mt-md-1,.my-md-1{margin-top:.25rem!important}.mr-md-1,.mx-md-1{margin-right:.25rem!important}.mb-md-1,.my-md-1{margin-bottom:.25rem!important}.ml-md-1,.mx-md-1{margin-left:.25rem!important}.m-md-2{margin:.5rem!important}.mt-md-2,.my-md-2{margin-top:.5rem!important}.mr-md-2,.mx-md-2{margin-right:.5rem!important}.mb-md-2,.my-md-2{margin-bottom:.5rem!important}.ml-md-2,.mx-md-2{margin-left:.5rem!important}.m-md-3{margin:1rem!important}.mt-md-3,.my-md-3{margin-top:1rem!important}.mr-md-3,.mx-md-3{margin-right:1rem!important}.mb-md-3,.my-md-3{margin-bottom:1rem!important}.ml-md-3,.mx-md-3{margin-left:1rem!important}.m-md-4{margin:1.5rem!important}.mt-md-4,.my-md-4{margin-top:1.5rem!important}.mr-md-4,.mx-md-4{margin-right:1.5rem!important}.mb-md-4,.my-md-4{margin-bottom:1.5rem!important}.ml-md-4,.mx-md-4{margin-left:1.5rem!important}.m-md-5{margin:3rem!important}.mt-md-5,.my-md-5{margin-top:3rem!important}.mr-md-5,.mx-md-5{margin-right:3rem!important}.mb-md-5,.my-md-5{margin-bottom:3rem!important}.ml-md-5,.mx-md-5{margin-left:3rem!important}.p-md-0{padding:0!important}.pt-md-0,.py-md-0{padding-top:0!important}.pr-md-0,.px-md-0{padding-right:0!important}.pb-md-0,.py-md-0{padding-bottom:0!important}.pl-md-0,.px-md-0{padding-left:0!important}.p-md-1{padding:.25rem!important}.pt-md-1,.py-md-1{padding-top:.25rem!important}.pr-md-1,.px-md-1{padding-right:.25rem!important}.pb-md-1,.py-md-1{padding-bottom:.25rem!important}.pl-md-1,.px-md-1{padding-left:.25rem!important}.p-md-2{padding:.5rem!important}.pt-md-2,.py-md-2{padding-top:.5rem!important}.pr-md-2,.px-md-2{padding-right:.5rem!important}.pb-md-2,.py-md-2{padding-bottom:.5rem!important}.pl-md-2,.px-md-2{padding-left:.5rem!important}.p-md-3{padding:1rem!important}.pt-md-3,.py-md-3{padding-top:1rem!important}.pr-md-3,.px-md-3{padding-right:1rem!important}.pb-md-3,.py-md-3{padding-bottom:1rem!important}.pl-md-3,.px-md-3{padding-left:1rem!important}.p-md-4{padding:1.5rem!important}.pt-md-4,.py-md-4{padding-top:1.5rem!important}.pr-md-4,.px-md-4{padding-right:1.5rem!important}.pb-md-4,.py-md-4{padding-bottom:1.5rem!important}.pl-md-4,.px-md-4{padding-left:1.5rem!important}.p-md-5{padding:3rem!important}.pt-md-5,.py-md-5{padding-top:3rem!important}.pr-md-5,.px-md-5{padding-right:3rem!important}.pb-md-5,.py-md-5{padding-bottom:3rem!important}.pl-md-5,.px-md-5{padding-left:3rem!important}.m-md-n1{margin:-.25rem!important}.mt-md-n1,.my-md-n1{margin-top:-.25rem!important}.mr-md-n1,.mx-md-n1{margin-right:-.25rem!important}.mb-md-n1,.my-md-n1{margin-bottom:-.25rem!important}.ml-md-n1,.mx-md-n1{margin-left:-.25rem!important}.m-md-n2{margin:-.5rem!important}.mt-md-n2,.my-md-n2{margin-top:-.5rem!important}.mr-md-n2,.mx-md-n2{margin-right:-.5rem!important}.mb-md-n2,.my-md-n2{margin-bottom:-.5rem!important}.ml-md-n2,.mx-md-n2{margin-left:-.5rem!important}.m-md-n3{margin:-1rem!important}.mt-md-n3,.my-md-n3{margin-top:-1rem!important}.mr-md-n3,.mx-md-n3{margin-right:-1rem!important}.mb-md-n3,.my-md-n3{margin-bottom:-1rem!important}.ml-md-n3,.mx-md-n3{margin-left:-1rem!important}.m-md-n4{margin:-1.5rem!important}.mt-md-n4,.my-md-n4{margin-top:-1.5rem!important}.mr-md-n4,.mx-md-n4{margin-right:-1.5rem!important}.mb-md-n4,.my-md-n4{margin-bottom:-1.5rem!important}.ml-md-n4,.mx-md-n4{margin-left:-1.5rem!important}.m-md-n5{margin:-3rem!important}.mt-md-n5,.my-md-n5{margin-top:-3rem!important}.mr-md-n5,.mx-md-n5{margin-right:-3rem!important}.mb-md-n5,.my-md-n5{margin-bottom:-3rem!important}.ml-md-n5,.mx-md-n5{margin-left:-3rem!important}.m-md-auto{margin:auto!important}.mt-md-auto,.my-md-auto{margin-top:auto!important}.mr-md-auto,.mx-md-auto{margin-right:auto!important}.mb-md-auto,.my-md-auto{margin-bottom:auto!important}.ml-md-auto,.mx-md-auto{margin-left:auto!important}}@media (min-width:9px){.m-lg-0{margin:0!important}.mt-lg-0,.my-lg-0{margin-top:0!important}.mr-lg-0,.mx-lg-0{margin-right:0!important}.mb-lg-0,.my-lg-0{margin-bottom:0!important}.ml-lg-0,.mx-lg-0{margin-left:0!important}.m-lg-1{margin:.25rem!important}.mt-lg-1,.my-lg-1{margin-top:.25rem!important}.mr-lg-1,.mx-lg-1{margin-right:.25rem!important}.mb-lg-1,.my-lg-1{margin-bottom:.25rem!important}.ml-lg-1,.mx-lg-1{margin-left:.25rem!important}.m-lg-2{margin:.5rem!important}.mt-lg-2,.my-lg-2{margin-top:.5rem!important}.mr-lg-2,.mx-lg-2{margin-right:.5rem!important}.mb-lg-2,.my-lg-2{margin-bottom:.5rem!important}.ml-lg-2,.mx-lg-2{margin-left:.5rem!important}.m-lg-3{margin:1rem!important}.mt-lg-3,.my-lg-3{margin-top:1rem!important}.mr-lg-3,.mx-lg-3{margin-right:1rem!important}.mb-lg-3,.my-lg-3{margin-bottom:1rem!important}.ml-lg-3,.mx-lg-3{margin-left:1rem!important}.m-lg-4{margin:1.5rem!important}.mt-lg-4,.my-lg-4{margin-top:1.5rem!important}.mr-lg-4,.mx-lg-4{margin-right:1.5rem!important}.mb-lg-4,.my-lg-4{margin-bottom:1.5rem!important}.ml-lg-4,.mx-lg-4{margin-left:1.5rem!important}.m-lg-5{margin:3rem!important}.mt-lg-5,.my-lg-5{margin-top:3rem!important}.mr-lg-5,.mx-lg-5{margin-right:3rem!important}.mb-lg-5,.my-lg-5{margin-bottom:3rem!important}.ml-lg-5,.mx-lg-5{margin-left:3rem!important}.p-lg-0{padding:0!important}.pt-lg-0,.py-lg-0{padding-top:0!important}.pr-lg-0,.px-lg-0{padding-right:0!important}.pb-lg-0,.py-lg-0{padding-bottom:0!important}.pl-lg-0,.px-lg-0{padding-left:0!important}.p-lg-1{padding:.25rem!important}.pt-lg-1,.py-lg-1{padding-top:.25rem!important}.pr-lg-1,.px-lg-1{padding-right:.25rem!important}.pb-lg-1,.py-lg-1{padding-bottom:.25rem!important}.pl-lg-1,.px-lg-1{padding-left:.25rem!important}.p-lg-2{padding:.5rem!important}.pt-lg-2,.py-lg-2{padding-top:.5rem!important}.pr-lg-2,.px-lg-2{padding-right:.5rem!important}.pb-lg-2,.py-lg-2{padding-bottom:.5rem!important}.pl-lg-2,.px-lg-2{padding-left:.5rem!important}.p-lg-3{padding:1rem!important}.pt-lg-3,.py-lg-3{padding-top:1rem!important}.pr-lg-3,.px-lg-3{padding-right:1rem!important}.pb-lg-3,.py-lg-3{padding-bottom:1rem!important}.pl-lg-3,.px-lg-3{padding-left:1rem!important}.p-lg-4{padding:1.5rem!important}.pt-lg-4,.py-lg-4{padding-top:1.5rem!important}.pr-lg-4,.px-lg-4{padding-right:1.5rem!important}.pb-lg-4,.py-lg-4{padding-bottom:1.5rem!important}.pl-lg-4,.px-lg-4{padding-left:1.5rem!important}.p-lg-5{padding:3rem!important}.pt-lg-5,.py-lg-5{padding-top:3rem!important}.pr-lg-5,.px-lg-5{padding-right:3rem!important}.pb-lg-5,.py-lg-5{padding-bottom:3rem!important}.pl-lg-5,.px-lg-5{padding-left:3rem!important}.m-lg-n1{margin:-.25rem!important}.mt-lg-n1,.my-lg-n1{margin-top:-.25rem!important}.mr-lg-n1,.mx-lg-n1{margin-right:-.25rem!important}.mb-lg-n1,.my-lg-n1{margin-bottom:-.25rem!important}.ml-lg-n1,.mx-lg-n1{margin-left:-.25rem!important}.m-lg-n2{margin:-.5rem!important}.mt-lg-n2,.my-lg-n2{margin-top:-.5rem!important}.mr-lg-n2,.mx-lg-n2{margin-right:-.5rem!important}.mb-lg-n2,.my-lg-n2{margin-bottom:-.5rem!important}.ml-lg-n2,.mx-lg-n2{margin-left:-.5rem!important}.m-lg-n3{margin:-1rem!important}.mt-lg-n3,.my-lg-n3{margin-top:-1rem!important}.mr-lg-n3,.mx-lg-n3{margin-right:-1rem!important}.mb-lg-n3,.my-lg-n3{margin-bottom:-1rem!important}.ml-lg-n3,.mx-lg-n3{margin-left:-1rem!important}.m-lg-n4{margin:-1.5rem!important}.mt-lg-n4,.my-lg-n4{margin-top:-1.5rem!important}.mr-lg-n4,.mx-lg-n4{margin-right:-1.5rem!important}.mb-lg-n4,.my-lg-n4{margin-bottom:-1.5rem!important}.ml-lg-n4,.mx-lg-n4{margin-left:-1.5rem!important}.m-lg-n5{margin:-3rem!important}.mt-lg-n5,.my-lg-n5{margin-top:-3rem!important}.mr-lg-n5,.mx-lg-n5{margin-right:-3rem!important}.mb-lg-n5,.my-lg-n5{margin-bottom:-3rem!important}.ml-lg-n5,.mx-lg-n5{margin-left:-3rem!important}.m-lg-auto{margin:auto!important}.mt-lg-auto,.my-lg-auto{margin-top:auto!important}.mr-lg-auto,.mx-lg-auto{margin-right:auto!important}.mb-lg-auto,.my-lg-auto{margin-bottom:auto!important}.ml-lg-auto,.mx-lg-auto{margin-left:auto!important}}@media (min-width:10px){.m-xl-0{margin:0!important}.mt-xl-0,.my-xl-0{margin-top:0!important}.mr-xl-0,.mx-xl-0{margin-right:0!important}.mb-xl-0,.my-xl-0{margin-bottom:0!important}.ml-xl-0,.mx-xl-0{margin-left:0!important}.m-xl-1{margin:.25rem!important}.mt-xl-1,.my-xl-1{margin-top:.25rem!important}.mr-xl-1,.mx-xl-1{margin-right:.25rem!important}.mb-xl-1,.my-xl-1{margin-bottom:.25rem!important}.ml-xl-1,.mx-xl-1{margin-left:.25rem!important}.m-xl-2{margin:.5rem!important}.mt-xl-2,.my-xl-2{margin-top:.5rem!important}.mr-xl-2,.mx-xl-2{margin-right:.5rem!important}.mb-xl-2,.my-xl-2{margin-bottom:.5rem!important}.ml-xl-2,.mx-xl-2{margin-left:.5rem!important}.m-xl-3{margin:1rem!important}.mt-xl-3,.my-xl-3{margin-top:1rem!important}.mr-xl-3,.mx-xl-3{margin-right:1rem!important}.mb-xl-3,.my-xl-3{margin-bottom:1rem!important}.ml-xl-3,.mx-xl-3{margin-left:1rem!important}.m-xl-4{margin:1.5rem!important}.mt-xl-4,.my-xl-4{margin-top:1.5rem!important}.mr-xl-4,.mx-xl-4{margin-right:1.5rem!important}.mb-xl-4,.my-xl-4{margin-bottom:1.5rem!important}.ml-xl-4,.mx-xl-4{margin-left:1.5rem!important}.m-xl-5{margin:3rem!important}.mt-xl-5,.my-xl-5{margin-top:3rem!important}.mr-xl-5,.mx-xl-5{margin-right:3rem!important}.mb-xl-5,.my-xl-5{margin-bottom:3rem!important}.ml-xl-5,.mx-xl-5{margin-left:3rem!important}.p-xl-0{padding:0!important}.pt-xl-0,.py-xl-0{padding-top:0!important}.pr-xl-0,.px-xl-0{padding-right:0!important}.pb-xl-0,.py-xl-0{padding-bottom:0!important}.pl-xl-0,.px-xl-0{padding-left:0!important}.p-xl-1{padding:.25rem!important}.pt-xl-1,.py-xl-1{padding-top:.25rem!important}.pr-xl-1,.px-xl-1{padding-right:.25rem!important}.pb-xl-1,.py-xl-1{padding-bottom:.25rem!important}.pl-xl-1,.px-xl-1{padding-left:.25rem!important}.p-xl-2{padding:.5rem!important}.pt-xl-2,.py-xl-2{padding-top:.5rem!important}.pr-xl-2,.px-xl-2{padding-right:.5rem!important}.pb-xl-2,.py-xl-2{padding-bottom:.5rem!important}.pl-xl-2,.px-xl-2{padding-left:.5rem!important}.p-xl-3{padding:1rem!important}.pt-xl-3,.py-xl-3{padding-top:1rem!important}.pr-xl-3,.px-xl-3{padding-right:1rem!important}.pb-xl-3,.py-xl-3{padding-bottom:1rem!important}.pl-xl-3,.px-xl-3{padding-left:1rem!important}.p-xl-4{padding:1.5rem!important}.pt-xl-4,.py-xl-4{padding-top:1.5rem!important}.pr-xl-4,.px-xl-4{padding-right:1.5rem!important}.pb-xl-4,.py-xl-4{padding-bottom:1.5rem!important}.pl-xl-4,.px-xl-4{padding-left:1.5rem!important}.p-xl-5{padding:3rem!important}.pt-xl-5,.py-xl-5{padding-top:3rem!important}.pr-xl-5,.px-xl-5{padding-right:3rem!important}.pb-xl-5,.py-xl-5{padding-bottom:3rem!important}.pl-xl-5,.px-xl-5{padding-left:3rem!important}.m-xl-n1{margin:-.25rem!important}.mt-xl-n1,.my-xl-n1{margin-top:-.25rem!important}.mr-xl-n1,.mx-xl-n1{margin-right:-.25rem!important}.mb-xl-n1,.my-xl-n1{margin-bottom:-.25rem!important}.ml-xl-n1,.mx-xl-n1{margin-left:-.25rem!important}.m-xl-n2{margin:-.5rem!important}.mt-xl-n2,.my-xl-n2{margin-top:-.5rem!important}.mr-xl-n2,.mx-xl-n2{margin-right:-.5rem!important}.mb-xl-n2,.my-xl-n2{margin-bottom:-.5rem!important}.ml-xl-n2,.mx-xl-n2{margin-left:-.5rem!important}.m-xl-n3{margin:-1rem!important}.mt-xl-n3,.my-xl-n3{margin-top:-1rem!important}.mr-xl-n3,.mx-xl-n3{margin-right:-1rem!important}.mb-xl-n3,.my-xl-n3{margin-bottom:-1rem!important}.ml-xl-n3,.mx-xl-n3{margin-left:-1rem!important}.m-xl-n4{margin:-1.5rem!important}.mt-xl-n4,.my-xl-n4{margin-top:-1.5rem!important}.mr-xl-n4,.mx-xl-n4{margin-right:-1.5rem!important}.mb-xl-n4,.my-xl-n4{margin-bottom:-1.5rem!important}.ml-xl-n4,.mx-xl-n4{margin-left:-1.5rem!important}.m-xl-n5{margin:-3rem!important}.mt-xl-n5,.my-xl-n5{margin-top:-3rem!important}.mr-xl-n5,.mx-xl-n5{margin-right:-3rem!important}.mb-xl-n5,.my-xl-n5{margin-bottom:-3rem!important}.ml-xl-n5,.mx-xl-n5{margin-left:-3rem!important}.m-xl-auto{margin:auto!important}.mt-xl-auto,.my-xl-auto{margin-top:auto!important}.mr-xl-auto,.mx-xl-auto{margin-right:auto!important}.mb-xl-auto,.my-xl-auto{margin-bottom:auto!important}.ml-xl-auto,.mx-xl-auto{margin-left:auto!important}}.stretched-link:after{background-color:transparent;bottom:0;content:"";left:0;pointer-events:auto;position:absolute;right:0;top:0;z-index:1}.text-monospace{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace!important}.text-justify{text-align:justify!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-left{text-align:left!important}.text-right{text-align:right!important}.text-center{text-align:center!important}@media (min-width:2px){.text-sm-left{text-align:left!important}.text-sm-right{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:8px){.text-md-left{text-align:left!important}.text-md-right{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:9px){.text-lg-left{text-align:left!important}.text-lg-right{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:10px){.text-xl-left{text-align:left!important}.text-xl-right{text-align:right!important}.text-xl-center{text-align:center!important}}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.font-weight-light{font-weight:300!important}.font-weight-lighter{font-weight:lighter!important}.font-weight-normal{font-weight:400!important}.font-weight-bold{font-weight:600!important}.font-weight-bolder{font-weight:bolder!important}.font-italic{font-style:italic!important}.text-white{color:#fff!important}.text-primary{color:#4040c8!important}a.text-primary:focus,a.text-primary:hover{color:#2a2a92!important}.text-secondary{color:#4b5563!important}a.text-secondary:focus,a.text-secondary:hover{color:#2a3037!important}.text-success{color:#059669!important}a.text-success:focus,a.text-success:hover{color:#034c35!important}.text-info{color:#2563eb!important}a.text-info:focus,a.text-info:hover{color:#1043b3!important}.text-warning{color:#d97706!important}a.text-warning:focus,a.text-warning:hover{color:#8f4e04!important}.text-danger{color:#dc2626!important}a.text-danger:focus,a.text-danger:hover{color:#9c1919!important}.text-light{color:#f3f4f6!important}a.text-light:focus,a.text-light:hover{color:#c7ccd5!important}.text-dark{color:#1f2937!important}a.text-dark:focus,a.text-dark:hover{color:#030506!important}.text-body{color:#111827!important}.text-muted{color:#6b7280!important}.text-black-50{color:rgba(0,0,0,.5)!important}.text-white-50{color:hsla(0,0%,100%,.5)!important}.text-hide{background-color:transparent;border:0;color:transparent;font:0/0 a;text-shadow:none}.text-decoration-none{text-decoration:none!important}.text-break{word-wrap:break-word!important;word-break:break-word!important}.text-reset{color:inherit!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}@media print{*,:after,:before{box-shadow:none!important;text-shadow:none!important}a:not(.btn){text-decoration:underline}abbr[title]:after{content:" (" attr(title) ")"}pre{white-space:pre-wrap!important}blockquote,pre{border:1px solid #6b7280}blockquote,img,pre,tr{page-break-inside:avoid}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}@page{size:a3}.container,body{min-width:9px!important}.navbar{display:none}.badge{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #d1d5db!important}.table-dark{color:inherit}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#e5e7eb}.table .thead-dark th{border-color:#e5e7eb;color:inherit}}.vjs-tree{color:#bfc7d5!important;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.vjs-tree .vjs-tree__content{border-left:1px dotted hsla(0,0%,80%,.28)!important}.vjs-tree .vjs-tree__node{cursor:pointer}.vjs-tree .vjs-tree__node:hover{color:#20a0ff}.vjs-tree .vjs-checkbox{left:-30px;position:absolute}.vjs-tree .vjs-value__boolean,.vjs-tree .vjs-value__null,.vjs-tree .vjs-value__number{color:#a291f5!important}.vjs-tree .vjs-value__string{color:#c3e88d!important}.vjs-tree .vjs-key{color:#c3cbd3!important}.hljs-addition,.hljs-attr,.hljs-keyword,.hljs-selector-tag{color:#13ce66}.hljs-bullet,.hljs-meta,.hljs-name,.hljs-string,.hljs-symbol,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable{color:#c3e88d}.hljs-comment,.hljs-deletion,.hljs-quote{color:#bfcbd9}.hljs-literal,.hljs-number,.hljs-title{color:#a291f5!important}body{padding-bottom:20px}.container{max-width:1440px}html{min-width:1140px}[v-cloak]{display:none}svg.icon{height:1rem;width:1rem}.header{border-bottom:1px solid #e5e7eb}.header .logo{color:#374151;text-decoration:none}.header .logo svg{height:1.7rem;width:1.7rem}.sidebar .nav-item a{border-radius:6px;color:#4b5563;margin-bottom:4px;padding:.5rem .75rem}.sidebar .nav-item a svg{fill:#9ca3af;height:1.25rem;margin-right:15px;width:1.25rem}.sidebar .nav-item a.active,.sidebar .nav-item a:hover{background-color:#e5e7eb;color:#4040c8}.sidebar .nav-item a.active svg{fill:#4040c8}.card{border:none;box-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -2px rgba(0,0,0,.1)}.card .bottom-radius{border-bottom-left-radius:6px;border-bottom-right-radius:6px}.card .card-header{background-color:#fff;border-bottom:none;min-height:60px;padding-bottom:.7rem;padding-top:.7rem}.card .card-header .btn-group .btn{padding:.2rem .5rem}.card .card-header .form-control-with-icon{position:relative}.card .card-header .form-control-with-icon .icon-wrapper{align-items:center;bottom:0;display:flex;justify-content:center;left:.75rem;position:absolute;top:0}.card .card-header .form-control-with-icon .icon-wrapper .icon{fill:#6b7280}.card .card-header .form-control-with-icon .form-control{border-radius:9999px;font-size:.875rem;padding-left:2.25rem}.card .table td,.card .table th{padding:.75rem 1.25rem}.card .table th{background-color:#f3f4f6;border-bottom:0;font-size:.875rem;padding:.5rem 1.25rem}.card .table:not(.table-borderless) td{border-top:1px solid #e5e7eb}.card .table.penultimate-column-right td:nth-last-child(2),.card .table.penultimate-column-right th:nth-last-child(2){text-align:right}.card .table td.table-fit,.card .table th.table-fit{white-space:nowrap;width:1%}.fill-text-color{fill:#111827}.fill-danger{fill:#dc2626}.fill-warning{fill:#d97706}.fill-info{fill:#2563eb}.fill-success{fill:#059669}.fill-primary{fill:#4040c8}button:hover .fill-primary{fill:#fff}.btn-outline-primary.active .fill-primary{fill:#f3f4f6}.btn-outline-primary:not(:disabled):not(.disabled).active:focus{box-shadow:none!important}.btn-muted{background:#e5e7eb;color:#4b5563}.btn-muted:focus,.btn-muted:hover{background:#d1d5db;color:#111827}.btn-muted.active{background:#4040c8;color:#fff}.badge-secondary{background:#e5e7eb;color:#4b5563}.badge-success{background:#d1fae5;color:#059669}.badge-info{background:#dbeafe;color:#2563eb}.badge-warning{background:#fef3c7;color:#d97706}.badge-danger{background:#fee2e2;color:#dc2626}.control-action svg{fill:#d1d5db;height:1.2rem;width:1.2rem}.control-action svg:hover{fill:#4f46e5}@keyframes spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.spin{animation:spin 2s linear infinite}.card .nav-pills{background:#fff}.card .nav-pills .nav-link{border-radius:0;color:#4b5563;font-size:.9rem;padding:.75rem 1.25rem}.card .nav-pills .nav-link:focus,.card .nav-pills .nav-link:hover{color:#1f2937}.card .nav-pills .nav-link.active{background:none;border-bottom:2px solid #4f46e5;color:#4f46e5}.list-enter-active:not(.dontanimate){transition:background 1s linear}.list-enter:not(.dontanimate),.list-leave-to:not(.dontanimate){background:#eef2ff}.code-bg .list-enter:not(.dontanimate),.code-bg .list-leave-to:not(.dontanimate){background:#4b5563}#indexScreen td{vertical-align:middle!important}.card-bg-secondary{background:#f3f4f6}.code-bg{background:#292d3e}.disabled-watcher{background:#dc2626;color:#fff;padding:.75rem}.copy-to-clipboard{--tw-text-opacity:1;color:rgb(231 232 242/var(--tw-text-opacity));opacity:.7;outline:2px solid transparent;outline-offset:2px;position:absolute;right:0;top:0;z-index:10} diff --git a/public/vendor/telescope/app.js b/public/vendor/telescope/app.js new file mode 100644 index 000000000..d1c0e7f28 --- /dev/null +++ b/public/vendor/telescope/app.js @@ -0,0 +1,2 @@ +/*! For license information please see app.js.LICENSE.txt */ +(()=>{var t,e={2110:(t,e,n)=>{"use strict";var o=Object.freeze({}),p=Array.isArray;function M(t){return null==t}function b(t){return null!=t}function c(t){return!0===t}function r(t){return"string"==typeof t||"number"==typeof t||"symbol"==typeof t||"boolean"==typeof t}function z(t){return"function"==typeof t}function a(t){return null!==t&&"object"==typeof t}var i=Object.prototype.toString;function O(t){return"[object Object]"===i.call(t)}function s(t){return"[object RegExp]"===i.call(t)}function A(t){var e=parseFloat(String(t));return e>=0&&Math.floor(e)===e&&isFinite(t)}function u(t){return b(t)&&"function"==typeof t.then&&"function"==typeof t.catch}function l(t){return null==t?"":Array.isArray(t)||O(t)&&t.toString===i?JSON.stringify(t,null,2):String(t)}function d(t){var e=parseFloat(t);return isNaN(e)?t:e}function f(t,e){for(var n=Object.create(null),o=t.split(","),p=0;p-1)return t.splice(o,1)}}var v=Object.prototype.hasOwnProperty;function R(t,e){return v.call(t,e)}function m(t){var e=Object.create(null);return function(n){return e[n]||(e[n]=t(n))}}var g=/-(\w)/g,L=m((function(t){return t.replace(g,(function(t,e){return e?e.toUpperCase():""}))})),y=m((function(t){return t.charAt(0).toUpperCase()+t.slice(1)})),_=/\B([A-Z])/g,N=m((function(t){return t.replace(_,"-$1").toLowerCase()}));var E=Function.prototype.bind?function(t,e){return t.bind(e)}:function(t,e){function n(n){var o=arguments.length;return o?o>1?t.apply(e,arguments):t.call(e,n):t.call(e)}return n._length=t.length,n};function T(t,e){e=e||0;for(var n=t.length-e,o=new Array(n);n--;)o[n]=t[n+e];return o}function B(t,e){for(var n in e)t[n]=e[n];return t}function C(t){for(var e={},n=0;n0,tt=Z&&Z.indexOf("edge/")>0;Z&&Z.indexOf("android");var et=Z&&/iphone|ipad|ipod|ios/.test(Z);Z&&/chrome\/\d+/.test(Z),Z&&/phantomjs/.test(Z);var nt,ot=Z&&Z.match(/firefox\/(\d+)/),pt={}.watch,Mt=!1;if(K)try{var bt={};Object.defineProperty(bt,"passive",{get:function(){Mt=!0}}),window.addEventListener("test-passive",null,bt)}catch(t){}var ct=function(){return void 0===nt&&(nt=!K&&void 0!==n.g&&(n.g.process&&"server"===n.g.process.env.VUE_ENV)),nt},rt=K&&window.__VUE_DEVTOOLS_GLOBAL_HOOK__;function zt(t){return"function"==typeof t&&/native code/.test(t.toString())}var at,it="undefined"!=typeof Symbol&&zt(Symbol)&&"undefined"!=typeof Reflect&&zt(Reflect.ownKeys);at="undefined"!=typeof Set&&zt(Set)?Set:function(){function t(){this.set=Object.create(null)}return t.prototype.has=function(t){return!0===this.set[t]},t.prototype.add=function(t){this.set[t]=!0},t.prototype.clear=function(){this.set=Object.create(null)},t}();var Ot=null;function st(t){void 0===t&&(t=null),t||Ot&&Ot._scope.off(),Ot=t,t&&t._scope.on()}var At=function(){function t(t,e,n,o,p,M,b,c){this.tag=t,this.data=e,this.children=n,this.text=o,this.elm=p,this.ns=void 0,this.context=M,this.fnContext=void 0,this.fnOptions=void 0,this.fnScopeId=void 0,this.key=e&&e.key,this.componentOptions=b,this.componentInstance=void 0,this.parent=void 0,this.raw=!1,this.isStatic=!1,this.isRootInsert=!0,this.isComment=!1,this.isCloned=!1,this.isOnce=!1,this.asyncFactory=c,this.asyncMeta=void 0,this.isAsyncPlaceholder=!1}return Object.defineProperty(t.prototype,"child",{get:function(){return this.componentInstance},enumerable:!1,configurable:!0}),t}(),ut=function(t){void 0===t&&(t="");var e=new At;return e.text=t,e.isComment=!0,e};function lt(t){return new At(void 0,void 0,void 0,String(t))}function dt(t){var e=new At(t.tag,t.data,t.children&&t.children.slice(),t.text,t.elm,t.context,t.componentOptions,t.asyncFactory);return e.ns=t.ns,e.isStatic=t.isStatic,e.key=t.key,e.isComment=t.isComment,e.fnContext=t.fnContext,e.fnOptions=t.fnOptions,e.fnScopeId=t.fnScopeId,e.asyncMeta=t.asyncMeta,e.isCloned=!0,e}var ft=0,qt=[],ht=function(){for(var t=0;t0&&(Vt((o=Kt(o,"".concat(e||"","_").concat(n)))[0])&&Vt(a)&&(i[z]=lt(a.text+o[0].text),o.shift()),i.push.apply(i,o)):r(o)?Vt(a)?i[z]=lt(a.text+o):""!==o&&i.push(lt(o)):Vt(o)&&Vt(a)?i[z]=lt(a.text+o.text):(c(t._isVList)&&b(o.tag)&&M(o.key)&&b(e)&&(o.key="__vlist".concat(e,"_").concat(n,"__")),i.push(o)));return i}var Zt=1,Qt=2;function Jt(t,e,n,o,M,i){return(p(n)||r(n))&&(M=o,o=n,n=void 0),c(i)&&(M=Qt),function(t,e,n,o,M){if(b(n)&&b(n.__ob__))return ut();b(n)&&b(n.is)&&(e=n.is);if(!e)return ut();0;p(o)&&z(o[0])&&((n=n||{}).scopedSlots={default:o[0]},o.length=0);M===Qt?o=$t(o):M===Zt&&(o=function(t){for(var e=0;e0,c=e?!!e.$stable:!b,r=e&&e.$key;if(e){if(e._normalized)return e._normalized;if(c&&p&&p!==o&&r===p.$key&&!b&&!p.$hasNormal)return p;for(var z in M={},e)e[z]&&"$"!==z[0]&&(M[z]=he(t,n,z,e[z]))}else M={};for(var a in n)a in M||(M[a]=We(n,a));return e&&Object.isExtensible(e)&&(e._normalized=M),Y(M,"$stable",c),Y(M,"$key",r),Y(M,"$hasNormal",b),M}function he(t,e,n,o){var M=function(){var e=Ot;st(t);var n=arguments.length?o.apply(null,arguments):o({}),M=(n=n&&"object"==typeof n&&!p(n)?[n]:$t(n))&&n[0];return st(e),n&&(!M||1===n.length&&M.isComment&&!fe(M))?void 0:n};return o.proxy&&Object.defineProperty(e,n,{get:M,enumerable:!0,configurable:!0}),M}function We(t,e){return function(){return t[e]}}function ve(t){return{get attrs(){if(!t._attrsProxy){var e=t._attrsProxy={};Y(e,"_v_attr_proxy",!0),Re(e,t.$attrs,o,t,"$attrs")}return t._attrsProxy},get listeners(){t._listenersProxy||Re(t._listenersProxy={},t.$listeners,o,t,"$listeners");return t._listenersProxy},get slots(){return function(t){t._slotsProxy||ge(t._slotsProxy={},t.$scopedSlots);return t._slotsProxy}(t)},emit:E(t.$emit,t),expose:function(e){e&&Object.keys(e).forEach((function(n){return Ut(t,e,n)}))}}}function Re(t,e,n,o,p){var M=!1;for(var b in e)b in t?e[b]!==n[b]&&(M=!0):(M=!0,me(t,b,o,p));for(var b in t)b in e||(M=!0,delete t[b]);return M}function me(t,e,n,o){Object.defineProperty(t,e,{enumerable:!0,configurable:!0,get:function(){return n[o][e]}})}function ge(t,e){for(var n in e)t[n]=e[n];for(var n in t)n in e||delete t[n]}var Le,ye=null;function _e(t,e){return(t.__esModule||it&&"Module"===t[Symbol.toStringTag])&&(t=t.default),a(t)?e.extend(t):t}function Ne(t){if(p(t))for(var e=0;edocument.createEvent("Event").timeStamp&&(Ye=function(){return $e.now()})}var Ve=function(t,e){if(t.post){if(!e.post)return 1}else if(e.post)return-1;return t.id-e.id};function Ke(){var t,e;for(Ge=Ye(),Fe=!0,De.sort(Ve),He=0;HeHe&&De[n].id>t.id;)n--;De.splice(n+1,0,t)}else De.push(t);je||(je=!0,ln(Ke))}}var Qe="watcher";"".concat(Qe," callback"),"".concat(Qe," getter"),"".concat(Qe," cleanup");var Je;var tn=function(){function t(t){void 0===t&&(t=!1),this.detached=t,this.active=!0,this.effects=[],this.cleanups=[],this.parent=Je,!t&&Je&&(this.index=(Je.scopes||(Je.scopes=[])).push(this)-1)}return t.prototype.run=function(t){if(this.active){var e=Je;try{return Je=this,t()}finally{Je=e}}else 0},t.prototype.on=function(){Je=this},t.prototype.off=function(){Je=this.parent},t.prototype.stop=function(t){if(this.active){var e=void 0,n=void 0;for(e=0,n=this.effects.length;e-1)if(M&&!R(p,"default"))b=!1;else if(""===b||b===N(t)){var r=eo(String,p.type);(r<0||c-1:"string"==typeof t?t.split(",").indexOf(e)>-1:!!s(t)&&t.test(e)}function bo(t,e){var n=t.cache,o=t.keys,p=t._vnode;for(var M in n){var b=n[M];if(b){var c=b.name;c&&!e(c)&&co(n,M,o,p)}}}function co(t,e,n,o){var p=t[e];!p||o&&p.tag===o.tag||p.componentInstance.$destroy(),t[e]=null,W(n,e)}!function(t){t.prototype._init=function(t){var e=this;e._uid=Bn++,e._isVue=!0,e.__v_skip=!0,e._scope=new tn(!0),e._scope._vm=!0,t&&t._isComponent?function(t,e){var n=t.$options=Object.create(t.constructor.options),o=e._parentVnode;n.parent=e.parent,n._parentVnode=o;var p=o.componentOptions;n.propsData=p.propsData,n._parentListeners=p.listeners,n._renderChildren=p.children,n._componentTag=p.tag,e.render&&(n.render=e.render,n.staticRenderFns=e.staticRenderFns)}(e,t):e.$options=Vn(Cn(e.constructor),t||{},e),e._renderProxy=e,e._self=e,function(t){var e=t.$options,n=e.parent;if(n&&!e.abstract){for(;n.$options.abstract&&n.$parent;)n=n.$parent;n.$children.push(t)}t.$parent=n,t.$root=n?n.$root:t,t.$children=[],t.$refs={},t._provided=n?n._provided:Object.create(null),t._watcher=null,t._inactive=null,t._directInactive=!1,t._isMounted=!1,t._isDestroyed=!1,t._isBeingDestroyed=!1}(e),function(t){t._events=Object.create(null),t._hasHookEvent=!1;var e=t.$options._parentListeners;e&&Ce(t,e)}(e),function(t){t._vnode=null,t._staticTrees=null;var e=t.$options,n=t.$vnode=e._parentVnode,p=n&&n.context;t.$slots=le(e._renderChildren,p),t.$scopedSlots=n?qe(t.$parent,n.data.scopedSlots,t.$slots):o,t._c=function(e,n,o,p){return Jt(t,e,n,o,p,!1)},t.$createElement=function(e,n,o,p){return Jt(t,e,n,o,p,!0)};var M=n&&n.data;wt(t,"$attrs",M&&M.attrs||o,null,!0),wt(t,"$listeners",e._parentListeners||o,null,!0)}(e),Ie(e,"beforeCreate",void 0,!1),function(t){var e=Tn(t.$options.inject,t);e&&(Et(!1),Object.keys(e).forEach((function(n){wt(t,n,e[n])})),Et(!0))}(e),gn(e),function(t){var e=t.$options.provide;if(e){var n=z(e)?e.call(t):e;if(!a(n))return;for(var o=en(t),p=it?Reflect.ownKeys(n):Object.keys(n),M=0;M1?T(n):n;for(var o=T(arguments,1),p='event handler for "'.concat(t,'"'),M=0,b=n.length;MparseInt(this.max)&&co(e,n[0],n,this._vnode),this.vnodeToCache=null}}},created:function(){this.cache=Object.create(null),this.keys=[]},destroyed:function(){for(var t in this.cache)co(this.cache,t,this.keys)},mounted:function(){var t=this;this.cacheVNode(),this.$watch("include",(function(e){bo(t,(function(t){return Mo(e,t)}))})),this.$watch("exclude",(function(e){bo(t,(function(t){return!Mo(e,t)}))}))},updated:function(){this.cacheVNode()},render:function(){var t=this.$slots.default,e=Ne(t),n=e&&e.componentOptions;if(n){var o=po(n),p=this.include,M=this.exclude;if(p&&(!o||!Mo(p,o))||M&&o&&Mo(M,o))return e;var b=this.cache,c=this.keys,r=null==e.key?n.Ctor.cid+(n.tag?"::".concat(n.tag):""):e.key;b[r]?(e.componentInstance=b[r].componentInstance,W(c,r),c.push(r)):(this.vnodeToCache=e,this.keyToCache=r),e.data.keepAlive=!0}return e||t&&t[0]}},ao={KeepAlive:zo};!function(t){var e={get:function(){return F}};Object.defineProperty(t,"config",e),t.util={warn:Un,extend:B,mergeOptions:Vn,defineReactive:wt},t.set=St,t.delete=Xt,t.nextTick=ln,t.observable=function(t){return Ct(t),t},t.options=Object.create(null),U.forEach((function(e){t.options[e+"s"]=Object.create(null)})),t.options._base=t,B(t.options.components,ao),function(t){t.use=function(t){var e=this._installedPlugins||(this._installedPlugins=[]);if(e.indexOf(t)>-1)return this;var n=T(arguments,1);return n.unshift(this),z(t.install)?t.install.apply(t,n):z(t)&&t.apply(null,n),e.push(t),this}}(t),function(t){t.mixin=function(t){return this.options=Vn(this.options,t),this}}(t),oo(t),function(t){U.forEach((function(e){t[e]=function(t,n){return n?("component"===e&&O(n)&&(n.name=n.name||t,n=this.options._base.extend(n)),"directive"===e&&z(n)&&(n={bind:n,update:n}),this.options[e+"s"][t]=n,n):this.options[e+"s"][t]}}))}(t)}(no),Object.defineProperty(no.prototype,"$isServer",{get:ct}),Object.defineProperty(no.prototype,"$ssrContext",{get:function(){return this.$vnode&&this.$vnode.ssrContext}}),Object.defineProperty(no,"FunctionalRenderContext",{value:wn}),no.version="2.7.14";var io=f("style,class"),Oo=f("input,textarea,option,select,progress"),so=function(t,e,n){return"value"===n&&Oo(t)&&"button"!==e||"selected"===n&&"option"===t||"checked"===n&&"input"===t||"muted"===n&&"video"===t},Ao=f("contenteditable,draggable,spellcheck"),uo=f("events,caret,typing,plaintext-only"),lo=function(t,e){return vo(e)||"false"===e?"false":"contenteditable"===t&&uo(e)?e:"true"},fo=f("allowfullscreen,async,autofocus,autoplay,checked,compact,controls,declare,default,defaultchecked,defaultmuted,defaultselected,defer,disabled,enabled,formnovalidate,hidden,indeterminate,inert,ismap,itemscope,loop,multiple,muted,nohref,noresize,noshade,novalidate,nowrap,open,pauseonexit,readonly,required,reversed,scoped,seamless,selected,sortable,truespeed,typemustmatch,visible"),qo="http://www.w3.org/1999/xlink",ho=function(t){return":"===t.charAt(5)&&"xlink"===t.slice(0,5)},Wo=function(t){return ho(t)?t.slice(6,t.length):""},vo=function(t){return null==t||!1===t};function Ro(t){for(var e=t.data,n=t,o=t;b(o.componentInstance);)(o=o.componentInstance._vnode)&&o.data&&(e=mo(o.data,e));for(;b(n=n.parent);)n&&n.data&&(e=mo(e,n.data));return function(t,e){if(b(t)||b(e))return go(t,Lo(e));return""}(e.staticClass,e.class)}function mo(t,e){return{staticClass:go(t.staticClass,e.staticClass),class:b(t.class)?[t.class,e.class]:e.class}}function go(t,e){return t?e?t+" "+e:t:e||""}function Lo(t){return Array.isArray(t)?function(t){for(var e,n="",o=0,p=t.length;o-1?Qo(t,e,n):fo(e)?vo(n)?t.removeAttribute(e):(n="allowfullscreen"===e&&"EMBED"===t.tagName?"true":e,t.setAttribute(e,n)):Ao(e)?t.setAttribute(e,lo(e,n)):ho(e)?vo(n)?t.removeAttributeNS(qo,Wo(e)):t.setAttributeNS(qo,e,n):Qo(t,e,n)}function Qo(t,e,n){if(vo(n))t.removeAttribute(e);else{if(Q&&!J&&"TEXTAREA"===t.tagName&&"placeholder"===e&&""!==n&&!t.__ieph){var o=function(e){e.stopImmediatePropagation(),t.removeEventListener("input",o)};t.addEventListener("input",o),t.__ieph=!0}t.setAttribute(e,n)}}var Jo={create:Ko,update:Ko};function tp(t,e){var n=e.elm,o=e.data,p=t.data;if(!(M(o.staticClass)&&M(o.class)&&(M(p)||M(p.staticClass)&&M(p.class)))){var c=Ro(e),r=n._transitionClasses;b(r)&&(c=go(c,Lo(r))),c!==n._prevClass&&(n.setAttribute("class",c),n._prevClass=c)}}var ep,np,op,pp,Mp,bp,cp={create:tp,update:tp},rp=/[\w).+\-_$\]]/;function zp(t){var e,n,o,p,M,b=!1,c=!1,r=!1,z=!1,a=0,i=0,O=0,s=0;for(o=0;o=0&&" "===(u=t.charAt(A));A--);u&&rp.test(u)||(z=!0)}}else void 0===p?(s=o+1,p=t.slice(0,o).trim()):l();function l(){(M||(M=[])).push(t.slice(s,o).trim()),s=o+1}if(void 0===p?p=t.slice(0,o).trim():0!==s&&l(),M)for(o=0;o-1?{exp:t.slice(0,pp),key:'"'+t.slice(pp+1)+'"'}:{exp:t,key:null};np=t,pp=Mp=bp=0;for(;!Lp();)yp(op=gp())?Np(op):91===op&&_p(op);return{exp:t.slice(0,Mp),key:t.slice(Mp+1,bp)}}(t);return null===n.key?"".concat(t,"=").concat(e):"$set(".concat(n.exp,", ").concat(n.key,", ").concat(e,")")}function gp(){return np.charCodeAt(++pp)}function Lp(){return pp>=ep}function yp(t){return 34===t||39===t}function _p(t){var e=1;for(Mp=pp;!Lp();)if(yp(t=gp()))Np(t);else if(91===t&&e++,93===t&&e--,0===e){bp=pp;break}}function Np(t){for(var e=t;!Lp()&&(t=gp())!==e;);}var Ep,Tp="__r",Bp="__c";function Cp(t,e,n){var o=Ep;return function p(){null!==e.apply(null,arguments)&&Xp(t,p,n,o)}}var wp=cn&&!(ot&&Number(ot[1])<=53);function Sp(t,e,n,o){if(wp){var p=Ge,M=e;e=M._wrapper=function(t){if(t.target===t.currentTarget||t.timeStamp>=p||t.timeStamp<=0||t.target.ownerDocument!==document)return M.apply(this,arguments)}}Ep.addEventListener(t,e,Mt?{capture:n,passive:o}:n)}function Xp(t,e,n,o){(o||Ep).removeEventListener(t,e._wrapper||e,n)}function xp(t,e){if(!M(t.data.on)||!M(e.data.on)){var n=e.data.on||{},o=t.data.on||{};Ep=e.elm||t.elm,function(t){if(b(t[Tp])){var e=Q?"change":"input";t[e]=[].concat(t[Tp],t[e]||[]),delete t[Tp]}b(t[Bp])&&(t.change=[].concat(t[Bp],t.change||[]),delete t[Bp])}(n),Ht(n,o,Sp,Xp,Cp,e.context),Ep=void 0}}var kp,Ip={create:xp,update:xp,destroy:function(t){return xp(t,Io)}};function Dp(t,e){if(!M(t.data.domProps)||!M(e.data.domProps)){var n,o,p=e.elm,r=t.data.domProps||{},z=e.data.domProps||{};for(n in(b(z.__ob__)||c(z._v_attr_proxy))&&(z=e.data.domProps=B({},z)),r)n in z||(p[n]="");for(n in z){if(o=z[n],"textContent"===n||"innerHTML"===n){if(e.children&&(e.children.length=0),o===r[n])continue;1===p.childNodes.length&&p.removeChild(p.childNodes[0])}if("value"===n&&"PROGRESS"!==p.tagName){p._value=o;var a=M(o)?"":String(o);Pp(p,a)&&(p.value=a)}else if("innerHTML"===n&&No(p.tagName)&&M(p.innerHTML)){(kp=kp||document.createElement("div")).innerHTML="".concat(o,"");for(var i=kp.firstChild;p.firstChild;)p.removeChild(p.firstChild);for(;i.firstChild;)p.appendChild(i.firstChild)}else if(o!==r[n])try{p[n]=o}catch(t){}}}}function Pp(t,e){return!t.composing&&("OPTION"===t.tagName||function(t,e){var n=!0;try{n=document.activeElement!==t}catch(t){}return n&&t.value!==e}(t,e)||function(t,e){var n=t.value,o=t._vModifiers;if(b(o)){if(o.number)return d(n)!==d(e);if(o.trim)return n.trim()!==e.trim()}return n!==e}(t,e))}var Up={create:Dp,update:Dp},jp=m((function(t){var e={},n=/:(.+)/;return t.split(/;(?![^(]*\))/g).forEach((function(t){if(t){var o=t.split(n);o.length>1&&(e[o[0].trim()]=o[1].trim())}})),e}));function Fp(t){var e=Hp(t.style);return t.staticStyle?B(t.staticStyle,e):e}function Hp(t){return Array.isArray(t)?C(t):"string"==typeof t?jp(t):t}var Gp,Yp=/^--/,$p=/\s*!important$/,Vp=function(t,e,n){if(Yp.test(e))t.style.setProperty(e,n);else if($p.test(n))t.style.setProperty(N(e),n.replace($p,""),"important");else{var o=Zp(e);if(Array.isArray(n))for(var p=0,M=n.length;p-1?e.split(tM).forEach((function(e){return t.classList.add(e)})):t.classList.add(e);else{var n=" ".concat(t.getAttribute("class")||""," ");n.indexOf(" "+e+" ")<0&&t.setAttribute("class",(n+e).trim())}}function nM(t,e){if(e&&(e=e.trim()))if(t.classList)e.indexOf(" ")>-1?e.split(tM).forEach((function(e){return t.classList.remove(e)})):t.classList.remove(e),t.classList.length||t.removeAttribute("class");else{for(var n=" ".concat(t.getAttribute("class")||""," "),o=" "+e+" ";n.indexOf(o)>=0;)n=n.replace(o," ");(n=n.trim())?t.setAttribute("class",n):t.removeAttribute("class")}}function oM(t){if(t){if("object"==typeof t){var e={};return!1!==t.css&&B(e,pM(t.name||"v")),B(e,t),e}return"string"==typeof t?pM(t):void 0}}var pM=m((function(t){return{enterClass:"".concat(t,"-enter"),enterToClass:"".concat(t,"-enter-to"),enterActiveClass:"".concat(t,"-enter-active"),leaveClass:"".concat(t,"-leave"),leaveToClass:"".concat(t,"-leave-to"),leaveActiveClass:"".concat(t,"-leave-active")}})),MM=K&&!J,bM="transition",cM="animation",rM="transition",zM="transitionend",aM="animation",iM="animationend";MM&&(void 0===window.ontransitionend&&void 0!==window.onwebkittransitionend&&(rM="WebkitTransition",zM="webkitTransitionEnd"),void 0===window.onanimationend&&void 0!==window.onwebkitanimationend&&(aM="WebkitAnimation",iM="webkitAnimationEnd"));var OM=K?window.requestAnimationFrame?window.requestAnimationFrame.bind(window):setTimeout:function(t){return t()};function sM(t){OM((function(){OM(t)}))}function AM(t,e){var n=t._transitionClasses||(t._transitionClasses=[]);n.indexOf(e)<0&&(n.push(e),eM(t,e))}function uM(t,e){t._transitionClasses&&W(t._transitionClasses,e),nM(t,e)}function lM(t,e,n){var o=fM(t,e),p=o.type,M=o.timeout,b=o.propCount;if(!p)return n();var c=p===bM?zM:iM,r=0,z=function(){t.removeEventListener(c,a),n()},a=function(e){e.target===t&&++r>=b&&z()};setTimeout((function(){r0&&(n=bM,a=b,i=M.length):e===cM?z>0&&(n=cM,a=z,i=r.length):i=(n=(a=Math.max(b,z))>0?b>z?bM:cM:null)?n===bM?M.length:r.length:0,{type:n,timeout:a,propCount:i,hasTransform:n===bM&&dM.test(o[rM+"Property"])}}function qM(t,e){for(;t.length1}function gM(t,e){!0!==e.data.show&&WM(e)}var LM=function(t){var e,n,o={},z=t.modules,a=t.nodeOps;for(e=0;eA?h(t,M(n[d+1])?null:n[d+1].elm,n,s,d,o):s>d&&v(e,i,A)}(i,u,d,n,z):b(d)?(b(t.text)&&a.setTextContent(i,""),h(i,null,d,0,d.length-1,n)):b(u)?v(u,0,u.length-1):b(t.text)&&a.setTextContent(i,""):t.text!==e.text&&a.setTextContent(i,e.text),b(A)&&b(s=A.hook)&&b(s=s.postpatch)&&s(t,e)}}}function L(t,e,n){if(c(n)&&b(t.parent))t.parent.data.pendingInsert=e;else for(var o=0;o-1,b.selected!==M&&(b.selected=M);else if(x(TM(b),o))return void(t.selectedIndex!==c&&(t.selectedIndex=c));p||(t.selectedIndex=-1)}}function EM(t,e){return e.every((function(e){return!x(e,t)}))}function TM(t){return"_value"in t?t._value:t.value}function BM(t){t.target.composing=!0}function CM(t){t.target.composing&&(t.target.composing=!1,wM(t.target,"input"))}function wM(t,e){var n=document.createEvent("HTMLEvents");n.initEvent(e,!0,!0),t.dispatchEvent(n)}function SM(t){return!t.componentInstance||t.data&&t.data.transition?t:SM(t.componentInstance._vnode)}var XM={bind:function(t,e,n){var o=e.value,p=(n=SM(n)).data&&n.data.transition,M=t.__vOriginalDisplay="none"===t.style.display?"":t.style.display;o&&p?(n.data.show=!0,WM(n,(function(){t.style.display=M}))):t.style.display=o?M:"none"},update:function(t,e,n){var o=e.value;!o!=!e.oldValue&&((n=SM(n)).data&&n.data.transition?(n.data.show=!0,o?WM(n,(function(){t.style.display=t.__vOriginalDisplay})):vM(n,(function(){t.style.display="none"}))):t.style.display=o?t.__vOriginalDisplay:"none")},unbind:function(t,e,n,o,p){p||(t.style.display=t.__vOriginalDisplay)}},xM={model:yM,show:XM},kM={name:String,appear:Boolean,css:Boolean,mode:String,type:String,enterClass:String,leaveClass:String,enterToClass:String,leaveToClass:String,enterActiveClass:String,leaveActiveClass:String,appearClass:String,appearActiveClass:String,appearToClass:String,duration:[Number,String,Object]};function IM(t){var e=t&&t.componentOptions;return e&&e.Ctor.options.abstract?IM(Ne(e.children)):t}function DM(t){var e={},n=t.$options;for(var o in n.propsData)e[o]=t[o];var p=n._parentListeners;for(var o in p)e[L(o)]=p[o];return e}function PM(t,e){if(/\d-keep-alive$/.test(e.tag))return t("keep-alive",{props:e.componentOptions.propsData})}var UM=function(t){return t.tag||fe(t)},jM=function(t){return"show"===t.name},FM={name:"transition",props:kM,abstract:!0,render:function(t){var e=this,n=this.$slots.default;if(n&&(n=n.filter(UM)).length){0;var o=this.mode;0;var p=n[0];if(function(t){for(;t=t.parent;)if(t.data.transition)return!0}(this.$vnode))return p;var M=IM(p);if(!M)return p;if(this._leaving)return PM(t,p);var b="__transition-".concat(this._uid,"-");M.key=null==M.key?M.isComment?b+"comment":b+M.tag:r(M.key)?0===String(M.key).indexOf(b)?M.key:b+M.key:M.key;var c=(M.data||(M.data={})).transition=DM(this),z=this._vnode,a=IM(z);if(M.data.directives&&M.data.directives.some(jM)&&(M.data.show=!0),a&&a.data&&!function(t,e){return e.key===t.key&&e.tag===t.tag}(M,a)&&!fe(a)&&(!a.componentInstance||!a.componentInstance._vnode.isComment)){var i=a.data.transition=B({},c);if("out-in"===o)return this._leaving=!0,Gt(i,"afterLeave",(function(){e._leaving=!1,e.$forceUpdate()})),PM(t,p);if("in-out"===o){if(fe(M))return z;var O,s=function(){O()};Gt(c,"afterEnter",s),Gt(c,"enterCancelled",s),Gt(i,"delayLeave",(function(t){O=t}))}}return p}}},HM=B({tag:String,moveClass:String},kM);delete HM.mode;var GM={props:HM,beforeMount:function(){var t=this,e=this._update;this._update=function(n,o){var p=Se(t);t.__patch__(t._vnode,t.kept,!1,!0),t._vnode=t.kept,p(),e.call(t,n,o)}},render:function(t){for(var e=this.tag||this.$vnode.data.tag||"span",n=Object.create(null),o=this.prevChildren=this.children,p=this.$slots.default||[],M=this.children=[],b=DM(this),c=0;c-1?Bo[t]=e.constructor===window.HTMLUnknownElement||e.constructor===window.HTMLElement:Bo[t]=/HTMLUnknownElement/.test(e.toString())},B(no.options.directives,xM),B(no.options.components,KM),no.prototype.__patch__=K?LM:w,no.prototype.$mount=function(t,e){return function(t,e,n){var o;t.$el=e,t.$options.render||(t.$options.render=ut),Ie(t,"beforeMount"),o=function(){t._update(t._render(),n)},new vn(t,o,w,{before:function(){t._isMounted&&!t._isDestroyed&&Ie(t,"beforeUpdate")}},!0),n=!1;var p=t._preWatchers;if(p)for(var M=0;M\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/,rb=/^\s*((?:v-[\w-]+:|@|:|#)\[[^=]+?\][^\s"'<>\/=]*)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/,zb="[a-zA-Z_][\\-\\.0-9_a-zA-Z".concat(H.source,"]*"),ab="((?:".concat(zb,"\\:)?").concat(zb,")"),ib=new RegExp("^<".concat(ab)),Ob=/^\s*(\/?)>/,sb=new RegExp("^<\\/".concat(ab,"[^>]*>")),Ab=/^]+>/i,ub=/^",""":'"',"&":"&"," ":"\n"," ":"\t","'":"'"},hb=/&(?:lt|gt|quot|amp|#39);/g,Wb=/&(?:lt|gt|quot|amp|#39|#10|#9);/g,vb=f("pre,textarea",!0),Rb=function(t,e){return t&&vb(t)&&"\n"===e[0]};function mb(t,e){var n=e?Wb:hb;return t.replace(n,(function(t){return qb[t]}))}function gb(t,e){for(var n,o,p=[],M=e.expectHTML,b=e.isUnaryTag||S,c=e.canBeLeftOpenTag||S,r=0,z=function(){if(n=t,o&&db(o)){var z=0,O=o.toLowerCase(),s=fb[O]||(fb[O]=new RegExp("([\\s\\S]*?)(]*>)","i"));v=t.replace(s,(function(t,n,o){return z=o.length,db(O)||"noscript"===O||(n=n.replace(//g,"$1").replace(//g,"$1")),Rb(O,n)&&(n=n.slice(1)),e.chars&&e.chars(n),""}));r+=t.length-v.length,t=v,i(O,r-z,r)}else{var A=t.indexOf("<");if(0===A){if(ub.test(t)){var u=t.indexOf("--\x3e");if(u>=0)return e.shouldKeepComment&&e.comment&&e.comment(t.substring(4,u),r,r+u+3),a(u+3),"continue"}if(lb.test(t)){var l=t.indexOf("]>");if(l>=0)return a(l+2),"continue"}var d=t.match(Ab);if(d)return a(d[0].length),"continue";var f=t.match(sb);if(f){var q=r;return a(f[0].length),i(f[1],q,r),"continue"}var h=function(){var e=t.match(ib);if(e){var n={tagName:e[1],attrs:[],start:r};a(e[0].length);for(var o=void 0,p=void 0;!(o=t.match(Ob))&&(p=t.match(rb)||t.match(cb));)p.start=r,a(p[0].length),p.end=r,n.attrs.push(p);if(o)return n.unarySlash=o[1],a(o[0].length),n.end=r,n}}();if(h)return function(t){var n=t.tagName,r=t.unarySlash;M&&("p"===o&&bb(n)&&i(o),c(n)&&o===n&&i(n));for(var z=b(n)||!!r,a=t.attrs.length,O=new Array(a),s=0;s=0){for(v=t.slice(A);!(sb.test(v)||ib.test(v)||ub.test(v)||lb.test(v)||(R=v.indexOf("<",1))<0);)A+=R,v=t.slice(A);W=t.substring(0,A)}A<0&&(W=t),W&&a(W.length),e.chars&&W&&e.chars(W,r-W.length,r)}if(t===n)return e.chars&&e.chars(t),"break"};t;){if("break"===z())break}function a(e){r+=e,t=t.substring(e)}function i(t,n,M){var b,c;if(null==n&&(n=r),null==M&&(M=r),t)for(c=t.toLowerCase(),b=p.length-1;b>=0&&p[b].lowerCasedTag!==c;b--);else b=0;if(b>=0){for(var z=p.length-1;z>=b;z--)e.end&&e.end(p[z].tag,n,M);p.length=b,o=b&&p[b-1].tag}else"br"===c?e.start&&e.start(t,[],!0,n,M):"p"===c&&(e.start&&e.start(t,[],!1,n,M),e.end&&e.end(t,n,M))}i()}var Lb,yb,_b,Nb,Eb,Tb,Bb,Cb,wb=/^@|^v-on:/,Sb=/^v-|^@|^:|^#/,Xb=/([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/,xb=/,([^,\}\]]*)(?:,([^,\}\]]*))?$/,kb=/^\(|\)$/g,Ib=/^\[.*\]$/,Db=/:(.*)$/,Pb=/^:|^\.|^v-bind:/,Ub=/\.[^.\]]+(?=[^\]]*$)/g,jb=/^v-slot(:|$)|^#/,Fb=/[\r\n]/,Hb=/[ \f\t\r\n]+/g,Gb=m(ob),Yb="_empty_";function $b(t,e,n){return{type:1,tag:t,attrsList:e,attrsMap:ec(e),rawAttrsMap:{},parent:n,children:[]}}function Vb(t,e){Lb=e.warn||ip,Tb=e.isPreTag||S,Bb=e.mustUseProp||S,Cb=e.getTagNamespace||S;var n=e.isReservedTag||S;(function(t){return!(!(t.component||t.attrsMap[":is"]||t.attrsMap["v-bind:is"])&&(t.attrsMap.is?n(t.attrsMap.is):n(t.tag)))}),_b=Op(e.modules,"transformNode"),Nb=Op(e.modules,"preTransformNode"),Eb=Op(e.modules,"postTransformNode"),yb=e.delimiters;var o,p,M=[],b=!1!==e.preserveWhitespace,c=e.whitespace,r=!1,z=!1;function a(t){if(i(t),r||t.processed||(t=Kb(t,e)),M.length||t===o||o.if&&(t.elseif||t.else)&&Qb(o,{exp:t.elseif,block:t}),p&&!t.forbidden)if(t.elseif||t.else)b=t,c=function(t){for(var e=t.length;e--;){if(1===t[e].type)return t[e];t.pop()}}(p.children),c&&c.if&&Qb(c,{exp:b.elseif,block:b});else{if(t.slotScope){var n=t.slotTarget||'"default"';(p.scopedSlots||(p.scopedSlots={}))[n]=t}p.children.push(t),t.parent=p}var b,c;t.children=t.children.filter((function(t){return!t.slotScope})),i(t),t.pre&&(r=!1),Tb(t.tag)&&(z=!1);for(var a=0;ar&&(c.push(M=t.slice(r,p)),b.push(JSON.stringify(M)));var z=zp(o[1].trim());b.push("_s(".concat(z,")")),c.push({"@binding":z}),r=p+o[0].length}return r-1")+("true"===M?":(".concat(e,")"):":_q(".concat(e,",").concat(M,")"))),fp(t,"change","var $$a=".concat(e,",")+"$$el=$event.target,"+"$$c=$$el.checked?(".concat(M,"):(").concat(b,");")+"if(Array.isArray($$a)){"+"var $$v=".concat(o?"_n("+p+")":p,",")+"$$i=_i($$a,$$v);"+"if($$el.checked){$$i<0&&(".concat(mp(e,"$$a.concat([$$v])"),")}")+"else{$$i>-1&&(".concat(mp(e,"$$a.slice(0,$$i).concat($$a.slice($$i+1))"),")}")+"}else{".concat(mp(e,"$$c"),"}"),null,!0)}(t,o,p);else if("input"===M&&"radio"===b)!function(t,e,n){var o=n&&n.number,p=qp(t,"value")||"null";p=o?"_n(".concat(p,")"):p,sp(t,"checked","_q(".concat(e,",").concat(p,")")),fp(t,"change",mp(e,p),null,!0)}(t,o,p);else if("input"===M||"textarea"===M)!function(t,e,n){var o=t.attrsMap.type;0;var p=n||{},M=p.lazy,b=p.number,c=p.trim,r=!M&&"range"!==o,z=M?"change":"range"===o?Tp:"input",a="$event.target.value";c&&(a="$event.target.value.trim()");b&&(a="_n(".concat(a,")"));var i=mp(e,a);r&&(i="if($event.target.composing)return;".concat(i));sp(t,"value","(".concat(e,")")),fp(t,z,i,null,!0),(c||b)&&fp(t,"blur","$forceUpdate()")}(t,o,p);else{if(!F.isReservedTag(M))return Rp(t,o,p),!1}return!0},text:function(t,e){e.value&&sp(t,"textContent","_s(".concat(e.value,")"),e)},html:function(t,e){e.value&&sp(t,"innerHTML","_s(".concat(e.value,")"),e)}},ac={expectHTML:!0,modules:bc,directives:zc,isPreTag:function(t){return"pre"===t},isUnaryTag:pb,mustUseProp:so,canBeLeftOpenTag:Mb,isReservedTag:Eo,getTagNamespace:To,staticKeys:function(t){return t.reduce((function(t,e){return t.concat(e.staticKeys||[])}),[]).join(",")}(bc)},ic=m((function(t){return f("type,tag,attrsList,attrsMap,plain,parent,children,attrs,start,end,rawAttrsMap"+(t?","+t:""))}));function Oc(t,e){t&&(cc=ic(e.staticKeys||""),rc=e.isReservedTag||S,sc(t),Ac(t,!1))}function sc(t){if(t.static=function(t){if(2===t.type)return!1;if(3===t.type)return!0;return!(!t.pre&&(t.hasBindings||t.if||t.for||q(t.tag)||!rc(t.tag)||function(t){for(;t.parent;){if("template"!==(t=t.parent).tag)return!1;if(t.for)return!0}return!1}(t)||!Object.keys(t).every(cc)))}(t),1===t.type){if(!rc(t.tag)&&"slot"!==t.tag&&null==t.attrsMap["inline-template"])return;for(var e=0,n=t.children.length;e|^function(?:\s+[\w$]+)?\s*\(/,lc=/\([^)]*?\);*$/,dc=/^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\['[^']*?']|\["[^"]*?"]|\[\d+]|\[[A-Za-z_$][\w$]*])*$/,fc={esc:27,tab:9,enter:13,space:32,up:38,left:37,right:39,down:40,delete:[8,46]},qc={esc:["Esc","Escape"],tab:"Tab",enter:"Enter",space:[" ","Spacebar"],up:["Up","ArrowUp"],left:["Left","ArrowLeft"],right:["Right","ArrowRight"],down:["Down","ArrowDown"],delete:["Backspace","Delete","Del"]},hc=function(t){return"if(".concat(t,")return null;")},Wc={stop:"$event.stopPropagation();",prevent:"$event.preventDefault();",self:hc("$event.target !== $event.currentTarget"),ctrl:hc("!$event.ctrlKey"),shift:hc("!$event.shiftKey"),alt:hc("!$event.altKey"),meta:hc("!$event.metaKey"),left:hc("'button' in $event && $event.button !== 0"),middle:hc("'button' in $event && $event.button !== 1"),right:hc("'button' in $event && $event.button !== 2")};function vc(t,e){var n=e?"nativeOn:":"on:",o="",p="";for(var M in t){var b=Rc(t[M]);t[M]&&t[M].dynamic?p+="".concat(M,",").concat(b,","):o+='"'.concat(M,'":').concat(b,",")}return o="{".concat(o.slice(0,-1),"}"),p?n+"_d(".concat(o,",[").concat(p.slice(0,-1),"])"):n+o}function Rc(t){if(!t)return"function(){}";if(Array.isArray(t))return"[".concat(t.map((function(t){return Rc(t)})).join(","),"]");var e=dc.test(t.value),n=uc.test(t.value),o=dc.test(t.value.replace(lc,""));if(t.modifiers){var p="",M="",b=[],c=function(e){if(Wc[e])M+=Wc[e],fc[e]&&b.push(e);else if("exact"===e){var n=t.modifiers;M+=hc(["ctrl","shift","alt","meta"].filter((function(t){return!n[t]})).map((function(t){return"$event.".concat(t,"Key")})).join("||"))}else b.push(e)};for(var r in t.modifiers)c(r);b.length&&(p+=function(t){return"if(!$event.type.indexOf('key')&&"+"".concat(t.map(mc).join("&&"),")return null;")}(b)),M&&(p+=M);var z=e?"return ".concat(t.value,".apply(null, arguments)"):n?"return (".concat(t.value,").apply(null, arguments)"):o?"return ".concat(t.value):t.value;return"function($event){".concat(p).concat(z,"}")}return e||n?t.value:"function($event){".concat(o?"return ".concat(t.value):t.value,"}")}function mc(t){var e=parseInt(t,10);if(e)return"$event.keyCode!==".concat(e);var n=fc[t],o=qc[t];return"_k($event.keyCode,"+"".concat(JSON.stringify(t),",")+"".concat(JSON.stringify(n),",")+"$event.key,"+"".concat(JSON.stringify(o))+")"}var gc={on:function(t,e){t.wrapListeners=function(t){return"_g(".concat(t,",").concat(e.value,")")}},bind:function(t,e){t.wrapData=function(n){return"_b(".concat(n,",'").concat(t.tag,"',").concat(e.value,",").concat(e.modifiers&&e.modifiers.prop?"true":"false").concat(e.modifiers&&e.modifiers.sync?",true":"",")")}},cloak:w},Lc=function(t){this.options=t,this.warn=t.warn||ip,this.transforms=Op(t.modules,"transformCode"),this.dataGenFns=Op(t.modules,"genData"),this.directives=B(B({},gc),t.directives);var e=t.isReservedTag||S;this.maybeComponent=function(t){return!!t.component||!e(t.tag)},this.onceId=0,this.staticRenderFns=[],this.pre=!1};function yc(t,e){var n=new Lc(e),o=t?"script"===t.tag?"null":_c(t,n):'_c("div")';return{render:"with(this){return ".concat(o,"}"),staticRenderFns:n.staticRenderFns}}function _c(t,e){if(t.parent&&(t.pre=t.pre||t.parent.pre),t.staticRoot&&!t.staticProcessed)return Nc(t,e);if(t.once&&!t.onceProcessed)return Ec(t,e);if(t.for&&!t.forProcessed)return Cc(t,e);if(t.if&&!t.ifProcessed)return Tc(t,e);if("template"!==t.tag||t.slotTarget||e.pre){if("slot"===t.tag)return function(t,e){var n=t.slotName||'"default"',o=xc(t,e),p="_t(".concat(n).concat(o?",function(){return ".concat(o,"}"):""),M=t.attrs||t.dynamicAttrs?Dc((t.attrs||[]).concat(t.dynamicAttrs||[]).map((function(t){return{name:L(t.name),value:t.value,dynamic:t.dynamic}}))):null,b=t.attrsMap["v-bind"];!M&&!b||o||(p+=",null");M&&(p+=",".concat(M));b&&(p+="".concat(M?"":",null",",").concat(b));return p+")"}(t,e);var n=void 0;if(t.component)n=function(t,e,n){var o=e.inlineTemplate?null:xc(e,n,!0);return"_c(".concat(t,",").concat(wc(e,n)).concat(o?",".concat(o):"",")")}(t.component,t,e);else{var o=void 0,p=e.maybeComponent(t);(!t.plain||t.pre&&p)&&(o=wc(t,e));var M=void 0,b=e.options.bindings;p&&b&&!1!==b.__isScriptSetup&&(M=function(t,e){var n=L(e),o=y(n),p=function(p){return t[e]===p?e:t[n]===p?n:t[o]===p?o:void 0},M=p("setup-const")||p("setup-reactive-const");if(M)return M;var b=p("setup-let")||p("setup-ref")||p("setup-maybe-ref");if(b)return b}(b,t.tag)),M||(M="'".concat(t.tag,"'"));var c=t.inlineTemplate?null:xc(t,e,!0);n="_c(".concat(M).concat(o?",".concat(o):"").concat(c?",".concat(c):"",")")}for(var r=0;r>>0}(b)):"",")")}(t,t.scopedSlots,e),",")),t.model&&(n+="model:{value:".concat(t.model.value,",callback:").concat(t.model.callback,",expression:").concat(t.model.expression,"},")),t.inlineTemplate){var M=function(t,e){var n=t.children[0];0;if(n&&1===n.type){var o=yc(n,e.options);return"inlineTemplate:{render:function(){".concat(o.render,"},staticRenderFns:[").concat(o.staticRenderFns.map((function(t){return"function(){".concat(t,"}")})).join(","),"]}")}}(t,e);M&&(n+="".concat(M,","))}return n=n.replace(/,$/,"")+"}",t.dynamicAttrs&&(n="_b(".concat(n,',"').concat(t.tag,'",').concat(Dc(t.dynamicAttrs),")")),t.wrapData&&(n=t.wrapData(n)),t.wrapListeners&&(n=t.wrapListeners(n)),n}function Sc(t){return 1===t.type&&("slot"===t.tag||t.children.some(Sc))}function Xc(t,e){var n=t.attrsMap["slot-scope"];if(t.if&&!t.ifProcessed&&!n)return Tc(t,e,Xc,"null");if(t.for&&!t.forProcessed)return Cc(t,e,Xc);var o=t.slotScope===Yb?"":String(t.slotScope),p="function(".concat(o,"){")+"return ".concat("template"===t.tag?t.if&&n?"(".concat(t.if,")?").concat(xc(t,e)||"undefined",":undefined"):xc(t,e)||"undefined":_c(t,e),"}"),M=o?"":",proxy:true";return"{key:".concat(t.slotTarget||'"default"',",fn:").concat(p).concat(M,"}")}function xc(t,e,n,o,p){var M=t.children;if(M.length){var b=M[0];if(1===M.length&&b.for&&"template"!==b.tag&&"slot"!==b.tag){var c=n?e.maybeComponent(b)?",1":",0":"";return"".concat((o||_c)(b,e)).concat(c)}var r=n?function(t,e){for(var n=0,o=0;o':'
',Hc.innerHTML.indexOf(" ")>0}var Vc=!!K&&$c(!1),Kc=!!K&&$c(!0),Zc=m((function(t){var e=wo(t);return e&&e.innerHTML})),Qc=no.prototype.$mount;no.prototype.$mount=function(t,e){if((t=t&&wo(t))===document.body||t===document.documentElement)return this;var n=this.$options;if(!n.render){var o=n.template;if(o)if("string"==typeof o)"#"===o.charAt(0)&&(o=Zc(o));else{if(!o.nodeType)return this;o=o.innerHTML}else t&&(o=function(t){if(t.outerHTML)return t.outerHTML;var e=document.createElement("div");return e.appendChild(t.cloneNode(!0)),e.innerHTML}(t));if(o){0;var p=Yc(o,{outputSourceRange:!1,shouldDecodeNewlines:Vc,shouldDecodeNewlinesForHref:Kc,delimiters:n.delimiters,comments:n.comments},this),M=p.render,b=p.staticRenderFns;n.render=M,n.staticRenderFns=b}}return Qc.call(this,t,e)},no.compile=Yc;var Jc=n(6486),tr=n.n(Jc),er=n(8),nr=n.n(er);const or={computed:{Telescope:function(t){function e(){return t.apply(this,arguments)}return e.toString=function(){return t.toString()},e}((function(){return Telescope}))},methods:{timeAgo:function(t){nr().updateLocale("en",{relativeTime:{future:"in %s",past:"%s ago",s:function(t){return t+"s ago"},ss:"%ds ago",m:"1m ago",mm:"%dm ago",h:"1h ago",hh:"%dh ago",d:"1d ago",dd:"%dd ago",M:"a month ago",MM:"%d months ago",y:"a year ago",yy:"%d years ago"}});var e=nr()().diff(t,"seconds"),n=nr()("2018-01-01").startOf("day").seconds(e);return e>300?nr()(t).fromNow(!0):e<60?n.format("s")+"s ago":n.format("m:ss")+"m ago"},localTime:function(t){return nr()(t).local().format("MMMM Do YYYY, h:mm:ss A")},truncate:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:70;return tr().truncate(t,{length:e,separator:/,? +/})},debouncer:tr().debounce((function(t){return t()}),500),alertError:function(t){this.$root.alert.type="error",this.$root.alert.autoClose=!1,this.$root.alert.message=t},alertSuccess:function(t,e){this.$root.alert.type="success",this.$root.alert.autoClose=e,this.$root.alert.message=t},alertConfirm:function(t,e,n){this.$root.alert.type="confirmation",this.$root.alert.autoClose=!1,this.$root.alert.message=t,this.$root.alert.confirmationProceed=e,this.$root.alert.confirmationCancel=n}}};var pr=n(7066);const Mr=[{path:"/",redirect:"/requests"},{path:"/mail/:id",name:"mail-preview",component:n(7776).Z},{path:"/mail",name:"mail",component:n(4456).Z},{path:"/exceptions/:id",name:"exception-preview",component:n(8882).Z},{path:"/exceptions",name:"exceptions",component:n(5323).Z},{path:"/dumps",name:"dumps",component:n(7208).Z},{path:"/logs/:id",name:"log-preview",component:n(8360).Z},{path:"/logs",name:"logs",component:n(1929).Z},{path:"/notifications/:id",name:"notification-preview",component:n(3590).Z},{path:"/notifications",name:"notifications",component:n(624).Z},{path:"/jobs/:id",name:"job-preview",component:n(4142).Z},{path:"/jobs",name:"jobs",component:n(558).Z},{path:"/batches/:id",name:"batch-preview",component:n(8159).Z},{path:"/batches",name:"batches",component:n(7374).Z},{path:"/events/:id",name:"event-preview",component:n(5701).Z},{path:"/events",name:"events",component:n(8814).Z},{path:"/cache/:id",name:"cache-preview",component:n(2246).Z},{path:"/cache",name:"cache",component:n(896).Z},{path:"/queries/:id",name:"query-preview",component:n(3992).Z},{path:"/queries",name:"queries",component:n(4652).Z},{path:"/models/:id",name:"model-preview",component:n(706).Z},{path:"/models",name:"models",component:n(1556).Z},{path:"/requests/:id",name:"request-preview",component:n(1619).Z},{path:"/requests",name:"requests",component:n(9751).Z},{path:"/commands/:id",name:"command-preview",component:n(1241).Z},{path:"/commands",name:"commands",component:n(7210).Z},{path:"/schedule/:id",name:"schedule-preview",component:n(4622).Z},{path:"/schedule",name:"schedule",component:n(8244).Z},{path:"/redis/:id",name:"redis-preview",component:n(5799).Z},{path:"/redis",name:"redis",component:n(7837).Z},{path:"/monitored-tags",name:"monitored-tags",component:n(5505).Z},{path:"/gates/:id",name:"gate-preview",component:n(6581).Z},{path:"/gates",name:"gates",component:n(4840).Z},{path:"/views/:id",name:"view-preview",component:n(6968).Z},{path:"/views",name:"views",component:n(3395).Z},{path:"/client-requests/:id",name:"client-request-preview",component:n(9101).Z},{path:"/client-requests",name:"client-requests",component:n(2935).Z}];function br(t,e){for(var n in e)t[n]=e[n];return t}var cr=/[!'()*]/g,rr=function(t){return"%"+t.charCodeAt(0).toString(16)},zr=/%2C/g,ar=function(t){return encodeURIComponent(t).replace(cr,rr).replace(zr,",")};function ir(t){try{return decodeURIComponent(t)}catch(t){0}return t}var Or=function(t){return null==t||"object"==typeof t?t:String(t)};function sr(t){var e={};return(t=t.trim().replace(/^(\?|#|&)/,""))?(t.split("&").forEach((function(t){var n=t.replace(/\+/g," ").split("="),o=ir(n.shift()),p=n.length>0?ir(n.join("=")):null;void 0===e[o]?e[o]=p:Array.isArray(e[o])?e[o].push(p):e[o]=[e[o],p]})),e):e}function Ar(t){var e=t?Object.keys(t).map((function(e){var n=t[e];if(void 0===n)return"";if(null===n)return ar(e);if(Array.isArray(n)){var o=[];return n.forEach((function(t){void 0!==t&&(null===t?o.push(ar(e)):o.push(ar(e)+"="+ar(t)))})),o.join("&")}return ar(e)+"="+ar(n)})).filter((function(t){return t.length>0})).join("&"):null;return e?"?"+e:""}var ur=/\/?$/;function lr(t,e,n,o){var p=o&&o.options.stringifyQuery,M=e.query||{};try{M=dr(M)}catch(t){}var b={name:e.name||t&&t.name,meta:t&&t.meta||{},path:e.path||"/",hash:e.hash||"",query:M,params:e.params||{},fullPath:hr(e,p),matched:t?qr(t):[]};return n&&(b.redirectedFrom=hr(n,p)),Object.freeze(b)}function dr(t){if(Array.isArray(t))return t.map(dr);if(t&&"object"==typeof t){var e={};for(var n in t)e[n]=dr(t[n]);return e}return t}var fr=lr(null,{path:"/"});function qr(t){for(var e=[];t;)e.unshift(t),t=t.parent;return e}function hr(t,e){var n=t.path,o=t.query;void 0===o&&(o={});var p=t.hash;return void 0===p&&(p=""),(n||"/")+(e||Ar)(o)+p}function Wr(t,e,n){return e===fr?t===e:!!e&&(t.path&&e.path?t.path.replace(ur,"")===e.path.replace(ur,"")&&(n||t.hash===e.hash&&vr(t.query,e.query)):!(!t.name||!e.name)&&(t.name===e.name&&(n||t.hash===e.hash&&vr(t.query,e.query)&&vr(t.params,e.params))))}function vr(t,e){if(void 0===t&&(t={}),void 0===e&&(e={}),!t||!e)return t===e;var n=Object.keys(t).sort(),o=Object.keys(e).sort();return n.length===o.length&&n.every((function(n,p){var M=t[n];if(o[p]!==n)return!1;var b=e[n];return null==M||null==b?M===b:"object"==typeof M&&"object"==typeof b?vr(M,b):String(M)===String(b)}))}function Rr(t){for(var e=0;e=0&&(e=t.slice(o),t=t.slice(0,o));var p=t.indexOf("?");return p>=0&&(n=t.slice(p+1),t=t.slice(0,p)),{path:t,query:n,hash:e}}(p.path||""),z=e&&e.path||"/",a=r.path?Lr(r.path,z,n||p.append):z,i=function(t,e,n){void 0===e&&(e={});var o,p=n||sr;try{o=p(t||"")}catch(t){o={}}for(var M in e){var b=e[M];o[M]=Array.isArray(b)?b.map(Or):Or(b)}return o}(r.query,p.query,o&&o.options.parseQuery),O=p.hash||r.hash;return O&&"#"!==O.charAt(0)&&(O="#"+O),{_normalized:!0,path:a,query:i,hash:O}}var $r,Vr=function(){},Kr={name:"RouterLink",props:{to:{type:[String,Object],required:!0},tag:{type:String,default:"a"},custom:Boolean,exact:Boolean,exactPath:Boolean,append:Boolean,replace:Boolean,activeClass:String,exactActiveClass:String,ariaCurrentValue:{type:String,default:"page"},event:{type:[String,Array],default:"click"}},render:function(t){var e=this,n=this.$router,o=this.$route,p=n.resolve(this.to,o,this.append),M=p.location,b=p.route,c=p.href,r={},z=n.options.linkActiveClass,a=n.options.linkExactActiveClass,i=null==z?"router-link-active":z,O=null==a?"router-link-exact-active":a,s=null==this.activeClass?i:this.activeClass,A=null==this.exactActiveClass?O:this.exactActiveClass,u=b.redirectedFrom?lr(null,Yr(b.redirectedFrom),null,n):b;r[A]=Wr(o,u,this.exactPath),r[s]=this.exact||this.exactPath?r[A]:function(t,e){return 0===t.path.replace(ur,"/").indexOf(e.path.replace(ur,"/"))&&(!e.hash||t.hash===e.hash)&&function(t,e){for(var n in e)if(!(n in t))return!1;return!0}(t.query,e.query)}(o,u);var l=r[A]?this.ariaCurrentValue:null,d=function(t){Zr(t)&&(e.replace?n.replace(M,Vr):n.push(M,Vr))},f={click:Zr};Array.isArray(this.event)?this.event.forEach((function(t){f[t]=d})):f[this.event]=d;var q={class:r},h=!this.$scopedSlots.$hasNormal&&this.$scopedSlots.default&&this.$scopedSlots.default({href:c,route:b,navigate:d,isActive:r[s],isExactActive:r[A]});if(h){if(1===h.length)return h[0];if(h.length>1||!h.length)return 0===h.length?t():t("span",{},h)}if("a"===this.tag)q.on=f,q.attrs={href:c,"aria-current":l};else{var W=Qr(this.$slots.default);if(W){W.isStatic=!1;var v=W.data=br({},W.data);for(var R in v.on=v.on||{},v.on){var m=v.on[R];R in f&&(v.on[R]=Array.isArray(m)?m:[m])}for(var g in f)g in v.on?v.on[g].push(f[g]):v.on[g]=d;var L=W.data.attrs=br({},W.data.attrs);L.href=c,L["aria-current"]=l}else q.on=f}return t(this.tag,q,this.$slots.default)}};function Zr(t){if(!(t.metaKey||t.altKey||t.ctrlKey||t.shiftKey||t.defaultPrevented||void 0!==t.button&&0!==t.button)){if(t.currentTarget&&t.currentTarget.getAttribute){var e=t.currentTarget.getAttribute("target");if(/\b_blank\b/i.test(e))return}return t.preventDefault&&t.preventDefault(),!0}}function Qr(t){if(t)for(var e,n=0;n-1&&(c.params[O]=n.params[O]);return c.path=Gr(a.path,c.params),r(a,c,b)}if(c.path){c.params={};for(var s=0;s-1}function Ez(t,e){return Nz(t)&&t._isRouter&&(null==e||t.type===e)}function Tz(t,e,n){var o=function(p){p>=t.length?n():t[p]?e(t[p],(function(){o(p+1)})):o(p+1)};o(0)}function Bz(t){return function(e,n,o){var p=!1,M=0,b=null;Cz(t,(function(t,e,n,c){if("function"==typeof t&&void 0===t.cid){p=!0,M++;var r,z=Xz((function(e){var p;((p=e).__esModule||Sz&&"Module"===p[Symbol.toStringTag])&&(e=e.default),t.resolved="function"==typeof e?e:$r.extend(e),n.components[c]=e,--M<=0&&o()})),a=Xz((function(t){var e="Failed to resolve async component "+c+": "+t;b||(b=Nz(t)?t:new Error(e),o(b))}));try{r=t(z,a)}catch(t){a(t)}if(r)if("function"==typeof r.then)r.then(z,a);else{var i=r.component;i&&"function"==typeof i.then&&i.then(z,a)}}})),p||o()}}function Cz(t,e){return wz(t.map((function(t){return Object.keys(t.components).map((function(n){return e(t.components[n],t.instances[n],t,n)}))})))}function wz(t){return Array.prototype.concat.apply([],t)}var Sz="function"==typeof Symbol&&"symbol"==typeof Symbol.toStringTag;function Xz(t){var e=!1;return function(){for(var n=[],o=arguments.length;o--;)n[o]=arguments[o];if(!e)return e=!0,t.apply(this,n)}}var xz=function(t,e){this.router=t,this.base=function(t){if(!t)if(Jr){var e=document.querySelector("base");t=(t=e&&e.getAttribute("href")||"/").replace(/^https?:\/\/[^\/]+/,"")}else t="/";"/"!==t.charAt(0)&&(t="/"+t);return t.replace(/\/$/,"")}(e),this.current=fr,this.pending=null,this.ready=!1,this.readyCbs=[],this.readyErrorCbs=[],this.errorCbs=[],this.listeners=[]};function kz(t,e,n,o){var p=Cz(t,(function(t,o,p,M){var b=function(t,e){"function"!=typeof t&&(t=$r.extend(t));return t.options[e]}(t,e);if(b)return Array.isArray(b)?b.map((function(t){return n(t,o,p,M)})):n(b,o,p,M)}));return wz(o?p.reverse():p)}function Iz(t,e){if(e)return function(){return t.apply(e,arguments)}}xz.prototype.listen=function(t){this.cb=t},xz.prototype.onReady=function(t,e){this.ready?t():(this.readyCbs.push(t),e&&this.readyErrorCbs.push(e))},xz.prototype.onError=function(t){this.errorCbs.push(t)},xz.prototype.transitionTo=function(t,e,n){var o,p=this;try{o=this.router.match(t,this.current)}catch(t){throw this.errorCbs.forEach((function(e){e(t)})),t}var M=this.current;this.confirmTransition(o,(function(){p.updateRoute(o),e&&e(o),p.ensureURL(),p.router.afterHooks.forEach((function(t){t&&t(o,M)})),p.ready||(p.ready=!0,p.readyCbs.forEach((function(t){t(o)})))}),(function(t){n&&n(t),t&&!p.ready&&(Ez(t,mz.redirected)&&M===fr||(p.ready=!0,p.readyErrorCbs.forEach((function(e){e(t)}))))}))},xz.prototype.confirmTransition=function(t,e,n){var o=this,p=this.current;this.pending=t;var M,b,c=function(t){!Ez(t)&&Nz(t)&&o.errorCbs.length&&o.errorCbs.forEach((function(e){e(t)})),n&&n(t)},r=t.matched.length-1,z=p.matched.length-1;if(Wr(t,p)&&r===z&&t.matched[r]===p.matched[z])return this.ensureURL(),t.hash&&Oz(this.router,p,t,!1),c(((b=yz(M=p,t,mz.duplicated,'Avoided redundant navigation to current location: "'+M.fullPath+'".')).name="NavigationDuplicated",b));var a=function(t,e){var n,o=Math.max(t.length,e.length);for(n=0;n0)){var e=this.router,n=e.options.scrollBehavior,o=Wz&&n;o&&this.listeners.push(iz());var p=function(){var n=t.current,p=Pz(t.base);t.current===fr&&p===t._startLocation||t.transitionTo(p,(function(t){o&&Oz(e,t,n,!0)}))};window.addEventListener("popstate",p),this.listeners.push((function(){window.removeEventListener("popstate",p)}))}},e.prototype.go=function(t){window.history.go(t)},e.prototype.push=function(t,e,n){var o=this,p=this.current;this.transitionTo(t,(function(t){vz(yr(o.base+t.fullPath)),Oz(o.router,t,p,!1),e&&e(t)}),n)},e.prototype.replace=function(t,e,n){var o=this,p=this.current;this.transitionTo(t,(function(t){Rz(yr(o.base+t.fullPath)),Oz(o.router,t,p,!1),e&&e(t)}),n)},e.prototype.ensureURL=function(t){if(Pz(this.base)!==this.current.fullPath){var e=yr(this.base+this.current.fullPath);t?vz(e):Rz(e)}},e.prototype.getCurrentLocation=function(){return Pz(this.base)},e}(xz);function Pz(t){var e=window.location.pathname,n=e.toLowerCase(),o=t.toLowerCase();return!t||n!==o&&0!==n.indexOf(yr(o+"/"))||(e=e.slice(t.length)),(e||"/")+window.location.search+window.location.hash}var Uz=function(t){function e(e,n,o){t.call(this,e,n),o&&function(t){var e=Pz(t);if(!/^\/#/.test(e))return window.location.replace(yr(t+"/#"+e)),!0}(this.base)||jz()}return t&&(e.__proto__=t),e.prototype=Object.create(t&&t.prototype),e.prototype.constructor=e,e.prototype.setupListeners=function(){var t=this;if(!(this.listeners.length>0)){var e=this.router.options.scrollBehavior,n=Wz&&e;n&&this.listeners.push(iz());var o=function(){var e=t.current;jz()&&t.transitionTo(Fz(),(function(o){n&&Oz(t.router,o,e,!0),Wz||Yz(o.fullPath)}))},p=Wz?"popstate":"hashchange";window.addEventListener(p,o),this.listeners.push((function(){window.removeEventListener(p,o)}))}},e.prototype.push=function(t,e,n){var o=this,p=this.current;this.transitionTo(t,(function(t){Gz(t.fullPath),Oz(o.router,t,p,!1),e&&e(t)}),n)},e.prototype.replace=function(t,e,n){var o=this,p=this.current;this.transitionTo(t,(function(t){Yz(t.fullPath),Oz(o.router,t,p,!1),e&&e(t)}),n)},e.prototype.go=function(t){window.history.go(t)},e.prototype.ensureURL=function(t){var e=this.current.fullPath;Fz()!==e&&(t?Gz(e):Yz(e))},e.prototype.getCurrentLocation=function(){return Fz()},e}(xz);function jz(){var t=Fz();return"/"===t.charAt(0)||(Yz("/"+t),!1)}function Fz(){var t=window.location.href,e=t.indexOf("#");return e<0?"":t=t.slice(e+1)}function Hz(t){var e=window.location.href,n=e.indexOf("#");return(n>=0?e.slice(0,n):e)+"#"+t}function Gz(t){Wz?vz(Hz(t)):window.location.hash=t}function Yz(t){Wz?Rz(Hz(t)):window.location.replace(Hz(t))}var $z=function(t){function e(e,n){t.call(this,e,n),this.stack=[],this.index=-1}return t&&(e.__proto__=t),e.prototype=Object.create(t&&t.prototype),e.prototype.constructor=e,e.prototype.push=function(t,e,n){var o=this;this.transitionTo(t,(function(t){o.stack=o.stack.slice(0,o.index+1).concat(t),o.index++,e&&e(t)}),n)},e.prototype.replace=function(t,e,n){var o=this;this.transitionTo(t,(function(t){o.stack=o.stack.slice(0,o.index).concat(t),e&&e(t)}),n)},e.prototype.go=function(t){var e=this,n=this.index+t;if(!(n<0||n>=this.stack.length)){var o=this.stack[n];this.confirmTransition(o,(function(){var t=e.current;e.index=n,e.updateRoute(o),e.router.afterHooks.forEach((function(e){e&&e(o,t)}))}),(function(t){Ez(t,mz.duplicated)&&(e.index=n)}))}},e.prototype.getCurrentLocation=function(){var t=this.stack[this.stack.length-1];return t?t.fullPath:"/"},e.prototype.ensureURL=function(){},e}(xz),Vz=function(t){void 0===t&&(t={}),this.app=null,this.apps=[],this.options=t,this.beforeHooks=[],this.resolveHooks=[],this.afterHooks=[],this.matcher=oz(t.routes||[],this);var e=t.mode||"hash";switch(this.fallback="history"===e&&!Wz&&!1!==t.fallback,this.fallback&&(e="hash"),Jr||(e="abstract"),this.mode=e,e){case"history":this.history=new Dz(this,t.base);break;case"hash":this.history=new Uz(this,t.base,this.fallback);break;case"abstract":this.history=new $z(this,t.base)}},Kz={currentRoute:{configurable:!0}};Vz.prototype.match=function(t,e,n){return this.matcher.match(t,e,n)},Kz.currentRoute.get=function(){return this.history&&this.history.current},Vz.prototype.init=function(t){var e=this;if(this.apps.push(t),t.$once("hook:destroyed",(function(){var n=e.apps.indexOf(t);n>-1&&e.apps.splice(n,1),e.app===t&&(e.app=e.apps[0]||null),e.app||e.history.teardown()})),!this.app){this.app=t;var n=this.history;if(n instanceof Dz||n instanceof Uz){var o=function(t){n.setupListeners(),function(t){var o=n.current,p=e.options.scrollBehavior;Wz&&p&&"fullPath"in t&&Oz(e,t,o,!1)}(t)};n.transitionTo(n.getCurrentLocation(),o,o)}n.listen((function(t){e.apps.forEach((function(e){e._route=t}))}))}},Vz.prototype.beforeEach=function(t){return Qz(this.beforeHooks,t)},Vz.prototype.beforeResolve=function(t){return Qz(this.resolveHooks,t)},Vz.prototype.afterEach=function(t){return Qz(this.afterHooks,t)},Vz.prototype.onReady=function(t,e){this.history.onReady(t,e)},Vz.prototype.onError=function(t){this.history.onError(t)},Vz.prototype.push=function(t,e,n){var o=this;if(!e&&!n&&"undefined"!=typeof Promise)return new Promise((function(e,n){o.history.push(t,e,n)}));this.history.push(t,e,n)},Vz.prototype.replace=function(t,e,n){var o=this;if(!e&&!n&&"undefined"!=typeof Promise)return new Promise((function(e,n){o.history.replace(t,e,n)}));this.history.replace(t,e,n)},Vz.prototype.go=function(t){this.history.go(t)},Vz.prototype.back=function(){this.go(-1)},Vz.prototype.forward=function(){this.go(1)},Vz.prototype.getMatchedComponents=function(t){var e=t?t.matched?t:this.resolve(t).route:this.currentRoute;return e?[].concat.apply([],e.matched.map((function(t){return Object.keys(t.components).map((function(e){return t.components[e]}))}))):[]},Vz.prototype.resolve=function(t,e,n){var o=Yr(t,e=e||this.history.current,n,this),p=this.match(o,e),M=p.redirectedFrom||p.fullPath,b=function(t,e,n){var o="hash"===n?"#"+e:e;return t?yr(t+"/"+o):o}(this.history.base,M,this.mode);return{location:o,route:p,href:b,normalizedTo:o,resolved:p}},Vz.prototype.getRoutes=function(){return this.matcher.getRoutes()},Vz.prototype.addRoute=function(t,e){this.matcher.addRoute(t,e),this.history.current!==fr&&this.history.transitionTo(this.history.getCurrentLocation())},Vz.prototype.addRoutes=function(t){this.matcher.addRoutes(t),this.history.current!==fr&&this.history.transitionTo(this.history.getCurrentLocation())},Object.defineProperties(Vz.prototype,Kz);var Zz=Vz;function Qz(t,e){return t.push(e),function(){var n=t.indexOf(e);n>-1&&t.splice(n,1)}}Vz.install=function t(e){if(!t.installed||$r!==e){t.installed=!0,$r=e;var n=function(t){return void 0!==t},o=function(t,e){var o=t.$options._parentVnode;n(o)&&n(o=o.data)&&n(o=o.registerRouteInstance)&&o(t,e)};e.mixin({beforeCreate:function(){n(this.$options.router)?(this._routerRoot=this,this._router=this.$options.router,this._router.init(this),e.util.defineReactive(this,"_route",this._router.history.current)):this._routerRoot=this.$parent&&this.$parent._routerRoot||this,o(this,this)},destroyed:function(){o(this)}}),Object.defineProperty(e.prototype,"$router",{get:function(){return this._routerRoot._router}}),Object.defineProperty(e.prototype,"$route",{get:function(){return this._routerRoot._route}}),e.component("RouterView",mr),e.component("RouterLink",Kr);var p=e.config.optionMergeStrategies;p.beforeRouteEnter=p.beforeRouteLeave=p.beforeRouteUpdate=p.created}},Vz.version="3.6.5",Vz.isNavigationFailure=Ez,Vz.NavigationFailureType=mz,Vz.START_LOCATION=fr,Jr&&window.Vue&&window.Vue.use(Vz);var Jz=n(4566),ta=n.n(Jz),ea=n(3379),na=n.n(ea),oa=n(1991),pa={insert:"head",singleton:!1};na()(oa.Z,pa);oa.Z.locals;n(3734);var Ma=document.head.querySelector('meta[name="csrf-token"]');Ma&&(pr.Z.defaults.headers.common["X-CSRF-TOKEN"]=Ma.content),no.use(Zz),window.Popper=n(8981).default,nr().tz.setDefault(Telescope.timezone),window.Telescope.basePath="/"+window.Telescope.path;var ba=window.Telescope.basePath+"/";""!==window.Telescope.path&&"/"!==window.Telescope.path||(ba="/",window.Telescope.basePath="");var ca=new Zz({routes:Mr,mode:"history",base:ba});no.component("vue-json-pretty",ta()),no.component("related-entries",n(9932).Z),no.component("index-screen",n(8106).Z),no.component("preview-screen",n(2986).Z),no.component("alert",n(4518).Z),no.component("copy-clipboard",n(7973).Z),no.mixin(or),new no({el:"#telescope",router:ca,data:function(){return{alert:{type:null,autoClose:0,message:"",confirmationProceed:null,confirmationCancel:null},autoLoadsNewEntries:"1"===localStorage.autoLoadsNewEntries,recording:Telescope.recording}},created:function(){window.addEventListener("keydown",this.keydownListener)},destroyed:function(){window.removeEventListener("keydown",this.keydownListener)},methods:{autoLoadNewEntries:function(){this.autoLoadsNewEntries?(this.autoLoadsNewEntries=!1,localStorage.autoLoadsNewEntries=0):(this.autoLoadsNewEntries=!0,localStorage.autoLoadsNewEntries=1)},toggleRecording:function(){pr.Z.post(Telescope.basePath+"/telescope-api/toggle-recording"),window.Telescope.recording=!Telescope.recording,this.recording=!this.recording},clearEntries:function(){(!(arguments.length>0&&void 0!==arguments[0])||arguments[0])&&!confirm("Are you sure you want to delete all Telescope data?")||pr.Z.delete(Telescope.basePath+"/telescope-api/entries").then((function(t){return location.reload()}))},keydownListener:function(t){t.metaKey&&"k"===t.key&&this.clearEntries(!1)}}})},601:(t,e,n)=>{"use strict";n.d(e,{Z:()=>o});const o={methods:{cacheActionTypeClass:function(t){return"hit"===t?"success":"set"===t?"info":"forget"===t?"warning":"missed"===t?"danger":void 0},composerTypeClass:function(t){return"composer"===t?"info":"creator"===t?"success":void 0},gateResultClass:function(t){return"allowed"===t?"success":"denied"===t?"danger":void 0},jobStatusClass:function(t){return"pending"===t?"secondary":"processed"===t?"success":"failed"===t?"danger":void 0},logLevelClass:function(t){return"debug"===t?"success":"info"===t?"info":"notice"===t?"secondary":"warning"===t?"warning":"error"===t||"critical"===t||"alert"===t||"emergency"===t?"danger":void 0},modelActionClass:function(t){return"created"==t?"success":"updated"==t?"info":"retrieved"==t?"secondary":"deleted"==t||"forceDeleted"==t?"danger":void 0},requestStatusClass:function(t){return t?t<300?"success":t<400?"info":t<500?"warning":t>=500?"danger":void 0:"danger"},requestMethodClass:function(t){return"GET"==t||"OPTIONS"==t?"secondary":"POST"==t||"PATCH"==t||"PUT"==t?"info":"DELETE"==t?"danger":void 0}}}},9742:(t,e)=>{"use strict";e.byteLength=function(t){var e=c(t),n=e[0],o=e[1];return 3*(n+o)/4-o},e.toByteArray=function(t){var e,n,M=c(t),b=M[0],r=M[1],z=new p(function(t,e,n){return 3*(e+n)/4-n}(0,b,r)),a=0,i=r>0?b-4:b;for(n=0;n>16&255,z[a++]=e>>8&255,z[a++]=255&e;2===r&&(e=o[t.charCodeAt(n)]<<2|o[t.charCodeAt(n+1)]>>4,z[a++]=255&e);1===r&&(e=o[t.charCodeAt(n)]<<10|o[t.charCodeAt(n+1)]<<4|o[t.charCodeAt(n+2)]>>2,z[a++]=e>>8&255,z[a++]=255&e);return z},e.fromByteArray=function(t){for(var e,o=t.length,p=o%3,M=[],b=16383,c=0,z=o-p;cz?z:c+b));1===p?(e=t[o-1],M.push(n[e>>2]+n[e<<4&63]+"==")):2===p&&(e=(t[o-2]<<8)+t[o-1],M.push(n[e>>10]+n[e>>4&63]+n[e<<2&63]+"="));return M.join("")};for(var n=[],o=[],p="undefined"!=typeof Uint8Array?Uint8Array:Array,M="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",b=0;b<64;++b)n[b]=M[b],o[M.charCodeAt(b)]=b;function c(t){var e=t.length;if(e%4>0)throw new Error("Invalid string. Length must be a multiple of 4");var n=t.indexOf("=");return-1===n&&(n=e),[n,n===e?0:4-n%4]}function r(t,e,o){for(var p,M,b=[],c=e;c>18&63]+n[M>>12&63]+n[M>>6&63]+n[63&M]);return b.join("")}o["-".charCodeAt(0)]=62,o["_".charCodeAt(0)]=63},3734:function(t,e,n){!function(t,e,n){"use strict";function o(t){return t&&"object"==typeof t&&"default"in t?t:{default:t}}var p=o(e),M=o(n);function b(t,e){for(var n=0;n=b)throw new Error("Bootstrap's JavaScript requires at least jQuery v1.9.1 but less than v4.0.0")}};f.jQueryDetection(),d();var q="alert",h="4.6.2",W="bs.alert",v="."+W,R=".data-api",m=p.default.fn[q],g="alert",L="fade",y="show",_="close"+v,N="closed"+v,E="click"+v+R,T='[data-dismiss="alert"]',B=function(){function t(t){this._element=t}var e=t.prototype;return e.close=function(t){var e=this._element;t&&(e=this._getRootElement(t)),this._triggerCloseEvent(e).isDefaultPrevented()||this._removeElement(e)},e.dispose=function(){p.default.removeData(this._element,W),this._element=null},e._getRootElement=function(t){var e=f.getSelectorFromElement(t),n=!1;return e&&(n=document.querySelector(e)),n||(n=p.default(t).closest("."+g)[0]),n},e._triggerCloseEvent=function(t){var e=p.default.Event(_);return p.default(t).trigger(e),e},e._removeElement=function(t){var e=this;if(p.default(t).removeClass(y),p.default(t).hasClass(L)){var n=f.getTransitionDurationFromElement(t);p.default(t).one(f.TRANSITION_END,(function(n){return e._destroyElement(t,n)})).emulateTransitionEnd(n)}else this._destroyElement(t)},e._destroyElement=function(t){p.default(t).detach().trigger(N).remove()},t._jQueryInterface=function(e){return this.each((function(){var n=p.default(this),o=n.data(W);o||(o=new t(this),n.data(W,o)),"close"===e&&o[e](this)}))},t._handleDismiss=function(t){return function(e){e&&e.preventDefault(),t.close(this)}},c(t,null,[{key:"VERSION",get:function(){return h}}]),t}();p.default(document).on(E,T,B._handleDismiss(new B)),p.default.fn[q]=B._jQueryInterface,p.default.fn[q].Constructor=B,p.default.fn[q].noConflict=function(){return p.default.fn[q]=m,B._jQueryInterface};var C="button",w="4.6.2",S="bs.button",X="."+S,x=".data-api",k=p.default.fn[C],I="active",D="btn",P="focus",U="click"+X+x,j="focus"+X+x+" blur"+X+x,F="load"+X+x,H='[data-toggle^="button"]',G='[data-toggle="buttons"]',Y='[data-toggle="button"]',$='[data-toggle="buttons"] .btn',V='input:not([type="hidden"])',K=".active",Z=".btn",Q=function(){function t(t){this._element=t,this.shouldAvoidTriggerChange=!1}var e=t.prototype;return e.toggle=function(){var t=!0,e=!0,n=p.default(this._element).closest(G)[0];if(n){var o=this._element.querySelector(V);if(o){if("radio"===o.type)if(o.checked&&this._element.classList.contains(I))t=!1;else{var M=n.querySelector(K);M&&p.default(M).removeClass(I)}t&&("checkbox"!==o.type&&"radio"!==o.type||(o.checked=!this._element.classList.contains(I)),this.shouldAvoidTriggerChange||p.default(o).trigger("change")),o.focus(),e=!1}}this._element.hasAttribute("disabled")||this._element.classList.contains("disabled")||(e&&this._element.setAttribute("aria-pressed",!this._element.classList.contains(I)),t&&p.default(this._element).toggleClass(I))},e.dispose=function(){p.default.removeData(this._element,S),this._element=null},t._jQueryInterface=function(e,n){return this.each((function(){var o=p.default(this),M=o.data(S);M||(M=new t(this),o.data(S,M)),M.shouldAvoidTriggerChange=n,"toggle"===e&&M[e]()}))},c(t,null,[{key:"VERSION",get:function(){return w}}]),t}();p.default(document).on(U,H,(function(t){var e=t.target,n=e;if(p.default(e).hasClass(D)||(e=p.default(e).closest(Z)[0]),!e||e.hasAttribute("disabled")||e.classList.contains("disabled"))t.preventDefault();else{var o=e.querySelector(V);if(o&&(o.hasAttribute("disabled")||o.classList.contains("disabled")))return void t.preventDefault();"INPUT"!==n.tagName&&"LABEL"===e.tagName||Q._jQueryInterface.call(p.default(e),"toggle","INPUT"===n.tagName)}})).on(j,H,(function(t){var e=p.default(t.target).closest(Z)[0];p.default(e).toggleClass(P,/^focus(in)?$/.test(t.type))})),p.default(window).on(F,(function(){for(var t=[].slice.call(document.querySelectorAll($)),e=0,n=t.length;e0,this._pointerEvent=Boolean(window.PointerEvent||window.MSPointerEvent),this._addEventListeners()}var e=t.prototype;return e.next=function(){this._isSliding||this._slide(dt)},e.nextWhenVisible=function(){var t=p.default(this._element);!document.hidden&&t.is(":visible")&&"hidden"!==t.css("visibility")&&this.next()},e.prev=function(){this._isSliding||this._slide(ft)},e.pause=function(t){t||(this._isPaused=!0),this._element.querySelector(kt)&&(f.triggerTransitionEnd(this._element),this.cycle(!0)),clearInterval(this._interval),this._interval=null},e.cycle=function(t){t||(this._isPaused=!1),this._interval&&(clearInterval(this._interval),this._interval=null),this._config.interval&&!this._isPaused&&(this._updateInterval(),this._interval=setInterval((document.visibilityState?this.nextWhenVisible:this.next).bind(this),this._config.interval))},e.to=function(t){var e=this;this._activeElement=this._element.querySelector(St);var n=this._getItemIndex(this._activeElement);if(!(t>this._items.length-1||t<0))if(this._isSliding)p.default(this._element).one(vt,(function(){return e.to(t)}));else{if(n===t)return this.pause(),void this.cycle();var o=t>n?dt:ft;this._slide(o,this._items[t])}},e.dispose=function(){p.default(this._element).off(nt),p.default.removeData(this._element,et),this._items=null,this._config=null,this._element=null,this._interval=null,this._isPaused=null,this._isSliding=null,this._activeElement=null,this._indicatorsElement=null},e._getConfig=function(t){return t=r({},Ut,t),f.typeCheckConfig(J,t,jt),t},e._handleSwipe=function(){var t=Math.abs(this.touchDeltaX);if(!(t<=rt)){var e=t/this.touchDeltaX;this.touchDeltaX=0,e>0&&this.prev(),e<0&&this.next()}},e._addEventListeners=function(){var t=this;this._config.keyboard&&p.default(this._element).on(Rt,(function(e){return t._keydown(e)})),"hover"===this._config.pause&&p.default(this._element).on(mt,(function(e){return t.pause(e)})).on(gt,(function(e){return t.cycle(e)})),this._config.touch&&this._addTouchEventListeners()},e._addTouchEventListeners=function(){var t=this;if(this._touchSupported){var e=function(e){t._pointerEvent&&Ft[e.originalEvent.pointerType.toUpperCase()]?t.touchStartX=e.originalEvent.clientX:t._pointerEvent||(t.touchStartX=e.originalEvent.touches[0].clientX)},n=function(e){t.touchDeltaX=e.originalEvent.touches&&e.originalEvent.touches.length>1?0:e.originalEvent.touches[0].clientX-t.touchStartX},o=function(e){t._pointerEvent&&Ft[e.originalEvent.pointerType.toUpperCase()]&&(t.touchDeltaX=e.originalEvent.clientX-t.touchStartX),t._handleSwipe(),"hover"===t._config.pause&&(t.pause(),t.touchTimeout&&clearTimeout(t.touchTimeout),t.touchTimeout=setTimeout((function(e){return t.cycle(e)}),ct+t._config.interval))};p.default(this._element.querySelectorAll(xt)).on(Tt,(function(t){return t.preventDefault()})),this._pointerEvent?(p.default(this._element).on(Nt,(function(t){return e(t)})),p.default(this._element).on(Et,(function(t){return o(t)})),this._element.classList.add(lt)):(p.default(this._element).on(Lt,(function(t){return e(t)})),p.default(this._element).on(yt,(function(t){return n(t)})),p.default(this._element).on(_t,(function(t){return o(t)})))}},e._keydown=function(t){if(!/input|textarea/i.test(t.target.tagName))switch(t.which){case Mt:t.preventDefault(),this.prev();break;case bt:t.preventDefault(),this.next()}},e._getItemIndex=function(t){return this._items=t&&t.parentNode?[].slice.call(t.parentNode.querySelectorAll(Xt)):[],this._items.indexOf(t)},e._getItemByDirection=function(t,e){var n=t===dt,o=t===ft,p=this._getItemIndex(e),M=this._items.length-1;if((o&&0===p||n&&p===M)&&!this._config.wrap)return e;var b=(p+(t===ft?-1:1))%this._items.length;return-1===b?this._items[this._items.length-1]:this._items[b]},e._triggerSlideEvent=function(t,e){var n=this._getItemIndex(t),o=this._getItemIndex(this._element.querySelector(St)),M=p.default.Event(Wt,{relatedTarget:t,direction:e,from:o,to:n});return p.default(this._element).trigger(M),M},e._setActiveIndicatorElement=function(t){if(this._indicatorsElement){var e=[].slice.call(this._indicatorsElement.querySelectorAll(wt));p.default(e).removeClass(at);var n=this._indicatorsElement.children[this._getItemIndex(t)];n&&p.default(n).addClass(at)}},e._updateInterval=function(){var t=this._activeElement||this._element.querySelector(St);if(t){var e=parseInt(t.getAttribute("data-interval"),10);e?(this._config.defaultInterval=this._config.defaultInterval||this._config.interval,this._config.interval=e):this._config.interval=this._config.defaultInterval||this._config.interval}},e._slide=function(t,e){var n,o,M,b=this,c=this._element.querySelector(St),r=this._getItemIndex(c),z=e||c&&this._getItemByDirection(t,c),a=this._getItemIndex(z),i=Boolean(this._interval);if(t===dt?(n=st,o=At,M=qt):(n=Ot,o=ut,M=ht),z&&p.default(z).hasClass(at))this._isSliding=!1;else if(!this._triggerSlideEvent(z,M).isDefaultPrevented()&&c&&z){this._isSliding=!0,i&&this.pause(),this._setActiveIndicatorElement(z),this._activeElement=z;var O=p.default.Event(vt,{relatedTarget:z,direction:M,from:r,to:a});if(p.default(this._element).hasClass(it)){p.default(z).addClass(o),f.reflow(z),p.default(c).addClass(n),p.default(z).addClass(n);var s=f.getTransitionDurationFromElement(c);p.default(c).one(f.TRANSITION_END,(function(){p.default(z).removeClass(n+" "+o).addClass(at),p.default(c).removeClass(at+" "+o+" "+n),b._isSliding=!1,setTimeout((function(){return p.default(b._element).trigger(O)}),0)})).emulateTransitionEnd(s)}else p.default(c).removeClass(at),p.default(z).addClass(at),this._isSliding=!1,p.default(this._element).trigger(O);i&&this.cycle()}},t._jQueryInterface=function(e){return this.each((function(){var n=p.default(this).data(et),o=r({},Ut,p.default(this).data());"object"==typeof e&&(o=r({},o,e));var M="string"==typeof e?e:o.slide;if(n||(n=new t(this,o),p.default(this).data(et,n)),"number"==typeof e)n.to(e);else if("string"==typeof M){if(void 0===n[M])throw new TypeError('No method named "'+M+'"');n[M]()}else o.interval&&o.ride&&(n.pause(),n.cycle())}))},t._dataApiClickHandler=function(e){var n=f.getSelectorFromElement(this);if(n){var o=p.default(n)[0];if(o&&p.default(o).hasClass(zt)){var M=r({},p.default(o).data(),p.default(this).data()),b=this.getAttribute("data-slide-to");b&&(M.interval=!1),t._jQueryInterface.call(p.default(o),M),b&&p.default(o).data(et).to(b),e.preventDefault()}}},c(t,null,[{key:"VERSION",get:function(){return tt}},{key:"Default",get:function(){return Ut}}]),t}();p.default(document).on(Ct,Dt,Ht._dataApiClickHandler),p.default(window).on(Bt,(function(){for(var t=[].slice.call(document.querySelectorAll(Pt)),e=0,n=t.length;e0&&(this._selector=b,this._triggerArray.push(M))}this._parent=this._config.parent?this._getParent():null,this._config.parent||this._addAriaAndCollapsedClass(this._element,this._triggerArray),this._config.toggle&&this.toggle()}var e=t.prototype;return e.toggle=function(){p.default(this._element).hasClass(Qt)?this.hide():this.show()},e.show=function(){var e,n,o=this;if(!(this._isTransitioning||p.default(this._element).hasClass(Qt)||(this._parent&&0===(e=[].slice.call(this._parent.querySelectorAll(ze)).filter((function(t){return"string"==typeof o._config.parent?t.getAttribute("data-parent")===o._config.parent:t.classList.contains(Jt)}))).length&&(e=null),e&&(n=p.default(e).not(this._selector).data($t))&&n._isTransitioning))){var M=p.default.Event(pe);if(p.default(this._element).trigger(M),!M.isDefaultPrevented()){e&&(t._jQueryInterface.call(p.default(e).not(this._selector),"hide"),n||p.default(e).data($t,null));var b=this._getDimension();p.default(this._element).removeClass(Jt).addClass(te),this._element.style[b]=0,this._triggerArray.length&&p.default(this._triggerArray).removeClass(ee).attr("aria-expanded",!0),this.setTransitioning(!0);var c=function(){p.default(o._element).removeClass(te).addClass(Jt+" "+Qt),o._element.style[b]="",o.setTransitioning(!1),p.default(o._element).trigger(Me)},r="scroll"+(b[0].toUpperCase()+b.slice(1)),z=f.getTransitionDurationFromElement(this._element);p.default(this._element).one(f.TRANSITION_END,c).emulateTransitionEnd(z),this._element.style[b]=this._element[r]+"px"}}},e.hide=function(){var t=this;if(!this._isTransitioning&&p.default(this._element).hasClass(Qt)){var e=p.default.Event(be);if(p.default(this._element).trigger(e),!e.isDefaultPrevented()){var n=this._getDimension();this._element.style[n]=this._element.getBoundingClientRect()[n]+"px",f.reflow(this._element),p.default(this._element).addClass(te).removeClass(Jt+" "+Qt);var o=this._triggerArray.length;if(o>0)for(var M=0;M0},e._getOffset=function(){var t=this,e={};return"function"==typeof this._config.offset?e.fn=function(e){return e.offsets=r({},e.offsets,t._config.offset(e.offsets,t._element)),e}:e.offset=this._config.offset,e},e._getPopperConfig=function(){var t={placement:this._getPlacement(),modifiers:{offset:this._getOffset(),flip:{enabled:this._config.flip},preventOverflow:{boundariesElement:this._config.boundary}}};return"static"===this._config.display&&(t.modifiers.applyStyle={enabled:!1}),r({},t,this._config.popperConfig)},t._jQueryInterface=function(e){return this.each((function(){var n=p.default(this).data(le);if(n||(n=new t(this,"object"==typeof e?e:null),p.default(this).data(le,n)),"string"==typeof e){if(void 0===n[e])throw new TypeError('No method named "'+e+'"');n[e]()}}))},t._clearMenus=function(e){if(!e||e.which!==ge&&("keyup"!==e.type||e.which===ve))for(var n=[].slice.call(document.querySelectorAll(Ue)),o=0,M=n.length;o0&&b--,e.which===me&&bdocument.documentElement.clientHeight;n||(this._element.style.overflowY="hidden"),this._element.classList.add(ln);var o=f.getTransitionDurationFromElement(this._dialog);p.default(this._element).off(f.TRANSITION_END),p.default(this._element).one(f.TRANSITION_END,(function(){t._element.classList.remove(ln),n||p.default(t._element).one(f.TRANSITION_END,(function(){t._element.style.overflowY=""})).emulateTransitionEnd(t._element,o)})).emulateTransitionEnd(o),this._element.focus()}},e._showElement=function(t){var e=this,n=p.default(this._element).hasClass(An),o=this._dialog?this._dialog.querySelector(En):null;this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE||document.body.appendChild(this._element),this._element.style.display="block",this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),p.default(this._dialog).hasClass(zn)&&o?o.scrollTop=0:this._element.scrollTop=0,n&&f.reflow(this._element),p.default(this._element).addClass(un),this._config.focus&&this._enforceFocus();var M=p.default.Event(Wn,{relatedTarget:t}),b=function(){e._config.focus&&e._element.focus(),e._isTransitioning=!1,p.default(e._element).trigger(M)};if(n){var c=f.getTransitionDurationFromElement(this._dialog);p.default(this._dialog).one(f.TRANSITION_END,b).emulateTransitionEnd(c)}else b()},e._enforceFocus=function(){var t=this;p.default(document).off(vn).on(vn,(function(e){document!==e.target&&t._element!==e.target&&0===p.default(t._element).has(e.target).length&&t._element.focus()}))},e._setEscapeEvent=function(){var t=this;this._isShown?p.default(this._element).on(gn,(function(e){t._config.keyboard&&e.which===rn?(e.preventDefault(),t.hide()):t._config.keyboard||e.which!==rn||t._triggerBackdropTransition()})):this._isShown||p.default(this._element).off(gn)},e._setResizeEvent=function(){var t=this;this._isShown?p.default(window).on(Rn,(function(e){return t.handleUpdate(e)})):p.default(window).off(Rn)},e._hideModal=function(){var t=this;this._element.style.display="none",this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._isTransitioning=!1,this._showBackdrop((function(){p.default(document.body).removeClass(sn),t._resetAdjustments(),t._resetScrollbar(),p.default(t._element).trigger(qn)}))},e._removeBackdrop=function(){this._backdrop&&(p.default(this._backdrop).remove(),this._backdrop=null)},e._showBackdrop=function(t){var e=this,n=p.default(this._element).hasClass(An)?An:"";if(this._isShown&&this._config.backdrop){if(this._backdrop=document.createElement("div"),this._backdrop.className=On,n&&this._backdrop.classList.add(n),p.default(this._backdrop).appendTo(document.body),p.default(this._element).on(mn,(function(t){e._ignoreBackdropClick?e._ignoreBackdropClick=!1:t.target===t.currentTarget&&("static"===e._config.backdrop?e._triggerBackdropTransition():e.hide())})),n&&f.reflow(this._backdrop),p.default(this._backdrop).addClass(un),!t)return;if(!n)return void t();var o=f.getTransitionDurationFromElement(this._backdrop);p.default(this._backdrop).one(f.TRANSITION_END,t).emulateTransitionEnd(o)}else if(!this._isShown&&this._backdrop){p.default(this._backdrop).removeClass(un);var M=function(){e._removeBackdrop(),t&&t()};if(p.default(this._element).hasClass(An)){var b=f.getTransitionDurationFromElement(this._backdrop);p.default(this._backdrop).one(f.TRANSITION_END,M).emulateTransitionEnd(b)}else M()}else t&&t()},e._adjustDialog=function(){var t=this._element.scrollHeight>document.documentElement.clientHeight;!this._isBodyOverflowing&&t&&(this._element.style.paddingLeft=this._scrollbarWidth+"px"),this._isBodyOverflowing&&!t&&(this._element.style.paddingRight=this._scrollbarWidth+"px")},e._resetAdjustments=function(){this._element.style.paddingLeft="",this._element.style.paddingRight=""},e._checkScrollbar=function(){var t=document.body.getBoundingClientRect();this._isBodyOverflowing=Math.round(t.left+t.right)
',trigger:"hover focus",title:"",delay:0,html:!1,selector:!1,placement:"top",offset:0,container:!1,fallbackPlacement:"flip",boundary:"scrollParent",customClass:"",sanitize:!0,sanitizeFn:null,whiteList:In,popperConfig:null},ao={animation:"boolean",template:"string",title:"(string|element|function)",trigger:"string",delay:"(number|object)",html:"boolean",selector:"(string|boolean)",placement:"(string|function)",offset:"(number|string|function)",container:"(string|element|boolean)",fallbackPlacement:"(string|array)",boundary:"(string|element)",customClass:"(string|function)",sanitize:"boolean",sanitizeFn:"(null|function)",whiteList:"object",popperConfig:"(null|object)"},io={HIDE:"hide"+Yn,HIDDEN:"hidden"+Yn,SHOW:"show"+Yn,SHOWN:"shown"+Yn,INSERTED:"inserted"+Yn,CLICK:"click"+Yn,FOCUSIN:"focusin"+Yn,FOCUSOUT:"focusout"+Yn,MOUSEENTER:"mouseenter"+Yn,MOUSELEAVE:"mouseleave"+Yn},Oo=function(){function t(t,e){if(void 0===M.default)throw new TypeError("Bootstrap's tooltips require Popper (https://popper.js.org)");this._isEnabled=!0,this._timeout=0,this._hoverState="",this._activeTrigger={},this._popper=null,this.element=t,this.config=this._getConfig(e),this.tip=null,this._setListeners()}var e=t.prototype;return e.enable=function(){this._isEnabled=!0},e.disable=function(){this._isEnabled=!1},e.toggleEnabled=function(){this._isEnabled=!this._isEnabled},e.toggle=function(t){if(this._isEnabled)if(t){var e=this.constructor.DATA_KEY,n=p.default(t.currentTarget).data(e);n||(n=new this.constructor(t.currentTarget,this._getDelegateConfig()),p.default(t.currentTarget).data(e,n)),n._activeTrigger.click=!n._activeTrigger.click,n._isWithActiveTrigger()?n._enter(null,n):n._leave(null,n)}else{if(p.default(this.getTipElement()).hasClass(Jn))return void this._leave(null,this);this._enter(null,this)}},e.dispose=function(){clearTimeout(this._timeout),p.default.removeData(this.element,this.constructor.DATA_KEY),p.default(this.element).off(this.constructor.EVENT_KEY),p.default(this.element).closest(".modal").off("hide.bs.modal",this._hideModalHandler),this.tip&&p.default(this.tip).remove(),this._isEnabled=null,this._timeout=null,this._hoverState=null,this._activeTrigger=null,this._popper&&this._popper.destroy(),this._popper=null,this.element=null,this.config=null,this.tip=null},e.show=function(){var t=this;if("none"===p.default(this.element).css("display"))throw new Error("Please use show on visible elements");var e=p.default.Event(this.constructor.Event.SHOW);if(this.isWithContent()&&this._isEnabled){p.default(this.element).trigger(e);var n=f.findShadowRoot(this.element),o=p.default.contains(null!==n?n:this.element.ownerDocument.documentElement,this.element);if(e.isDefaultPrevented()||!o)return;var b=this.getTipElement(),c=f.getUID(this.constructor.NAME);b.setAttribute("id",c),this.element.setAttribute("aria-describedby",c),this.setContent(),this.config.animation&&p.default(b).addClass(Qn);var r="function"==typeof this.config.placement?this.config.placement.call(this,b,this.element):this.config.placement,z=this._getAttachment(r);this.addAttachmentClass(z);var a=this._getContainer();p.default(b).data(this.constructor.DATA_KEY,this),p.default.contains(this.element.ownerDocument.documentElement,this.tip)||p.default(b).appendTo(a),p.default(this.element).trigger(this.constructor.Event.INSERTED),this._popper=new M.default(this.element,b,this._getPopperConfig(z)),p.default(b).addClass(Jn),p.default(b).addClass(this.config.customClass),"ontouchstart"in document.documentElement&&p.default(document.body).children().on("mouseover",null,p.default.noop);var i=function(){t.config.animation&&t._fixTransition();var e=t._hoverState;t._hoverState=null,p.default(t.element).trigger(t.constructor.Event.SHOWN),e===eo&&t._leave(null,t)};if(p.default(this.tip).hasClass(Qn)){var O=f.getTransitionDurationFromElement(this.tip);p.default(this.tip).one(f.TRANSITION_END,i).emulateTransitionEnd(O)}else i()}},e.hide=function(t){var e=this,n=this.getTipElement(),o=p.default.Event(this.constructor.Event.HIDE),M=function(){e._hoverState!==to&&n.parentNode&&n.parentNode.removeChild(n),e._cleanTipClass(),e.element.removeAttribute("aria-describedby"),p.default(e.element).trigger(e.constructor.Event.HIDDEN),null!==e._popper&&e._popper.destroy(),t&&t()};if(p.default(this.element).trigger(o),!o.isDefaultPrevented()){if(p.default(n).removeClass(Jn),"ontouchstart"in document.documentElement&&p.default(document.body).children().off("mouseover",null,p.default.noop),this._activeTrigger[bo]=!1,this._activeTrigger[Mo]=!1,this._activeTrigger[po]=!1,p.default(this.tip).hasClass(Qn)){var b=f.getTransitionDurationFromElement(n);p.default(n).one(f.TRANSITION_END,M).emulateTransitionEnd(b)}else M();this._hoverState=""}},e.update=function(){null!==this._popper&&this._popper.scheduleUpdate()},e.isWithContent=function(){return Boolean(this.getTitle())},e.addAttachmentClass=function(t){p.default(this.getTipElement()).addClass(Vn+"-"+t)},e.getTipElement=function(){return this.tip=this.tip||p.default(this.config.template)[0],this.tip},e.setContent=function(){var t=this.getTipElement();this.setElementContent(p.default(t.querySelectorAll(no)),this.getTitle()),p.default(t).removeClass(Qn+" "+Jn)},e.setElementContent=function(t,e){"object"!=typeof e||!e.nodeType&&!e.jquery?this.config.html?(this.config.sanitize&&(e=jn(e,this.config.whiteList,this.config.sanitizeFn)),t.html(e)):t.text(e):this.config.html?p.default(e).parent().is(t)||t.empty().append(e):t.text(p.default(e).text())},e.getTitle=function(){var t=this.element.getAttribute("data-original-title");return t||(t="function"==typeof this.config.title?this.config.title.call(this.element):this.config.title),t},e._getPopperConfig=function(t){var e=this;return r({},{placement:t,modifiers:{offset:this._getOffset(),flip:{behavior:this.config.fallbackPlacement},arrow:{element:oo},preventOverflow:{boundariesElement:this.config.boundary}},onCreate:function(t){t.originalPlacement!==t.placement&&e._handlePopperPlacementChange(t)},onUpdate:function(t){return e._handlePopperPlacementChange(t)}},this.config.popperConfig)},e._getOffset=function(){var t=this,e={};return"function"==typeof this.config.offset?e.fn=function(e){return e.offsets=r({},e.offsets,t.config.offset(e.offsets,t.element)),e}:e.offset=this.config.offset,e},e._getContainer=function(){return!1===this.config.container?document.body:f.isElement(this.config.container)?p.default(this.config.container):p.default(document).find(this.config.container)},e._getAttachment=function(t){return ro[t.toUpperCase()]},e._setListeners=function(){var t=this;this.config.trigger.split(" ").forEach((function(e){if("click"===e)p.default(t.element).on(t.constructor.Event.CLICK,t.config.selector,(function(e){return t.toggle(e)}));else if(e!==co){var n=e===po?t.constructor.Event.MOUSEENTER:t.constructor.Event.FOCUSIN,o=e===po?t.constructor.Event.MOUSELEAVE:t.constructor.Event.FOCUSOUT;p.default(t.element).on(n,t.config.selector,(function(e){return t._enter(e)})).on(o,t.config.selector,(function(e){return t._leave(e)}))}})),this._hideModalHandler=function(){t.element&&t.hide()},p.default(this.element).closest(".modal").on("hide.bs.modal",this._hideModalHandler),this.config.selector?this.config=r({},this.config,{trigger:"manual",selector:""}):this._fixTitle()},e._fixTitle=function(){var t=typeof this.element.getAttribute("data-original-title");(this.element.getAttribute("title")||"string"!==t)&&(this.element.setAttribute("data-original-title",this.element.getAttribute("title")||""),this.element.setAttribute("title",""))},e._enter=function(t,e){var n=this.constructor.DATA_KEY;(e=e||p.default(t.currentTarget).data(n))||(e=new this.constructor(t.currentTarget,this._getDelegateConfig()),p.default(t.currentTarget).data(n,e)),t&&(e._activeTrigger["focusin"===t.type?Mo:po]=!0),p.default(e.getTipElement()).hasClass(Jn)||e._hoverState===to?e._hoverState=to:(clearTimeout(e._timeout),e._hoverState=to,e.config.delay&&e.config.delay.show?e._timeout=setTimeout((function(){e._hoverState===to&&e.show()}),e.config.delay.show):e.show())},e._leave=function(t,e){var n=this.constructor.DATA_KEY;(e=e||p.default(t.currentTarget).data(n))||(e=new this.constructor(t.currentTarget,this._getDelegateConfig()),p.default(t.currentTarget).data(n,e)),t&&(e._activeTrigger["focusout"===t.type?Mo:po]=!1),e._isWithActiveTrigger()||(clearTimeout(e._timeout),e._hoverState=eo,e.config.delay&&e.config.delay.hide?e._timeout=setTimeout((function(){e._hoverState===eo&&e.hide()}),e.config.delay.hide):e.hide())},e._isWithActiveTrigger=function(){for(var t in this._activeTrigger)if(this._activeTrigger[t])return!0;return!1},e._getConfig=function(t){var e=p.default(this.element).data();return Object.keys(e).forEach((function(t){-1!==Zn.indexOf(t)&&delete e[t]})),"number"==typeof(t=r({},this.constructor.Default,e,"object"==typeof t&&t?t:{})).delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),f.typeCheckConfig(Fn,t,this.constructor.DefaultType),t.sanitize&&(t.template=jn(t.template,t.whiteList,t.sanitizeFn)),t},e._getDelegateConfig=function(){var t={};if(this.config)for(var e in this.config)this.constructor.Default[e]!==this.config[e]&&(t[e]=this.config[e]);return t},e._cleanTipClass=function(){var t=p.default(this.getTipElement()),e=t.attr("class").match(Kn);null!==e&&e.length&&t.removeClass(e.join(""))},e._handlePopperPlacementChange=function(t){this.tip=t.instance.popper,this._cleanTipClass(),this.addAttachmentClass(this._getAttachment(t.placement))},e._fixTransition=function(){var t=this.getTipElement(),e=this.config.animation;null===t.getAttribute("x-placement")&&(p.default(t).removeClass(Qn),this.config.animation=!1,this.hide(),this.show(),this.config.animation=e)},t._jQueryInterface=function(e){return this.each((function(){var n=p.default(this),o=n.data(Gn),M="object"==typeof e&&e;if((o||!/dispose|hide/.test(e))&&(o||(o=new t(this,M),n.data(Gn,o)),"string"==typeof e)){if(void 0===o[e])throw new TypeError('No method named "'+e+'"');o[e]()}}))},c(t,null,[{key:"VERSION",get:function(){return Hn}},{key:"Default",get:function(){return zo}},{key:"NAME",get:function(){return Fn}},{key:"DATA_KEY",get:function(){return Gn}},{key:"Event",get:function(){return io}},{key:"EVENT_KEY",get:function(){return Yn}},{key:"DefaultType",get:function(){return ao}}]),t}();p.default.fn[Fn]=Oo._jQueryInterface,p.default.fn[Fn].Constructor=Oo,p.default.fn[Fn].noConflict=function(){return p.default.fn[Fn]=$n,Oo._jQueryInterface};var so="popover",Ao="4.6.2",uo="bs.popover",lo="."+uo,fo=p.default.fn[so],qo="bs-popover",ho=new RegExp("(^|\\s)"+qo+"\\S+","g"),Wo="fade",vo="show",Ro=".popover-header",mo=".popover-body",go=r({},Oo.Default,{placement:"right",trigger:"click",content:"",template:''}),Lo=r({},Oo.DefaultType,{content:"(string|element|function)"}),yo={HIDE:"hide"+lo,HIDDEN:"hidden"+lo,SHOW:"show"+lo,SHOWN:"shown"+lo,INSERTED:"inserted"+lo,CLICK:"click"+lo,FOCUSIN:"focusin"+lo,FOCUSOUT:"focusout"+lo,MOUSEENTER:"mouseenter"+lo,MOUSELEAVE:"mouseleave"+lo},_o=function(t){function e(){return t.apply(this,arguments)||this}z(e,t);var n=e.prototype;return n.isWithContent=function(){return this.getTitle()||this._getContent()},n.addAttachmentClass=function(t){p.default(this.getTipElement()).addClass(qo+"-"+t)},n.getTipElement=function(){return this.tip=this.tip||p.default(this.config.template)[0],this.tip},n.setContent=function(){var t=p.default(this.getTipElement());this.setElementContent(t.find(Ro),this.getTitle());var e=this._getContent();"function"==typeof e&&(e=e.call(this.element)),this.setElementContent(t.find(mo),e),t.removeClass(Wo+" "+vo)},n._getContent=function(){return this.element.getAttribute("data-content")||this.config.content},n._cleanTipClass=function(){var t=p.default(this.getTipElement()),e=t.attr("class").match(ho);null!==e&&e.length>0&&t.removeClass(e.join(""))},e._jQueryInterface=function(t){return this.each((function(){var n=p.default(this).data(uo),o="object"==typeof t?t:null;if((n||!/dispose|hide/.test(t))&&(n||(n=new e(this,o),p.default(this).data(uo,n)),"string"==typeof t)){if(void 0===n[t])throw new TypeError('No method named "'+t+'"');n[t]()}}))},c(e,null,[{key:"VERSION",get:function(){return Ao}},{key:"Default",get:function(){return go}},{key:"NAME",get:function(){return so}},{key:"DATA_KEY",get:function(){return uo}},{key:"Event",get:function(){return yo}},{key:"EVENT_KEY",get:function(){return lo}},{key:"DefaultType",get:function(){return Lo}}]),e}(Oo);p.default.fn[so]=_o._jQueryInterface,p.default.fn[so].Constructor=_o,p.default.fn[so].noConflict=function(){return p.default.fn[so]=fo,_o._jQueryInterface};var No="scrollspy",Eo="4.6.2",To="bs.scrollspy",Bo="."+To,Co=".data-api",wo=p.default.fn[No],So="dropdown-item",Xo="active",xo="activate"+Bo,ko="scroll"+Bo,Io="load"+Bo+Co,Do="offset",Po="position",Uo='[data-spy="scroll"]',jo=".nav, .list-group",Fo=".nav-link",Ho=".nav-item",Go=".list-group-item",Yo=".dropdown",$o=".dropdown-item",Vo=".dropdown-toggle",Ko={offset:10,method:"auto",target:""},Zo={offset:"number",method:"string",target:"(string|element)"},Qo=function(){function t(t,e){var n=this;this._element=t,this._scrollElement="BODY"===t.tagName?window:t,this._config=this._getConfig(e),this._selector=this._config.target+" "+Fo+","+this._config.target+" "+Go+","+this._config.target+" "+$o,this._offsets=[],this._targets=[],this._activeTarget=null,this._scrollHeight=0,p.default(this._scrollElement).on(ko,(function(t){return n._process(t)})),this.refresh(),this._process()}var e=t.prototype;return e.refresh=function(){var t=this,e=this._scrollElement===this._scrollElement.window?Do:Po,n="auto"===this._config.method?e:this._config.method,o=n===Po?this._getScrollTop():0;this._offsets=[],this._targets=[],this._scrollHeight=this._getScrollHeight(),[].slice.call(document.querySelectorAll(this._selector)).map((function(t){var e,M=f.getSelectorFromElement(t);if(M&&(e=document.querySelector(M)),e){var b=e.getBoundingClientRect();if(b.width||b.height)return[p.default(e)[n]().top+o,M]}return null})).filter(Boolean).sort((function(t,e){return t[0]-e[0]})).forEach((function(e){t._offsets.push(e[0]),t._targets.push(e[1])}))},e.dispose=function(){p.default.removeData(this._element,To),p.default(this._scrollElement).off(Bo),this._element=null,this._scrollElement=null,this._config=null,this._selector=null,this._offsets=null,this._targets=null,this._activeTarget=null,this._scrollHeight=null},e._getConfig=function(t){if("string"!=typeof(t=r({},Ko,"object"==typeof t&&t?t:{})).target&&f.isElement(t.target)){var e=p.default(t.target).attr("id");e||(e=f.getUID(No),p.default(t.target).attr("id",e)),t.target="#"+e}return f.typeCheckConfig(No,t,Zo),t},e._getScrollTop=function(){return this._scrollElement===window?this._scrollElement.pageYOffset:this._scrollElement.scrollTop},e._getScrollHeight=function(){return this._scrollElement.scrollHeight||Math.max(document.body.scrollHeight,document.documentElement.scrollHeight)},e._getOffsetHeight=function(){return this._scrollElement===window?window.innerHeight:this._scrollElement.getBoundingClientRect().height},e._process=function(){var t=this._getScrollTop()+this._config.offset,e=this._getScrollHeight(),n=this._config.offset+e-this._getOffsetHeight();if(this._scrollHeight!==e&&this.refresh(),t>=n){var o=this._targets[this._targets.length-1];this._activeTarget!==o&&this._activate(o)}else{if(this._activeTarget&&t0)return this._activeTarget=null,void this._clear();for(var p=this._offsets.length;p--;)this._activeTarget!==this._targets[p]&&t>=this._offsets[p]&&(void 0===this._offsets[p+1]||t{"use strict";var o=n(9742),p=n(645),M=n(5826);function b(){return r.TYPED_ARRAY_SUPPORT?2147483647:1073741823}function c(t,e){if(b()=b())throw new RangeError("Attempt to allocate Buffer larger than maximum size: 0x"+b().toString(16)+" bytes");return 0|t}function A(t,e){if(r.isBuffer(t))return t.length;if("undefined"!=typeof ArrayBuffer&&"function"==typeof ArrayBuffer.isView&&(ArrayBuffer.isView(t)||t instanceof ArrayBuffer))return t.byteLength;"string"!=typeof t&&(t=""+t);var n=t.length;if(0===n)return 0;for(var o=!1;;)switch(e){case"ascii":case"latin1":case"binary":return n;case"utf8":case"utf-8":case void 0:return P(t).length;case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return 2*n;case"hex":return n>>>1;case"base64":return U(t).length;default:if(o)return P(t).length;e=(""+e).toLowerCase(),o=!0}}function u(t,e,n){var o=!1;if((void 0===e||e<0)&&(e=0),e>this.length)return"";if((void 0===n||n>this.length)&&(n=this.length),n<=0)return"";if((n>>>=0)<=(e>>>=0))return"";for(t||(t="utf8");;)switch(t){case"hex":return E(this,e,n);case"utf8":case"utf-8":return L(this,e,n);case"ascii":return _(this,e,n);case"latin1":case"binary":return N(this,e,n);case"base64":return g(this,e,n);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return T(this,e,n);default:if(o)throw new TypeError("Unknown encoding: "+t);t=(t+"").toLowerCase(),o=!0}}function l(t,e,n){var o=t[e];t[e]=t[n],t[n]=o}function d(t,e,n,o,p){if(0===t.length)return-1;if("string"==typeof n?(o=n,n=0):n>2147483647?n=2147483647:n<-2147483648&&(n=-2147483648),n=+n,isNaN(n)&&(n=p?0:t.length-1),n<0&&(n=t.length+n),n>=t.length){if(p)return-1;n=t.length-1}else if(n<0){if(!p)return-1;n=0}if("string"==typeof e&&(e=r.from(e,o)),r.isBuffer(e))return 0===e.length?-1:f(t,e,n,o,p);if("number"==typeof e)return e&=255,r.TYPED_ARRAY_SUPPORT&&"function"==typeof Uint8Array.prototype.indexOf?p?Uint8Array.prototype.indexOf.call(t,e,n):Uint8Array.prototype.lastIndexOf.call(t,e,n):f(t,[e],n,o,p);throw new TypeError("val must be string, number or Buffer")}function f(t,e,n,o,p){var M,b=1,c=t.length,r=e.length;if(void 0!==o&&("ucs2"===(o=String(o).toLowerCase())||"ucs-2"===o||"utf16le"===o||"utf-16le"===o)){if(t.length<2||e.length<2)return-1;b=2,c/=2,r/=2,n/=2}function z(t,e){return 1===b?t[e]:t.readUInt16BE(e*b)}if(p){var a=-1;for(M=n;Mc&&(n=c-r),M=n;M>=0;M--){for(var i=!0,O=0;Op&&(o=p):o=p;var M=e.length;if(M%2!=0)throw new TypeError("Invalid hex string");o>M/2&&(o=M/2);for(var b=0;b>8,p=n%256,M.push(p),M.push(o);return M}(e,t.length-n),t,n,o)}function g(t,e,n){return 0===e&&n===t.length?o.fromByteArray(t):o.fromByteArray(t.slice(e,n))}function L(t,e,n){n=Math.min(t.length,n);for(var o=[],p=e;p239?4:z>223?3:z>191?2:1;if(p+i<=n)switch(i){case 1:z<128&&(a=z);break;case 2:128==(192&(M=t[p+1]))&&(r=(31&z)<<6|63&M)>127&&(a=r);break;case 3:M=t[p+1],b=t[p+2],128==(192&M)&&128==(192&b)&&(r=(15&z)<<12|(63&M)<<6|63&b)>2047&&(r<55296||r>57343)&&(a=r);break;case 4:M=t[p+1],b=t[p+2],c=t[p+3],128==(192&M)&&128==(192&b)&&128==(192&c)&&(r=(15&z)<<18|(63&M)<<12|(63&b)<<6|63&c)>65535&&r<1114112&&(a=r)}null===a?(a=65533,i=1):a>65535&&(a-=65536,o.push(a>>>10&1023|55296),a=56320|1023&a),o.push(a),p+=i}return function(t){var e=t.length;if(e<=y)return String.fromCharCode.apply(String,t);var n="",o=0;for(;o0&&(t=this.toString("hex",0,n).match(/.{2}/g).join(" "),this.length>n&&(t+=" ... ")),""},r.prototype.compare=function(t,e,n,o,p){if(!r.isBuffer(t))throw new TypeError("Argument must be a Buffer");if(void 0===e&&(e=0),void 0===n&&(n=t?t.length:0),void 0===o&&(o=0),void 0===p&&(p=this.length),e<0||n>t.length||o<0||p>this.length)throw new RangeError("out of range index");if(o>=p&&e>=n)return 0;if(o>=p)return-1;if(e>=n)return 1;if(this===t)return 0;for(var M=(p>>>=0)-(o>>>=0),b=(n>>>=0)-(e>>>=0),c=Math.min(M,b),z=this.slice(o,p),a=t.slice(e,n),i=0;ip)&&(n=p),t.length>0&&(n<0||e<0)||e>this.length)throw new RangeError("Attempt to write outside buffer bounds");o||(o="utf8");for(var M=!1;;)switch(o){case"hex":return q(this,t,e,n);case"utf8":case"utf-8":return h(this,t,e,n);case"ascii":return W(this,t,e,n);case"latin1":case"binary":return v(this,t,e,n);case"base64":return R(this,t,e,n);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return m(this,t,e,n);default:if(M)throw new TypeError("Unknown encoding: "+o);o=(""+o).toLowerCase(),M=!0}},r.prototype.toJSON=function(){return{type:"Buffer",data:Array.prototype.slice.call(this._arr||this,0)}};var y=4096;function _(t,e,n){var o="";n=Math.min(t.length,n);for(var p=e;po)&&(n=o);for(var p="",M=e;Mn)throw new RangeError("Trying to access beyond buffer length")}function C(t,e,n,o,p,M){if(!r.isBuffer(t))throw new TypeError('"buffer" argument must be a Buffer instance');if(e>p||et.length)throw new RangeError("Index out of range")}function w(t,e,n,o){e<0&&(e=65535+e+1);for(var p=0,M=Math.min(t.length-n,2);p>>8*(o?p:1-p)}function S(t,e,n,o){e<0&&(e=4294967295+e+1);for(var p=0,M=Math.min(t.length-n,4);p>>8*(o?p:3-p)&255}function X(t,e,n,o,p,M){if(n+o>t.length)throw new RangeError("Index out of range");if(n<0)throw new RangeError("Index out of range")}function x(t,e,n,o,M){return M||X(t,0,n,4),p.write(t,e,n,o,23,4),n+4}function k(t,e,n,o,M){return M||X(t,0,n,8),p.write(t,e,n,o,52,8),n+8}r.prototype.slice=function(t,e){var n,o=this.length;if((t=~~t)<0?(t+=o)<0&&(t=0):t>o&&(t=o),(e=void 0===e?o:~~e)<0?(e+=o)<0&&(e=0):e>o&&(e=o),e0&&(p*=256);)o+=this[t+--e]*p;return o},r.prototype.readUInt8=function(t,e){return e||B(t,1,this.length),this[t]},r.prototype.readUInt16LE=function(t,e){return e||B(t,2,this.length),this[t]|this[t+1]<<8},r.prototype.readUInt16BE=function(t,e){return e||B(t,2,this.length),this[t]<<8|this[t+1]},r.prototype.readUInt32LE=function(t,e){return e||B(t,4,this.length),(this[t]|this[t+1]<<8|this[t+2]<<16)+16777216*this[t+3]},r.prototype.readUInt32BE=function(t,e){return e||B(t,4,this.length),16777216*this[t]+(this[t+1]<<16|this[t+2]<<8|this[t+3])},r.prototype.readIntLE=function(t,e,n){t|=0,e|=0,n||B(t,e,this.length);for(var o=this[t],p=1,M=0;++M=(p*=128)&&(o-=Math.pow(2,8*e)),o},r.prototype.readIntBE=function(t,e,n){t|=0,e|=0,n||B(t,e,this.length);for(var o=e,p=1,M=this[t+--o];o>0&&(p*=256);)M+=this[t+--o]*p;return M>=(p*=128)&&(M-=Math.pow(2,8*e)),M},r.prototype.readInt8=function(t,e){return e||B(t,1,this.length),128&this[t]?-1*(255-this[t]+1):this[t]},r.prototype.readInt16LE=function(t,e){e||B(t,2,this.length);var n=this[t]|this[t+1]<<8;return 32768&n?4294901760|n:n},r.prototype.readInt16BE=function(t,e){e||B(t,2,this.length);var n=this[t+1]|this[t]<<8;return 32768&n?4294901760|n:n},r.prototype.readInt32LE=function(t,e){return e||B(t,4,this.length),this[t]|this[t+1]<<8|this[t+2]<<16|this[t+3]<<24},r.prototype.readInt32BE=function(t,e){return e||B(t,4,this.length),this[t]<<24|this[t+1]<<16|this[t+2]<<8|this[t+3]},r.prototype.readFloatLE=function(t,e){return e||B(t,4,this.length),p.read(this,t,!0,23,4)},r.prototype.readFloatBE=function(t,e){return e||B(t,4,this.length),p.read(this,t,!1,23,4)},r.prototype.readDoubleLE=function(t,e){return e||B(t,8,this.length),p.read(this,t,!0,52,8)},r.prototype.readDoubleBE=function(t,e){return e||B(t,8,this.length),p.read(this,t,!1,52,8)},r.prototype.writeUIntLE=function(t,e,n,o){(t=+t,e|=0,n|=0,o)||C(this,t,e,n,Math.pow(2,8*n)-1,0);var p=1,M=0;for(this[e]=255&t;++M=0&&(M*=256);)this[e+p]=t/M&255;return e+n},r.prototype.writeUInt8=function(t,e,n){return t=+t,e|=0,n||C(this,t,e,1,255,0),r.TYPED_ARRAY_SUPPORT||(t=Math.floor(t)),this[e]=255&t,e+1},r.prototype.writeUInt16LE=function(t,e,n){return t=+t,e|=0,n||C(this,t,e,2,65535,0),r.TYPED_ARRAY_SUPPORT?(this[e]=255&t,this[e+1]=t>>>8):w(this,t,e,!0),e+2},r.prototype.writeUInt16BE=function(t,e,n){return t=+t,e|=0,n||C(this,t,e,2,65535,0),r.TYPED_ARRAY_SUPPORT?(this[e]=t>>>8,this[e+1]=255&t):w(this,t,e,!1),e+2},r.prototype.writeUInt32LE=function(t,e,n){return t=+t,e|=0,n||C(this,t,e,4,4294967295,0),r.TYPED_ARRAY_SUPPORT?(this[e+3]=t>>>24,this[e+2]=t>>>16,this[e+1]=t>>>8,this[e]=255&t):S(this,t,e,!0),e+4},r.prototype.writeUInt32BE=function(t,e,n){return t=+t,e|=0,n||C(this,t,e,4,4294967295,0),r.TYPED_ARRAY_SUPPORT?(this[e]=t>>>24,this[e+1]=t>>>16,this[e+2]=t>>>8,this[e+3]=255&t):S(this,t,e,!1),e+4},r.prototype.writeIntLE=function(t,e,n,o){if(t=+t,e|=0,!o){var p=Math.pow(2,8*n-1);C(this,t,e,n,p-1,-p)}var M=0,b=1,c=0;for(this[e]=255&t;++M>0)-c&255;return e+n},r.prototype.writeIntBE=function(t,e,n,o){if(t=+t,e|=0,!o){var p=Math.pow(2,8*n-1);C(this,t,e,n,p-1,-p)}var M=n-1,b=1,c=0;for(this[e+M]=255&t;--M>=0&&(b*=256);)t<0&&0===c&&0!==this[e+M+1]&&(c=1),this[e+M]=(t/b>>0)-c&255;return e+n},r.prototype.writeInt8=function(t,e,n){return t=+t,e|=0,n||C(this,t,e,1,127,-128),r.TYPED_ARRAY_SUPPORT||(t=Math.floor(t)),t<0&&(t=255+t+1),this[e]=255&t,e+1},r.prototype.writeInt16LE=function(t,e,n){return t=+t,e|=0,n||C(this,t,e,2,32767,-32768),r.TYPED_ARRAY_SUPPORT?(this[e]=255&t,this[e+1]=t>>>8):w(this,t,e,!0),e+2},r.prototype.writeInt16BE=function(t,e,n){return t=+t,e|=0,n||C(this,t,e,2,32767,-32768),r.TYPED_ARRAY_SUPPORT?(this[e]=t>>>8,this[e+1]=255&t):w(this,t,e,!1),e+2},r.prototype.writeInt32LE=function(t,e,n){return t=+t,e|=0,n||C(this,t,e,4,2147483647,-2147483648),r.TYPED_ARRAY_SUPPORT?(this[e]=255&t,this[e+1]=t>>>8,this[e+2]=t>>>16,this[e+3]=t>>>24):S(this,t,e,!0),e+4},r.prototype.writeInt32BE=function(t,e,n){return t=+t,e|=0,n||C(this,t,e,4,2147483647,-2147483648),t<0&&(t=4294967295+t+1),r.TYPED_ARRAY_SUPPORT?(this[e]=t>>>24,this[e+1]=t>>>16,this[e+2]=t>>>8,this[e+3]=255&t):S(this,t,e,!1),e+4},r.prototype.writeFloatLE=function(t,e,n){return x(this,t,e,!0,n)},r.prototype.writeFloatBE=function(t,e,n){return x(this,t,e,!1,n)},r.prototype.writeDoubleLE=function(t,e,n){return k(this,t,e,!0,n)},r.prototype.writeDoubleBE=function(t,e,n){return k(this,t,e,!1,n)},r.prototype.copy=function(t,e,n,o){if(n||(n=0),o||0===o||(o=this.length),e>=t.length&&(e=t.length),e||(e=0),o>0&&o=this.length)throw new RangeError("sourceStart out of bounds");if(o<0)throw new RangeError("sourceEnd out of bounds");o>this.length&&(o=this.length),t.length-e=0;--p)t[p+e]=this[p+n];else if(M<1e3||!r.TYPED_ARRAY_SUPPORT)for(p=0;p>>=0,n=void 0===n?this.length:n>>>0,t||(t=0),"number"==typeof t)for(M=e;M55295&&n<57344){if(!p){if(n>56319){(e-=3)>-1&&M.push(239,191,189);continue}if(b+1===o){(e-=3)>-1&&M.push(239,191,189);continue}p=n;continue}if(n<56320){(e-=3)>-1&&M.push(239,191,189),p=n;continue}n=65536+(p-55296<<10|n-56320)}else p&&(e-=3)>-1&&M.push(239,191,189);if(p=null,n<128){if((e-=1)<0)break;M.push(n)}else if(n<2048){if((e-=2)<0)break;M.push(n>>6|192,63&n|128)}else if(n<65536){if((e-=3)<0)break;M.push(n>>12|224,n>>6&63|128,63&n|128)}else{if(!(n<1114112))throw new Error("Invalid code point");if((e-=4)<0)break;M.push(n>>18|240,n>>12&63|128,n>>6&63|128,63&n|128)}}return M}function U(t){return o.toByteArray(function(t){if((t=function(t){return t.trim?t.trim():t.replace(/^\s+|\s+$/g,"")}(t).replace(I,"")).length<2)return"";for(;t.length%4!=0;)t+="=";return t}(t))}function j(t,e,n,o){for(var p=0;p=e.length||p>=t.length);++p)e[p+n]=t[p];return p}},640:(t,e,n)=>{"use strict";var o=n(1742),p={"text/plain":"Text","text/html":"Url",default:"Text"};t.exports=function(t,e){var n,M,b,c,r,z=!1;e||(e={}),e.debug;try{if(M=o(),b=document.createRange(),c=document.getSelection(),(r=document.createElement("span")).textContent=t,r.ariaHidden="true",r.style.all="unset",r.style.position="fixed",r.style.top=0,r.style.clip="rect(0, 0, 0, 0)",r.style.whiteSpace="pre",r.style.webkitUserSelect="text",r.style.MozUserSelect="text",r.style.msUserSelect="text",r.style.userSelect="text",r.addEventListener("copy",(function(n){if(n.stopPropagation(),e.format)if(n.preventDefault(),void 0===n.clipboardData){window.clipboardData.clearData();var o=p[e.format]||p.default;window.clipboardData.setData(o,t)}else n.clipboardData.clearData(),n.clipboardData.setData(e.format,t);e.onCopy&&(n.preventDefault(),e.onCopy(n.clipboardData))})),document.body.appendChild(r),b.selectNodeContents(r),c.addRange(b),!document.execCommand("copy"))throw new Error("copy command was unsuccessful");z=!0}catch(o){try{window.clipboardData.setData(e.format||"text",t),e.onCopy&&e.onCopy(window.clipboardData),z=!0}catch(o){n=function(t){var e=(/mac os x/i.test(navigator.userAgent)?"⌘":"Ctrl")+"+C";return t.replace(/#{\s*key\s*}/g,e)}("message"in e?e.message:"Copy to clipboard: #{key}, Enter"),window.prompt(n,t)}}finally{c&&("function"==typeof c.removeRange?c.removeRange(b):c.removeAllRanges()),r&&document.body.removeChild(r),M()}return z}},645:(t,e)=>{e.read=function(t,e,n,o,p){var M,b,c=8*p-o-1,r=(1<>1,a=-7,i=n?p-1:0,O=n?-1:1,s=t[e+i];for(i+=O,M=s&(1<<-a)-1,s>>=-a,a+=c;a>0;M=256*M+t[e+i],i+=O,a-=8);for(b=M&(1<<-a)-1,M>>=-a,a+=o;a>0;b=256*b+t[e+i],i+=O,a-=8);if(0===M)M=1-z;else{if(M===r)return b?NaN:1/0*(s?-1:1);b+=Math.pow(2,o),M-=z}return(s?-1:1)*b*Math.pow(2,M-o)},e.write=function(t,e,n,o,p,M){var b,c,r,z=8*M-p-1,a=(1<>1,O=23===p?Math.pow(2,-24)-Math.pow(2,-77):0,s=o?0:M-1,A=o?1:-1,u=e<0||0===e&&1/e<0?1:0;for(e=Math.abs(e),isNaN(e)||e===1/0?(c=isNaN(e)?1:0,b=a):(b=Math.floor(Math.log(e)/Math.LN2),e*(r=Math.pow(2,-b))<1&&(b--,r*=2),(e+=b+i>=1?O/r:O*Math.pow(2,1-i))*r>=2&&(b++,r/=2),b+i>=a?(c=0,b=a):b+i>=1?(c=(e*r-1)*Math.pow(2,p),b+=i):(c=e*Math.pow(2,i-1)*Math.pow(2,p),b=0));p>=8;t[n+s]=255&c,s+=A,c/=256,p-=8);for(b=b<0;t[n+s]=255&b,s+=A,b/=256,z-=8);t[n+s-A]|=128*u}},5826:t=>{var e={}.toString;t.exports=Array.isArray||function(t){return"[object Array]"==e.call(t)}},9755:function(t,e){var n;!function(e,n){"use strict";"object"==typeof t.exports?t.exports=e.document?n(e,!0):function(t){if(!t.document)throw new Error("jQuery requires a window with a document");return n(t)}:n(e)}("undefined"!=typeof window?window:this,(function(o,p){"use strict";var M=[],b=Object.getPrototypeOf,c=M.slice,r=M.flat?function(t){return M.flat.call(t)}:function(t){return M.concat.apply([],t)},z=M.push,a=M.indexOf,i={},O=i.toString,s=i.hasOwnProperty,A=s.toString,u=A.call(Object),l={},d=function(t){return"function"==typeof t&&"number"!=typeof t.nodeType&&"function"!=typeof t.item},f=function(t){return null!=t&&t===t.window},q=o.document,h={type:!0,src:!0,nonce:!0,noModule:!0};function W(t,e,n){var o,p,M=(n=n||q).createElement("script");if(M.text=t,e)for(o in h)(p=e[o]||e.getAttribute&&e.getAttribute(o))&&M.setAttribute(o,p);n.head.appendChild(M).parentNode.removeChild(M)}function v(t){return null==t?t+"":"object"==typeof t||"function"==typeof t?i[O.call(t)]||"object":typeof t}var R="3.7.1",m=/HTML$/i,g=function(t,e){return new g.fn.init(t,e)};function L(t){var e=!!t&&"length"in t&&t.length,n=v(t);return!d(t)&&!f(t)&&("array"===n||0===e||"number"==typeof e&&e>0&&e-1 in t)}function y(t,e){return t.nodeName&&t.nodeName.toLowerCase()===e.toLowerCase()}g.fn=g.prototype={jquery:R,constructor:g,length:0,toArray:function(){return c.call(this)},get:function(t){return null==t?c.call(this):t<0?this[t+this.length]:this[t]},pushStack:function(t){var e=g.merge(this.constructor(),t);return e.prevObject=this,e},each:function(t){return g.each(this,t)},map:function(t){return this.pushStack(g.map(this,(function(e,n){return t.call(e,n,e)})))},slice:function(){return this.pushStack(c.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},even:function(){return this.pushStack(g.grep(this,(function(t,e){return(e+1)%2})))},odd:function(){return this.pushStack(g.grep(this,(function(t,e){return e%2})))},eq:function(t){var e=this.length,n=+t+(t<0?e:0);return this.pushStack(n>=0&&n+~]|"+T+")"+T+"*"),P=new RegExp(T+"|>"),U=new RegExp(x),j=new RegExp("^"+C+"$"),F={ID:new RegExp("^#("+C+")"),CLASS:new RegExp("^\\.("+C+")"),TAG:new RegExp("^("+C+"|[*])"),ATTR:new RegExp("^"+w),PSEUDO:new RegExp("^"+x),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+T+"*(even|odd|(([+-]|)(\\d*)n|)"+T+"*(?:([+-]|)"+T+"*(\\d+)|))"+T+"*\\)|)","i"),bool:new RegExp("^(?:"+L+")$","i"),needsContext:new RegExp("^"+T+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+T+"*((?:-\\d)?\\d*)"+T+"*\\)|)(?=[^-]|$)","i")},H=/^(?:input|select|textarea|button)$/i,G=/^h\d$/i,Y=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,$=/[+~]/,V=new RegExp("\\\\[\\da-fA-F]{1,6}"+T+"?|\\\\([^\\r\\n\\f])","g"),K=function(t,e){var n="0x"+t.slice(1)-65536;return e||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},Z=function(){rt()},Q=Ot((function(t){return!0===t.disabled&&y(t,"fieldset")}),{dir:"parentNode",next:"legend"});try{u.apply(M=c.call(S.childNodes),S.childNodes),M[S.childNodes.length].nodeType}catch(t){u={apply:function(t,e){X.apply(t,c.call(e))},call:function(t){X.apply(t,c.call(arguments,1))}}}function J(t,e,n,o){var p,M,b,c,z,a,s,A=e&&e.ownerDocument,f=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==f&&9!==f&&11!==f)return n;if(!o&&(rt(e),e=e||r,i)){if(11!==f&&(z=Y.exec(t)))if(p=z[1]){if(9===f){if(!(b=e.getElementById(p)))return n;if(b.id===p)return u.call(n,b),n}else if(A&&(b=A.getElementById(p))&&J.contains(e,b)&&b.id===p)return u.call(n,b),n}else{if(z[2])return u.apply(n,e.getElementsByTagName(t)),n;if((p=z[3])&&e.getElementsByClassName)return u.apply(n,e.getElementsByClassName(p)),n}if(!(R[t+" "]||O&&O.test(t))){if(s=t,A=e,1===f&&(P.test(t)||D.test(t))){for((A=$.test(t)&&ct(e.parentNode)||e)==e&&l.scope||((c=e.getAttribute("id"))?c=g.escapeSelector(c):e.setAttribute("id",c=d)),M=(a=at(t)).length;M--;)a[M]=(c?"#"+c:":scope")+" "+it(a[M]);s=a.join(",")}try{return u.apply(n,A.querySelectorAll(s)),n}catch(e){R(t,!0)}finally{c===d&&e.removeAttribute("id")}}}return ft(t.replace(B,"$1"),e,n,o)}function tt(){var t=[];return function n(o,p){return t.push(o+" ")>e.cacheLength&&delete n[t.shift()],n[o+" "]=p}}function et(t){return t[d]=!0,t}function nt(t){var e=r.createElement("fieldset");try{return!!t(e)}catch(t){return!1}finally{e.parentNode&&e.parentNode.removeChild(e),e=null}}function ot(t){return function(e){return y(e,"input")&&e.type===t}}function pt(t){return function(e){return(y(e,"input")||y(e,"button"))&&e.type===t}}function Mt(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&Q(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function bt(t){return et((function(e){return e=+e,et((function(n,o){for(var p,M=t([],n.length,e),b=M.length;b--;)n[p=M[b]]&&(n[p]=!(o[p]=n[p]))}))}))}function ct(t){return t&&void 0!==t.getElementsByTagName&&t}function rt(t){var n,o=t?t.ownerDocument||t:S;return o!=r&&9===o.nodeType&&o.documentElement?(z=(r=o).documentElement,i=!g.isXMLDoc(r),A=z.matches||z.webkitMatchesSelector||z.msMatchesSelector,z.msMatchesSelector&&S!=r&&(n=r.defaultView)&&n.top!==n&&n.addEventListener("unload",Z),l.getById=nt((function(t){return z.appendChild(t).id=g.expando,!r.getElementsByName||!r.getElementsByName(g.expando).length})),l.disconnectedMatch=nt((function(t){return A.call(t,"*")})),l.scope=nt((function(){return r.querySelectorAll(":scope")})),l.cssHas=nt((function(){try{return r.querySelector(":has(*,:jqfake)"),!1}catch(t){return!0}})),l.getById?(e.filter.ID=function(t){var e=t.replace(V,K);return function(t){return t.getAttribute("id")===e}},e.find.ID=function(t,e){if(void 0!==e.getElementById&&i){var n=e.getElementById(t);return n?[n]:[]}}):(e.filter.ID=function(t){var e=t.replace(V,K);return function(t){var n=void 0!==t.getAttributeNode&&t.getAttributeNode("id");return n&&n.value===e}},e.find.ID=function(t,e){if(void 0!==e.getElementById&&i){var n,o,p,M=e.getElementById(t);if(M){if((n=M.getAttributeNode("id"))&&n.value===t)return[M];for(p=e.getElementsByName(t),o=0;M=p[o++];)if((n=M.getAttributeNode("id"))&&n.value===t)return[M]}return[]}}),e.find.TAG=function(t,e){return void 0!==e.getElementsByTagName?e.getElementsByTagName(t):e.querySelectorAll(t)},e.find.CLASS=function(t,e){if(void 0!==e.getElementsByClassName&&i)return e.getElementsByClassName(t)},O=[],nt((function(t){var e;z.appendChild(t).innerHTML="",t.querySelectorAll("[selected]").length||O.push("\\["+T+"*(?:value|"+L+")"),t.querySelectorAll("[id~="+d+"-]").length||O.push("~="),t.querySelectorAll("a#"+d+"+*").length||O.push(".#.+[+~]"),t.querySelectorAll(":checked").length||O.push(":checked"),(e=r.createElement("input")).setAttribute("type","hidden"),t.appendChild(e).setAttribute("name","D"),z.appendChild(t).disabled=!0,2!==t.querySelectorAll(":disabled").length&&O.push(":enabled",":disabled"),(e=r.createElement("input")).setAttribute("name",""),t.appendChild(e),t.querySelectorAll("[name='']").length||O.push("\\["+T+"*name"+T+"*="+T+"*(?:''|\"\")")})),l.cssHas||O.push(":has"),O=O.length&&new RegExp(O.join("|")),m=function(t,e){if(t===e)return b=!0,0;var n=!t.compareDocumentPosition-!e.compareDocumentPosition;return n||(1&(n=(t.ownerDocument||t)==(e.ownerDocument||e)?t.compareDocumentPosition(e):1)||!l.sortDetached&&e.compareDocumentPosition(t)===n?t===r||t.ownerDocument==S&&J.contains(S,t)?-1:e===r||e.ownerDocument==S&&J.contains(S,e)?1:p?a.call(p,t)-a.call(p,e):0:4&n?-1:1)},r):r}for(t in J.matches=function(t,e){return J(t,null,null,e)},J.matchesSelector=function(t,e){if(rt(t),i&&!R[e+" "]&&(!O||!O.test(e)))try{var n=A.call(t,e);if(n||l.disconnectedMatch||t.document&&11!==t.document.nodeType)return n}catch(t){R(e,!0)}return J(e,r,null,[t]).length>0},J.contains=function(t,e){return(t.ownerDocument||t)!=r&&rt(t),g.contains(t,e)},J.attr=function(t,n){(t.ownerDocument||t)!=r&&rt(t);var o=e.attrHandle[n.toLowerCase()],p=o&&s.call(e.attrHandle,n.toLowerCase())?o(t,n,!i):void 0;return void 0!==p?p:t.getAttribute(n)},J.error=function(t){throw new Error("Syntax error, unrecognized expression: "+t)},g.uniqueSort=function(t){var e,n=[],o=0,M=0;if(b=!l.sortStable,p=!l.sortStable&&c.call(t,0),N.call(t,m),b){for(;e=t[M++];)e===t[M]&&(o=n.push(M));for(;o--;)E.call(t,n[o],1)}return p=null,t},g.fn.uniqueSort=function(){return this.pushStack(g.uniqueSort(c.apply(this)))},e=g.expr={cacheLength:50,createPseudo:et,match:F,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(t){return t[1]=t[1].replace(V,K),t[3]=(t[3]||t[4]||t[5]||"").replace(V,K),"~="===t[2]&&(t[3]=" "+t[3]+" "),t.slice(0,4)},CHILD:function(t){return t[1]=t[1].toLowerCase(),"nth"===t[1].slice(0,3)?(t[3]||J.error(t[0]),t[4]=+(t[4]?t[5]+(t[6]||1):2*("even"===t[3]||"odd"===t[3])),t[5]=+(t[7]+t[8]||"odd"===t[3])):t[3]&&J.error(t[0]),t},PSEUDO:function(t){var e,n=!t[6]&&t[2];return F.CHILD.test(t[0])?null:(t[3]?t[2]=t[4]||t[5]||"":n&&U.test(n)&&(e=at(n,!0))&&(e=n.indexOf(")",n.length-e)-n.length)&&(t[0]=t[0].slice(0,e),t[2]=n.slice(0,e)),t.slice(0,3))}},filter:{TAG:function(t){var e=t.replace(V,K).toLowerCase();return"*"===t?function(){return!0}:function(t){return y(t,e)}},CLASS:function(t){var e=h[t+" "];return e||(e=new RegExp("(^|"+T+")"+t+"("+T+"|$)"))&&h(t,(function(t){return e.test("string"==typeof t.className&&t.className||void 0!==t.getAttribute&&t.getAttribute("class")||"")}))},ATTR:function(t,e,n){return function(o){var p=J.attr(o,t);return null==p?"!="===e:!e||(p+="","="===e?p===n:"!="===e?p!==n:"^="===e?n&&0===p.indexOf(n):"*="===e?n&&p.indexOf(n)>-1:"$="===e?n&&p.slice(-n.length)===n:"~="===e?(" "+p.replace(k," ")+" ").indexOf(n)>-1:"|="===e&&(p===n||p.slice(0,n.length+1)===n+"-"))}},CHILD:function(t,e,n,o,p){var M="nth"!==t.slice(0,3),b="last"!==t.slice(-4),c="of-type"===e;return 1===o&&0===p?function(t){return!!t.parentNode}:function(e,n,r){var z,a,i,O,s,A=M!==b?"nextSibling":"previousSibling",u=e.parentNode,l=c&&e.nodeName.toLowerCase(),q=!r&&!c,h=!1;if(u){if(M){for(;A;){for(i=e;i=i[A];)if(c?y(i,l):1===i.nodeType)return!1;s=A="only"===t&&!s&&"nextSibling"}return!0}if(s=[b?u.firstChild:u.lastChild],b&&q){for(h=(O=(z=(a=u[d]||(u[d]={}))[t]||[])[0]===f&&z[1])&&z[2],i=O&&u.childNodes[O];i=++O&&i&&i[A]||(h=O=0)||s.pop();)if(1===i.nodeType&&++h&&i===e){a[t]=[f,O,h];break}}else if(q&&(h=O=(z=(a=e[d]||(e[d]={}))[t]||[])[0]===f&&z[1]),!1===h)for(;(i=++O&&i&&i[A]||(h=O=0)||s.pop())&&(!(c?y(i,l):1===i.nodeType)||!++h||(q&&((a=i[d]||(i[d]={}))[t]=[f,h]),i!==e)););return(h-=p)===o||h%o==0&&h/o>=0}}},PSEUDO:function(t,n){var o,p=e.pseudos[t]||e.setFilters[t.toLowerCase()]||J.error("unsupported pseudo: "+t);return p[d]?p(n):p.length>1?(o=[t,t,"",n],e.setFilters.hasOwnProperty(t.toLowerCase())?et((function(t,e){for(var o,M=p(t,n),b=M.length;b--;)t[o=a.call(t,M[b])]=!(e[o]=M[b])})):function(t){return p(t,0,o)}):p}},pseudos:{not:et((function(t){var e=[],n=[],o=dt(t.replace(B,"$1"));return o[d]?et((function(t,e,n,p){for(var M,b=o(t,null,p,[]),c=t.length;c--;)(M=b[c])&&(t[c]=!(e[c]=M))})):function(t,p,M){return e[0]=t,o(e,null,M,n),e[0]=null,!n.pop()}})),has:et((function(t){return function(e){return J(t,e).length>0}})),contains:et((function(t){return t=t.replace(V,K),function(e){return(e.textContent||g.text(e)).indexOf(t)>-1}})),lang:et((function(t){return j.test(t||"")||J.error("unsupported lang: "+t),t=t.replace(V,K).toLowerCase(),function(e){var n;do{if(n=i?e.lang:e.getAttribute("xml:lang")||e.getAttribute("lang"))return(n=n.toLowerCase())===t||0===n.indexOf(t+"-")}while((e=e.parentNode)&&1===e.nodeType);return!1}})),target:function(t){var e=o.location&&o.location.hash;return e&&e.slice(1)===t.id},root:function(t){return t===z},focus:function(t){return t===function(){try{return r.activeElement}catch(t){}}()&&r.hasFocus()&&!!(t.type||t.href||~t.tabIndex)},enabled:Mt(!1),disabled:Mt(!0),checked:function(t){return y(t,"input")&&!!t.checked||y(t,"option")&&!!t.selected},selected:function(t){return t.parentNode&&t.parentNode.selectedIndex,!0===t.selected},empty:function(t){for(t=t.firstChild;t;t=t.nextSibling)if(t.nodeType<6)return!1;return!0},parent:function(t){return!e.pseudos.empty(t)},header:function(t){return G.test(t.nodeName)},input:function(t){return H.test(t.nodeName)},button:function(t){return y(t,"input")&&"button"===t.type||y(t,"button")},text:function(t){var e;return y(t,"input")&&"text"===t.type&&(null==(e=t.getAttribute("type"))||"text"===e.toLowerCase())},first:bt((function(){return[0]})),last:bt((function(t,e){return[e-1]})),eq:bt((function(t,e,n){return[n<0?n+e:n]})),even:bt((function(t,e){for(var n=0;ne?e:n;--o>=0;)t.push(o);return t})),gt:bt((function(t,e,n){for(var o=n<0?n+e:n;++o1?function(e,n,o){for(var p=t.length;p--;)if(!t[p](e,n,o))return!1;return!0}:t[0]}function At(t,e,n,o,p){for(var M,b=[],c=0,r=t.length,z=null!=e;c-1&&(M[z]=!(b[z]=O))}}else s=At(s===b?s.splice(d,s.length):s),p?p(null,b,s,r):u.apply(b,s)}))}function lt(t){for(var o,p,M,b=t.length,c=e.relative[t[0].type],r=c||e.relative[" "],z=c?1:0,i=Ot((function(t){return t===o}),r,!0),O=Ot((function(t){return a.call(o,t)>-1}),r,!0),s=[function(t,e,p){var M=!c&&(p||e!=n)||((o=e).nodeType?i(t,e,p):O(t,e,p));return o=null,M}];z1&&st(s),z>1&&it(t.slice(0,z-1).concat({value:" "===t[z-2].type?"*":""})).replace(B,"$1"),p,z0,M=t.length>0,b=function(b,c,z,a,O){var s,A,l,d=0,q="0",h=b&&[],W=[],v=n,R=b||M&&e.find.TAG("*",O),m=f+=null==v?1:Math.random()||.1,L=R.length;for(O&&(n=c==r||c||O);q!==L&&null!=(s=R[q]);q++){if(M&&s){for(A=0,c||s.ownerDocument==r||(rt(s),z=!i);l=t[A++];)if(l(s,c||r,z)){u.call(a,s);break}O&&(f=m)}p&&((s=!l&&s)&&d--,b&&h.push(s))}if(d+=q,p&&q!==d){for(A=0;l=o[A++];)l(h,W,c,z);if(b){if(d>0)for(;q--;)h[q]||W[q]||(W[q]=_.call(a));W=At(W)}u.apply(a,W),O&&!b&&W.length>0&&d+o.length>1&&g.uniqueSort(a)}return O&&(f=m,n=v),h};return p?et(b):b}(b,M)),c.selector=t}return c}function ft(t,n,o,p){var M,b,c,r,z,a="function"==typeof t&&t,O=!p&&at(t=a.selector||t);if(o=o||[],1===O.length){if((b=O[0]=O[0].slice(0)).length>2&&"ID"===(c=b[0]).type&&9===n.nodeType&&i&&e.relative[b[1].type]){if(!(n=(e.find.ID(c.matches[0].replace(V,K),n)||[])[0]))return o;a&&(n=n.parentNode),t=t.slice(b.shift().value.length)}for(M=F.needsContext.test(t)?0:b.length;M--&&(c=b[M],!e.relative[r=c.type]);)if((z=e.find[r])&&(p=z(c.matches[0].replace(V,K),$.test(b[0].type)&&ct(n.parentNode)||n))){if(b.splice(M,1),!(t=p.length&&it(b)))return u.apply(o,p),o;break}}return(a||dt(t,O))(p,n,!i,o,!n||$.test(t)&&ct(n.parentNode)||n),o}zt.prototype=e.filters=e.pseudos,e.setFilters=new zt,l.sortStable=d.split("").sort(m).join("")===d,rt(),l.sortDetached=nt((function(t){return 1&t.compareDocumentPosition(r.createElement("fieldset"))})),g.find=J,g.expr[":"]=g.expr.pseudos,g.unique=g.uniqueSort,J.compile=dt,J.select=ft,J.setDocument=rt,J.tokenize=at,J.escape=g.escapeSelector,J.getText=g.text,J.isXML=g.isXMLDoc,J.selectors=g.expr,J.support=g.support,J.uniqueSort=g.uniqueSort}();var x=function(t,e,n){for(var o=[],p=void 0!==n;(t=t[e])&&9!==t.nodeType;)if(1===t.nodeType){if(p&&g(t).is(n))break;o.push(t)}return o},k=function(t,e){for(var n=[];t;t=t.nextSibling)1===t.nodeType&&t!==e&&n.push(t);return n},I=g.expr.match.needsContext,D=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function P(t,e,n){return d(e)?g.grep(t,(function(t,o){return!!e.call(t,o,t)!==n})):e.nodeType?g.grep(t,(function(t){return t===e!==n})):"string"!=typeof e?g.grep(t,(function(t){return a.call(e,t)>-1!==n})):g.filter(e,t,n)}g.filter=function(t,e,n){var o=e[0];return n&&(t=":not("+t+")"),1===e.length&&1===o.nodeType?g.find.matchesSelector(o,t)?[o]:[]:g.find.matches(t,g.grep(e,(function(t){return 1===t.nodeType})))},g.fn.extend({find:function(t){var e,n,o=this.length,p=this;if("string"!=typeof t)return this.pushStack(g(t).filter((function(){for(e=0;e1?g.uniqueSort(n):n},filter:function(t){return this.pushStack(P(this,t||[],!1))},not:function(t){return this.pushStack(P(this,t||[],!0))},is:function(t){return!!P(this,"string"==typeof t&&I.test(t)?g(t):t||[],!1).length}});var U,j=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/;(g.fn.init=function(t,e,n){var o,p;if(!t)return this;if(n=n||U,"string"==typeof t){if(!(o="<"===t[0]&&">"===t[t.length-1]&&t.length>=3?[null,t,null]:j.exec(t))||!o[1]&&e)return!e||e.jquery?(e||n).find(t):this.constructor(e).find(t);if(o[1]){if(e=e instanceof g?e[0]:e,g.merge(this,g.parseHTML(o[1],e&&e.nodeType?e.ownerDocument||e:q,!0)),D.test(o[1])&&g.isPlainObject(e))for(o in e)d(this[o])?this[o](e[o]):this.attr(o,e[o]);return this}return(p=q.getElementById(o[2]))&&(this[0]=p,this.length=1),this}return t.nodeType?(this[0]=t,this.length=1,this):d(t)?void 0!==n.ready?n.ready(t):t(g):g.makeArray(t,this)}).prototype=g.fn,U=g(q);var F=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function G(t,e){for(;(t=t[e])&&1!==t.nodeType;);return t}g.fn.extend({has:function(t){var e=g(t,this),n=e.length;return this.filter((function(){for(var t=0;t-1:1===n.nodeType&&g.find.matchesSelector(n,t))){M.push(n);break}return this.pushStack(M.length>1?g.uniqueSort(M):M)},index:function(t){return t?"string"==typeof t?a.call(g(t),this[0]):a.call(this,t.jquery?t[0]:t):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(t,e){return this.pushStack(g.uniqueSort(g.merge(this.get(),g(t,e))))},addBack:function(t){return this.add(null==t?this.prevObject:this.prevObject.filter(t))}}),g.each({parent:function(t){var e=t.parentNode;return e&&11!==e.nodeType?e:null},parents:function(t){return x(t,"parentNode")},parentsUntil:function(t,e,n){return x(t,"parentNode",n)},next:function(t){return G(t,"nextSibling")},prev:function(t){return G(t,"previousSibling")},nextAll:function(t){return x(t,"nextSibling")},prevAll:function(t){return x(t,"previousSibling")},nextUntil:function(t,e,n){return x(t,"nextSibling",n)},prevUntil:function(t,e,n){return x(t,"previousSibling",n)},siblings:function(t){return k((t.parentNode||{}).firstChild,t)},children:function(t){return k(t.firstChild)},contents:function(t){return null!=t.contentDocument&&b(t.contentDocument)?t.contentDocument:(y(t,"template")&&(t=t.content||t),g.merge([],t.childNodes))}},(function(t,e){g.fn[t]=function(n,o){var p=g.map(this,e,n);return"Until"!==t.slice(-5)&&(o=n),o&&"string"==typeof o&&(p=g.filter(o,p)),this.length>1&&(H[t]||g.uniqueSort(p),F.test(t)&&p.reverse()),this.pushStack(p)}}));var Y=/[^\x20\t\r\n\f]+/g;function $(t){return t}function V(t){throw t}function K(t,e,n,o){var p;try{t&&d(p=t.promise)?p.call(t).done(e).fail(n):t&&d(p=t.then)?p.call(t,e,n):e.apply(void 0,[t].slice(o))}catch(t){n.apply(void 0,[t])}}g.Callbacks=function(t){t="string"==typeof t?function(t){var e={};return g.each(t.match(Y)||[],(function(t,n){e[n]=!0})),e}(t):g.extend({},t);var e,n,o,p,M=[],b=[],c=-1,r=function(){for(p=p||t.once,o=e=!0;b.length;c=-1)for(n=b.shift();++c-1;)M.splice(n,1),n<=c&&c--})),this},has:function(t){return t?g.inArray(t,M)>-1:M.length>0},empty:function(){return M&&(M=[]),this},disable:function(){return p=b=[],M=n="",this},disabled:function(){return!M},lock:function(){return p=b=[],n||e||(M=n=""),this},locked:function(){return!!p},fireWith:function(t,n){return p||(n=[t,(n=n||[]).slice?n.slice():n],b.push(n),e||r()),this},fire:function(){return z.fireWith(this,arguments),this},fired:function(){return!!o}};return z},g.extend({Deferred:function(t){var e=[["notify","progress",g.Callbacks("memory"),g.Callbacks("memory"),2],["resolve","done",g.Callbacks("once memory"),g.Callbacks("once memory"),0,"resolved"],["reject","fail",g.Callbacks("once memory"),g.Callbacks("once memory"),1,"rejected"]],n="pending",p={state:function(){return n},always:function(){return M.done(arguments).fail(arguments),this},catch:function(t){return p.then(null,t)},pipe:function(){var t=arguments;return g.Deferred((function(n){g.each(e,(function(e,o){var p=d(t[o[4]])&&t[o[4]];M[o[1]]((function(){var t=p&&p.apply(this,arguments);t&&d(t.promise)?t.promise().progress(n.notify).done(n.resolve).fail(n.reject):n[o[0]+"With"](this,p?[t]:arguments)}))})),t=null})).promise()},then:function(t,n,p){var M=0;function b(t,e,n,p){return function(){var c=this,r=arguments,z=function(){var o,z;if(!(t=M&&(n!==V&&(c=void 0,r=[o]),e.rejectWith(c,r))}};t?a():(g.Deferred.getErrorHook?a.error=g.Deferred.getErrorHook():g.Deferred.getStackHook&&(a.error=g.Deferred.getStackHook()),o.setTimeout(a))}}return g.Deferred((function(o){e[0][3].add(b(0,o,d(p)?p:$,o.notifyWith)),e[1][3].add(b(0,o,d(t)?t:$)),e[2][3].add(b(0,o,d(n)?n:V))})).promise()},promise:function(t){return null!=t?g.extend(t,p):p}},M={};return g.each(e,(function(t,o){var b=o[2],c=o[5];p[o[1]]=b.add,c&&b.add((function(){n=c}),e[3-t][2].disable,e[3-t][3].disable,e[0][2].lock,e[0][3].lock),b.add(o[3].fire),M[o[0]]=function(){return M[o[0]+"With"](this===M?void 0:this,arguments),this},M[o[0]+"With"]=b.fireWith})),p.promise(M),t&&t.call(M,M),M},when:function(t){var e=arguments.length,n=e,o=Array(n),p=c.call(arguments),M=g.Deferred(),b=function(t){return function(n){o[t]=this,p[t]=arguments.length>1?c.call(arguments):n,--e||M.resolveWith(o,p)}};if(e<=1&&(K(t,M.done(b(n)).resolve,M.reject,!e),"pending"===M.state()||d(p[n]&&p[n].then)))return M.then();for(;n--;)K(p[n],b(n),M.reject);return M.promise()}});var Z=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;g.Deferred.exceptionHook=function(t,e){o.console&&o.console.warn&&t&&Z.test(t.name)&&o.console.warn("jQuery.Deferred exception: "+t.message,t.stack,e)},g.readyException=function(t){o.setTimeout((function(){throw t}))};var Q=g.Deferred();function J(){q.removeEventListener("DOMContentLoaded",J),o.removeEventListener("load",J),g.ready()}g.fn.ready=function(t){return Q.then(t).catch((function(t){g.readyException(t)})),this},g.extend({isReady:!1,readyWait:1,ready:function(t){(!0===t?--g.readyWait:g.isReady)||(g.isReady=!0,!0!==t&&--g.readyWait>0||Q.resolveWith(q,[g]))}}),g.ready.then=Q.then,"complete"===q.readyState||"loading"!==q.readyState&&!q.documentElement.doScroll?o.setTimeout(g.ready):(q.addEventListener("DOMContentLoaded",J),o.addEventListener("load",J));var tt=function(t,e,n,o,p,M,b){var c=0,r=t.length,z=null==n;if("object"===v(n))for(c in p=!0,n)tt(t,e,c,n[c],!0,M,b);else if(void 0!==o&&(p=!0,d(o)||(b=!0),z&&(b?(e.call(t,o),e=null):(z=e,e=function(t,e,n){return z.call(g(t),n)})),e))for(;c1,null,!0)},removeData:function(t){return this.each((function(){rt.remove(this,t)}))}}),g.extend({queue:function(t,e,n){var o;if(t)return e=(e||"fx")+"queue",o=ct.get(t,e),n&&(!o||Array.isArray(n)?o=ct.access(t,e,g.makeArray(n)):o.push(n)),o||[]},dequeue:function(t,e){e=e||"fx";var n=g.queue(t,e),o=n.length,p=n.shift(),M=g._queueHooks(t,e);"inprogress"===p&&(p=n.shift(),o--),p&&("fx"===e&&n.unshift("inprogress"),delete M.stop,p.call(t,(function(){g.dequeue(t,e)}),M)),!o&&M&&M.empty.fire()},_queueHooks:function(t,e){var n=e+"queueHooks";return ct.get(t,n)||ct.access(t,n,{empty:g.Callbacks("once memory").add((function(){ct.remove(t,[e+"queue",n])}))})}}),g.fn.extend({queue:function(t,e){var n=2;return"string"!=typeof t&&(e=t,t="fx",n--),arguments.length\x20\t\r\n\f]*)/i,yt=/^$|^module$|\/(?:java|ecma)script/i;Rt=q.createDocumentFragment().appendChild(q.createElement("div")),(mt=q.createElement("input")).setAttribute("type","radio"),mt.setAttribute("checked","checked"),mt.setAttribute("name","t"),Rt.appendChild(mt),l.checkClone=Rt.cloneNode(!0).cloneNode(!0).lastChild.checked,Rt.innerHTML="",l.noCloneChecked=!!Rt.cloneNode(!0).lastChild.defaultValue,Rt.innerHTML="",l.option=!!Rt.lastChild;var _t={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function Nt(t,e){var n;return n=void 0!==t.getElementsByTagName?t.getElementsByTagName(e||"*"):void 0!==t.querySelectorAll?t.querySelectorAll(e||"*"):[],void 0===e||e&&y(t,e)?g.merge([t],n):n}function Et(t,e){for(var n=0,o=t.length;n",""]);var Tt=/<|&#?\w+;/;function Bt(t,e,n,o,p){for(var M,b,c,r,z,a,i=e.createDocumentFragment(),O=[],s=0,A=t.length;s-1)p&&p.push(M);else if(z=lt(M),b=Nt(i.appendChild(M),"script"),z&&Et(b),n)for(a=0;M=b[a++];)yt.test(M.type||"")&&n.push(M);return i}var Ct=/^([^.]*)(?:\.(.+)|)/;function wt(){return!0}function St(){return!1}function Xt(t,e,n,o,p,M){var b,c;if("object"==typeof e){for(c in"string"!=typeof n&&(o=o||n,n=void 0),e)Xt(t,c,n,o,e[c],M);return t}if(null==o&&null==p?(p=n,o=n=void 0):null==p&&("string"==typeof n?(p=o,o=void 0):(p=o,o=n,n=void 0)),!1===p)p=St;else if(!p)return t;return 1===M&&(b=p,p=function(t){return g().off(t),b.apply(this,arguments)},p.guid=b.guid||(b.guid=g.guid++)),t.each((function(){g.event.add(this,e,p,o,n)}))}function xt(t,e,n){n?(ct.set(t,e,!1),g.event.add(t,e,{namespace:!1,handler:function(t){var n,o=ct.get(this,e);if(1&t.isTrigger&&this[e]){if(o)(g.event.special[e]||{}).delegateType&&t.stopPropagation();else if(o=c.call(arguments),ct.set(this,e,o),this[e](),n=ct.get(this,e),ct.set(this,e,!1),o!==n)return t.stopImmediatePropagation(),t.preventDefault(),n}else o&&(ct.set(this,e,g.event.trigger(o[0],o.slice(1),this)),t.stopPropagation(),t.isImmediatePropagationStopped=wt)}})):void 0===ct.get(t,e)&&g.event.add(t,e,wt)}g.event={global:{},add:function(t,e,n,o,p){var M,b,c,r,z,a,i,O,s,A,u,l=ct.get(t);if(Mt(t))for(n.handler&&(n=(M=n).handler,p=M.selector),p&&g.find.matchesSelector(ut,p),n.guid||(n.guid=g.guid++),(r=l.events)||(r=l.events=Object.create(null)),(b=l.handle)||(b=l.handle=function(e){return void 0!==g&&g.event.triggered!==e.type?g.event.dispatch.apply(t,arguments):void 0}),z=(e=(e||"").match(Y)||[""]).length;z--;)s=u=(c=Ct.exec(e[z])||[])[1],A=(c[2]||"").split(".").sort(),s&&(i=g.event.special[s]||{},s=(p?i.delegateType:i.bindType)||s,i=g.event.special[s]||{},a=g.extend({type:s,origType:u,data:o,handler:n,guid:n.guid,selector:p,needsContext:p&&g.expr.match.needsContext.test(p),namespace:A.join(".")},M),(O=r[s])||((O=r[s]=[]).delegateCount=0,i.setup&&!1!==i.setup.call(t,o,A,b)||t.addEventListener&&t.addEventListener(s,b)),i.add&&(i.add.call(t,a),a.handler.guid||(a.handler.guid=n.guid)),p?O.splice(O.delegateCount++,0,a):O.push(a),g.event.global[s]=!0)},remove:function(t,e,n,o,p){var M,b,c,r,z,a,i,O,s,A,u,l=ct.hasData(t)&&ct.get(t);if(l&&(r=l.events)){for(z=(e=(e||"").match(Y)||[""]).length;z--;)if(s=u=(c=Ct.exec(e[z])||[])[1],A=(c[2]||"").split(".").sort(),s){for(i=g.event.special[s]||{},O=r[s=(o?i.delegateType:i.bindType)||s]||[],c=c[2]&&new RegExp("(^|\\.)"+A.join("\\.(?:.*\\.|)")+"(\\.|$)"),b=M=O.length;M--;)a=O[M],!p&&u!==a.origType||n&&n.guid!==a.guid||c&&!c.test(a.namespace)||o&&o!==a.selector&&("**"!==o||!a.selector)||(O.splice(M,1),a.selector&&O.delegateCount--,i.remove&&i.remove.call(t,a));b&&!O.length&&(i.teardown&&!1!==i.teardown.call(t,A,l.handle)||g.removeEvent(t,s,l.handle),delete r[s])}else for(s in r)g.event.remove(t,s+e[z],n,o,!0);g.isEmptyObject(r)&&ct.remove(t,"handle events")}},dispatch:function(t){var e,n,o,p,M,b,c=new Array(arguments.length),r=g.event.fix(t),z=(ct.get(this,"events")||Object.create(null))[r.type]||[],a=g.event.special[r.type]||{};for(c[0]=r,e=1;e=1))for(;z!==this;z=z.parentNode||this)if(1===z.nodeType&&("click"!==t.type||!0!==z.disabled)){for(M=[],b={},n=0;n-1:g.find(p,this,null,[z]).length),b[p]&&M.push(o);M.length&&c.push({elem:z,handlers:M})}return z=this,r\s*$/g;function Pt(t,e){return y(t,"table")&&y(11!==e.nodeType?e:e.firstChild,"tr")&&g(t).children("tbody")[0]||t}function Ut(t){return t.type=(null!==t.getAttribute("type"))+"/"+t.type,t}function jt(t){return"true/"===(t.type||"").slice(0,5)?t.type=t.type.slice(5):t.removeAttribute("type"),t}function Ft(t,e){var n,o,p,M,b,c;if(1===e.nodeType){if(ct.hasData(t)&&(c=ct.get(t).events))for(p in ct.remove(e,"handle events"),c)for(n=0,o=c[p].length;n1&&"string"==typeof A&&!l.checkClone&&It.test(A))return t.each((function(p){var M=t.eq(p);u&&(e[0]=A.call(this,p,M.html())),Gt(M,e,n,o)}));if(O&&(M=(p=Bt(e,t[0].ownerDocument,!1,t,o)).firstChild,1===p.childNodes.length&&(p=M),M||o)){for(c=(b=g.map(Nt(p,"script"),Ut)).length;i0&&Et(b,!r&&Nt(t,"script")),c},cleanData:function(t){for(var e,n,o,p=g.event.special,M=0;void 0!==(n=t[M]);M++)if(Mt(n)){if(e=n[ct.expando]){if(e.events)for(o in e.events)p[o]?g.event.remove(n,o):g.removeEvent(n,o,e.handle);n[ct.expando]=void 0}n[rt.expando]&&(n[rt.expando]=void 0)}}}),g.fn.extend({detach:function(t){return Yt(this,t,!0)},remove:function(t){return Yt(this,t)},text:function(t){return tt(this,(function(t){return void 0===t?g.text(this):this.empty().each((function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=t)}))}),null,t,arguments.length)},append:function(){return Gt(this,arguments,(function(t){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||Pt(this,t).appendChild(t)}))},prepend:function(){return Gt(this,arguments,(function(t){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var e=Pt(this,t);e.insertBefore(t,e.firstChild)}}))},before:function(){return Gt(this,arguments,(function(t){this.parentNode&&this.parentNode.insertBefore(t,this)}))},after:function(){return Gt(this,arguments,(function(t){this.parentNode&&this.parentNode.insertBefore(t,this.nextSibling)}))},empty:function(){for(var t,e=0;null!=(t=this[e]);e++)1===t.nodeType&&(g.cleanData(Nt(t,!1)),t.textContent="");return this},clone:function(t,e){return t=null!=t&&t,e=null==e?t:e,this.map((function(){return g.clone(this,t,e)}))},html:function(t){return tt(this,(function(t){var e=this[0]||{},n=0,o=this.length;if(void 0===t&&1===e.nodeType)return e.innerHTML;if("string"==typeof t&&!kt.test(t)&&!_t[(Lt.exec(t)||["",""])[1].toLowerCase()]){t=g.htmlPrefilter(t);try{for(;n=0&&(r+=Math.max(0,Math.ceil(t["offset"+e[0].toUpperCase()+e.slice(1)]-M-r-c-.5))||0),r+z}function ae(t,e,n){var o=Kt(t),p=(!l.boxSizingReliable()||n)&&"border-box"===g.css(t,"boxSizing",!1,o),M=p,b=Jt(t,e,o),c="offset"+e[0].toUpperCase()+e.slice(1);if($t.test(b)){if(!n)return b;b="auto"}return(!l.boxSizingReliable()&&p||!l.reliableTrDimensions()&&y(t,"tr")||"auto"===b||!parseFloat(b)&&"inline"===g.css(t,"display",!1,o))&&t.getClientRects().length&&(p="border-box"===g.css(t,"boxSizing",!1,o),(M=c in t)&&(b=t[c])),(b=parseFloat(b)||0)+ze(t,e,n||(p?"border":"content"),M,o,b)+"px"}function ie(t,e,n,o,p){return new ie.prototype.init(t,e,n,o,p)}g.extend({cssHooks:{opacity:{get:function(t,e){if(e){var n=Jt(t,"opacity");return""===n?"1":n}}}},cssNumber:{animationIterationCount:!0,aspectRatio:!0,borderImageSlice:!0,columnCount:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,gridArea:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnStart:!0,gridRow:!0,gridRowEnd:!0,gridRowStart:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,scale:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,floodOpacity:!0,stopOpacity:!0,strokeMiterlimit:!0,strokeOpacity:!0},cssProps:{},style:function(t,e,n,o){if(t&&3!==t.nodeType&&8!==t.nodeType&&t.style){var p,M,b,c=pt(e),r=Vt.test(e),z=t.style;if(r||(e=pe(c)),b=g.cssHooks[e]||g.cssHooks[c],void 0===n)return b&&"get"in b&&void 0!==(p=b.get(t,!1,o))?p:z[e];"string"===(M=typeof n)&&(p=st.exec(n))&&p[1]&&(n=qt(t,e,p),M="number"),null!=n&&n==n&&("number"!==M||r||(n+=p&&p[3]||(g.cssNumber[c]?"":"px")),l.clearCloneStyle||""!==n||0!==e.indexOf("background")||(z[e]="inherit"),b&&"set"in b&&void 0===(n=b.set(t,n,o))||(r?z.setProperty(e,n):z[e]=n))}},css:function(t,e,n,o){var p,M,b,c=pt(e);return Vt.test(e)||(e=pe(c)),(b=g.cssHooks[e]||g.cssHooks[c])&&"get"in b&&(p=b.get(t,!0,n)),void 0===p&&(p=Jt(t,e,o)),"normal"===p&&e in ce&&(p=ce[e]),""===n||n?(M=parseFloat(p),!0===n||isFinite(M)?M||0:p):p}}),g.each(["height","width"],(function(t,e){g.cssHooks[e]={get:function(t,n,o){if(n)return!Me.test(g.css(t,"display"))||t.getClientRects().length&&t.getBoundingClientRect().width?ae(t,e,o):Zt(t,be,(function(){return ae(t,e,o)}))},set:function(t,n,o){var p,M=Kt(t),b=!l.scrollboxSize()&&"absolute"===M.position,c=(b||o)&&"border-box"===g.css(t,"boxSizing",!1,M),r=o?ze(t,e,o,c,M):0;return c&&b&&(r-=Math.ceil(t["offset"+e[0].toUpperCase()+e.slice(1)]-parseFloat(M[e])-ze(t,e,"border",!1,M)-.5)),r&&(p=st.exec(n))&&"px"!==(p[3]||"px")&&(t.style[e]=n,n=g.css(t,e)),re(0,n,r)}}})),g.cssHooks.marginLeft=te(l.reliableMarginLeft,(function(t,e){if(e)return(parseFloat(Jt(t,"marginLeft"))||t.getBoundingClientRect().left-Zt(t,{marginLeft:0},(function(){return t.getBoundingClientRect().left})))+"px"})),g.each({margin:"",padding:"",border:"Width"},(function(t,e){g.cssHooks[t+e]={expand:function(n){for(var o=0,p={},M="string"==typeof n?n.split(" "):[n];o<4;o++)p[t+At[o]+e]=M[o]||M[o-2]||M[0];return p}},"margin"!==t&&(g.cssHooks[t+e].set=re)})),g.fn.extend({css:function(t,e){return tt(this,(function(t,e,n){var o,p,M={},b=0;if(Array.isArray(e)){for(o=Kt(t),p=e.length;b1)}}),g.Tween=ie,ie.prototype={constructor:ie,init:function(t,e,n,o,p,M){this.elem=t,this.prop=n,this.easing=p||g.easing._default,this.options=e,this.start=this.now=this.cur(),this.end=o,this.unit=M||(g.cssNumber[n]?"":"px")},cur:function(){var t=ie.propHooks[this.prop];return t&&t.get?t.get(this):ie.propHooks._default.get(this)},run:function(t){var e,n=ie.propHooks[this.prop];return this.options.duration?this.pos=e=g.easing[this.easing](t,this.options.duration*t,0,1,this.options.duration):this.pos=e=t,this.now=(this.end-this.start)*e+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):ie.propHooks._default.set(this),this}},ie.prototype.init.prototype=ie.prototype,ie.propHooks={_default:{get:function(t){var e;return 1!==t.elem.nodeType||null!=t.elem[t.prop]&&null==t.elem.style[t.prop]?t.elem[t.prop]:(e=g.css(t.elem,t.prop,""))&&"auto"!==e?e:0},set:function(t){g.fx.step[t.prop]?g.fx.step[t.prop](t):1!==t.elem.nodeType||!g.cssHooks[t.prop]&&null==t.elem.style[pe(t.prop)]?t.elem[t.prop]=t.now:g.style(t.elem,t.prop,t.now+t.unit)}}},ie.propHooks.scrollTop=ie.propHooks.scrollLeft={set:function(t){t.elem.nodeType&&t.elem.parentNode&&(t.elem[t.prop]=t.now)}},g.easing={linear:function(t){return t},swing:function(t){return.5-Math.cos(t*Math.PI)/2},_default:"swing"},g.fx=ie.prototype.init,g.fx.step={};var Oe,se,Ae=/^(?:toggle|show|hide)$/,ue=/queueHooks$/;function le(){se&&(!1===q.hidden&&o.requestAnimationFrame?o.requestAnimationFrame(le):o.setTimeout(le,g.fx.interval),g.fx.tick())}function de(){return o.setTimeout((function(){Oe=void 0})),Oe=Date.now()}function fe(t,e){var n,o=0,p={height:t};for(e=e?1:0;o<4;o+=2-e)p["margin"+(n=At[o])]=p["padding"+n]=t;return e&&(p.opacity=p.width=t),p}function qe(t,e,n){for(var o,p=(he.tweeners[e]||[]).concat(he.tweeners["*"]),M=0,b=p.length;M1)},removeAttr:function(t){return this.each((function(){g.removeAttr(this,t)}))}}),g.extend({attr:function(t,e,n){var o,p,M=t.nodeType;if(3!==M&&8!==M&&2!==M)return void 0===t.getAttribute?g.prop(t,e,n):(1===M&&g.isXMLDoc(t)||(p=g.attrHooks[e.toLowerCase()]||(g.expr.match.bool.test(e)?We:void 0)),void 0!==n?null===n?void g.removeAttr(t,e):p&&"set"in p&&void 0!==(o=p.set(t,n,e))?o:(t.setAttribute(e,n+""),n):p&&"get"in p&&null!==(o=p.get(t,e))?o:null==(o=g.find.attr(t,e))?void 0:o)},attrHooks:{type:{set:function(t,e){if(!l.radioValue&&"radio"===e&&y(t,"input")){var n=t.value;return t.setAttribute("type",e),n&&(t.value=n),e}}}},removeAttr:function(t,e){var n,o=0,p=e&&e.match(Y);if(p&&1===t.nodeType)for(;n=p[o++];)t.removeAttribute(n)}}),We={set:function(t,e,n){return!1===e?g.removeAttr(t,n):t.setAttribute(n,n),n}},g.each(g.expr.match.bool.source.match(/\w+/g),(function(t,e){var n=ve[e]||g.find.attr;ve[e]=function(t,e,o){var p,M,b=e.toLowerCase();return o||(M=ve[b],ve[b]=p,p=null!=n(t,e,o)?b:null,ve[b]=M),p}}));var Re=/^(?:input|select|textarea|button)$/i,me=/^(?:a|area)$/i;function ge(t){return(t.match(Y)||[]).join(" ")}function Le(t){return t.getAttribute&&t.getAttribute("class")||""}function ye(t){return Array.isArray(t)?t:"string"==typeof t&&t.match(Y)||[]}g.fn.extend({prop:function(t,e){return tt(this,g.prop,t,e,arguments.length>1)},removeProp:function(t){return this.each((function(){delete this[g.propFix[t]||t]}))}}),g.extend({prop:function(t,e,n){var o,p,M=t.nodeType;if(3!==M&&8!==M&&2!==M)return 1===M&&g.isXMLDoc(t)||(e=g.propFix[e]||e,p=g.propHooks[e]),void 0!==n?p&&"set"in p&&void 0!==(o=p.set(t,n,e))?o:t[e]=n:p&&"get"in p&&null!==(o=p.get(t,e))?o:t[e]},propHooks:{tabIndex:{get:function(t){var e=g.find.attr(t,"tabindex");return e?parseInt(e,10):Re.test(t.nodeName)||me.test(t.nodeName)&&t.href?0:-1}}},propFix:{for:"htmlFor",class:"className"}}),l.optSelected||(g.propHooks.selected={get:function(t){var e=t.parentNode;return e&&e.parentNode&&e.parentNode.selectedIndex,null},set:function(t){var e=t.parentNode;e&&(e.selectedIndex,e.parentNode&&e.parentNode.selectedIndex)}}),g.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],(function(){g.propFix[this.toLowerCase()]=this})),g.fn.extend({addClass:function(t){var e,n,o,p,M,b;return d(t)?this.each((function(e){g(this).addClass(t.call(this,e,Le(this)))})):(e=ye(t)).length?this.each((function(){if(o=Le(this),n=1===this.nodeType&&" "+ge(o)+" "){for(M=0;M-1;)n=n.replace(" "+p+" "," ");b=ge(n),o!==b&&this.setAttribute("class",b)}})):this:this.attr("class","")},toggleClass:function(t,e){var n,o,p,M,b=typeof t,c="string"===b||Array.isArray(t);return d(t)?this.each((function(n){g(this).toggleClass(t.call(this,n,Le(this),e),e)})):"boolean"==typeof e&&c?e?this.addClass(t):this.removeClass(t):(n=ye(t),this.each((function(){if(c)for(M=g(this),p=0;p-1)return!0;return!1}});var _e=/\r/g;g.fn.extend({val:function(t){var e,n,o,p=this[0];return arguments.length?(o=d(t),this.each((function(n){var p;1===this.nodeType&&(null==(p=o?t.call(this,n,g(this).val()):t)?p="":"number"==typeof p?p+="":Array.isArray(p)&&(p=g.map(p,(function(t){return null==t?"":t+""}))),(e=g.valHooks[this.type]||g.valHooks[this.nodeName.toLowerCase()])&&"set"in e&&void 0!==e.set(this,p,"value")||(this.value=p))}))):p?(e=g.valHooks[p.type]||g.valHooks[p.nodeName.toLowerCase()])&&"get"in e&&void 0!==(n=e.get(p,"value"))?n:"string"==typeof(n=p.value)?n.replace(_e,""):null==n?"":n:void 0}}),g.extend({valHooks:{option:{get:function(t){var e=g.find.attr(t,"value");return null!=e?e:ge(g.text(t))}},select:{get:function(t){var e,n,o,p=t.options,M=t.selectedIndex,b="select-one"===t.type,c=b?null:[],r=b?M+1:p.length;for(o=M<0?r:b?M:0;o-1)&&(n=!0);return n||(t.selectedIndex=-1),M}}}}),g.each(["radio","checkbox"],(function(){g.valHooks[this]={set:function(t,e){if(Array.isArray(e))return t.checked=g.inArray(g(t).val(),e)>-1}},l.checkOn||(g.valHooks[this].get=function(t){return null===t.getAttribute("value")?"on":t.value})}));var Ne=o.location,Ee={guid:Date.now()},Te=/\?/;g.parseXML=function(t){var e,n;if(!t||"string"!=typeof t)return null;try{e=(new o.DOMParser).parseFromString(t,"text/xml")}catch(t){}return n=e&&e.getElementsByTagName("parsererror")[0],e&&!n||g.error("Invalid XML: "+(n?g.map(n.childNodes,(function(t){return t.textContent})).join("\n"):t)),e};var Be=/^(?:focusinfocus|focusoutblur)$/,Ce=function(t){t.stopPropagation()};g.extend(g.event,{trigger:function(t,e,n,p){var M,b,c,r,z,a,i,O,A=[n||q],u=s.call(t,"type")?t.type:t,l=s.call(t,"namespace")?t.namespace.split("."):[];if(b=O=c=n=n||q,3!==n.nodeType&&8!==n.nodeType&&!Be.test(u+g.event.triggered)&&(u.indexOf(".")>-1&&(l=u.split("."),u=l.shift(),l.sort()),z=u.indexOf(":")<0&&"on"+u,(t=t[g.expando]?t:new g.Event(u,"object"==typeof t&&t)).isTrigger=p?2:3,t.namespace=l.join("."),t.rnamespace=t.namespace?new RegExp("(^|\\.)"+l.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,t.result=void 0,t.target||(t.target=n),e=null==e?[t]:g.makeArray(e,[t]),i=g.event.special[u]||{},p||!i.trigger||!1!==i.trigger.apply(n,e))){if(!p&&!i.noBubble&&!f(n)){for(r=i.delegateType||u,Be.test(r+u)||(b=b.parentNode);b;b=b.parentNode)A.push(b),c=b;c===(n.ownerDocument||q)&&A.push(c.defaultView||c.parentWindow||o)}for(M=0;(b=A[M++])&&!t.isPropagationStopped();)O=b,t.type=M>1?r:i.bindType||u,(a=(ct.get(b,"events")||Object.create(null))[t.type]&&ct.get(b,"handle"))&&a.apply(b,e),(a=z&&b[z])&&a.apply&&Mt(b)&&(t.result=a.apply(b,e),!1===t.result&&t.preventDefault());return t.type=u,p||t.isDefaultPrevented()||i._default&&!1!==i._default.apply(A.pop(),e)||!Mt(n)||z&&d(n[u])&&!f(n)&&((c=n[z])&&(n[z]=null),g.event.triggered=u,t.isPropagationStopped()&&O.addEventListener(u,Ce),n[u](),t.isPropagationStopped()&&O.removeEventListener(u,Ce),g.event.triggered=void 0,c&&(n[z]=c)),t.result}},simulate:function(t,e,n){var o=g.extend(new g.Event,n,{type:t,isSimulated:!0});g.event.trigger(o,null,e)}}),g.fn.extend({trigger:function(t,e){return this.each((function(){g.event.trigger(t,e,this)}))},triggerHandler:function(t,e){var n=this[0];if(n)return g.event.trigger(t,e,n,!0)}});var we=/\[\]$/,Se=/\r?\n/g,Xe=/^(?:submit|button|image|reset|file)$/i,xe=/^(?:input|select|textarea|keygen)/i;function ke(t,e,n,o){var p;if(Array.isArray(e))g.each(e,(function(e,p){n||we.test(t)?o(t,p):ke(t+"["+("object"==typeof p&&null!=p?e:"")+"]",p,n,o)}));else if(n||"object"!==v(e))o(t,e);else for(p in e)ke(t+"["+p+"]",e[p],n,o)}g.param=function(t,e){var n,o=[],p=function(t,e){var n=d(e)?e():e;o[o.length]=encodeURIComponent(t)+"="+encodeURIComponent(null==n?"":n)};if(null==t)return"";if(Array.isArray(t)||t.jquery&&!g.isPlainObject(t))g.each(t,(function(){p(this.name,this.value)}));else for(n in t)ke(n,t[n],e,p);return o.join("&")},g.fn.extend({serialize:function(){return g.param(this.serializeArray())},serializeArray:function(){return this.map((function(){var t=g.prop(this,"elements");return t?g.makeArray(t):this})).filter((function(){var t=this.type;return this.name&&!g(this).is(":disabled")&&xe.test(this.nodeName)&&!Xe.test(t)&&(this.checked||!gt.test(t))})).map((function(t,e){var n=g(this).val();return null==n?null:Array.isArray(n)?g.map(n,(function(t){return{name:e.name,value:t.replace(Se,"\r\n")}})):{name:e.name,value:n.replace(Se,"\r\n")}})).get()}});var Ie=/%20/g,De=/#.*$/,Pe=/([?&])_=[^&]*/,Ue=/^(.*?):[ \t]*([^\r\n]*)$/gm,je=/^(?:GET|HEAD)$/,Fe=/^\/\//,He={},Ge={},Ye="*/".concat("*"),$e=q.createElement("a");function Ve(t){return function(e,n){"string"!=typeof e&&(n=e,e="*");var o,p=0,M=e.toLowerCase().match(Y)||[];if(d(n))for(;o=M[p++];)"+"===o[0]?(o=o.slice(1)||"*",(t[o]=t[o]||[]).unshift(n)):(t[o]=t[o]||[]).push(n)}}function Ke(t,e,n,o){var p={},M=t===Ge;function b(c){var r;return p[c]=!0,g.each(t[c]||[],(function(t,c){var z=c(e,n,o);return"string"!=typeof z||M||p[z]?M?!(r=z):void 0:(e.dataTypes.unshift(z),b(z),!1)})),r}return b(e.dataTypes[0])||!p["*"]&&b("*")}function Ze(t,e){var n,o,p=g.ajaxSettings.flatOptions||{};for(n in e)void 0!==e[n]&&((p[n]?t:o||(o={}))[n]=e[n]);return o&&g.extend(!0,t,o),t}$e.href=Ne.href,g.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:Ne.href,type:"GET",isLocal:/^(?:about|app|app-storage|.+-extension|file|res|widget):$/.test(Ne.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Ye,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":g.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(t,e){return e?Ze(Ze(t,g.ajaxSettings),e):Ze(g.ajaxSettings,t)},ajaxPrefilter:Ve(He),ajaxTransport:Ve(Ge),ajax:function(t,e){"object"==typeof t&&(e=t,t=void 0),e=e||{};var n,p,M,b,c,r,z,a,i,O,s=g.ajaxSetup({},e),A=s.context||s,u=s.context&&(A.nodeType||A.jquery)?g(A):g.event,l=g.Deferred(),d=g.Callbacks("once memory"),f=s.statusCode||{},h={},W={},v="canceled",R={readyState:0,getResponseHeader:function(t){var e;if(z){if(!b)for(b={};e=Ue.exec(M);)b[e[1].toLowerCase()+" "]=(b[e[1].toLowerCase()+" "]||[]).concat(e[2]);e=b[t.toLowerCase()+" "]}return null==e?null:e.join(", ")},getAllResponseHeaders:function(){return z?M:null},setRequestHeader:function(t,e){return null==z&&(t=W[t.toLowerCase()]=W[t.toLowerCase()]||t,h[t]=e),this},overrideMimeType:function(t){return null==z&&(s.mimeType=t),this},statusCode:function(t){var e;if(t)if(z)R.always(t[R.status]);else for(e in t)f[e]=[f[e],t[e]];return this},abort:function(t){var e=t||v;return n&&n.abort(e),m(0,e),this}};if(l.promise(R),s.url=((t||s.url||Ne.href)+"").replace(Fe,Ne.protocol+"//"),s.type=e.method||e.type||s.method||s.type,s.dataTypes=(s.dataType||"*").toLowerCase().match(Y)||[""],null==s.crossDomain){r=q.createElement("a");try{r.href=s.url,r.href=r.href,s.crossDomain=$e.protocol+"//"+$e.host!=r.protocol+"//"+r.host}catch(t){s.crossDomain=!0}}if(s.data&&s.processData&&"string"!=typeof s.data&&(s.data=g.param(s.data,s.traditional)),Ke(He,s,e,R),z)return R;for(i in(a=g.event&&s.global)&&0==g.active++&&g.event.trigger("ajaxStart"),s.type=s.type.toUpperCase(),s.hasContent=!je.test(s.type),p=s.url.replace(De,""),s.hasContent?s.data&&s.processData&&0===(s.contentType||"").indexOf("application/x-www-form-urlencoded")&&(s.data=s.data.replace(Ie,"+")):(O=s.url.slice(p.length),s.data&&(s.processData||"string"==typeof s.data)&&(p+=(Te.test(p)?"&":"?")+s.data,delete s.data),!1===s.cache&&(p=p.replace(Pe,"$1"),O=(Te.test(p)?"&":"?")+"_="+Ee.guid+++O),s.url=p+O),s.ifModified&&(g.lastModified[p]&&R.setRequestHeader("If-Modified-Since",g.lastModified[p]),g.etag[p]&&R.setRequestHeader("If-None-Match",g.etag[p])),(s.data&&s.hasContent&&!1!==s.contentType||e.contentType)&&R.setRequestHeader("Content-Type",s.contentType),R.setRequestHeader("Accept",s.dataTypes[0]&&s.accepts[s.dataTypes[0]]?s.accepts[s.dataTypes[0]]+("*"!==s.dataTypes[0]?", "+Ye+"; q=0.01":""):s.accepts["*"]),s.headers)R.setRequestHeader(i,s.headers[i]);if(s.beforeSend&&(!1===s.beforeSend.call(A,R,s)||z))return R.abort();if(v="abort",d.add(s.complete),R.done(s.success),R.fail(s.error),n=Ke(Ge,s,e,R)){if(R.readyState=1,a&&u.trigger("ajaxSend",[R,s]),z)return R;s.async&&s.timeout>0&&(c=o.setTimeout((function(){R.abort("timeout")}),s.timeout));try{z=!1,n.send(h,m)}catch(t){if(z)throw t;m(-1,t)}}else m(-1,"No Transport");function m(t,e,b,r){var i,O,q,h,W,v=e;z||(z=!0,c&&o.clearTimeout(c),n=void 0,M=r||"",R.readyState=t>0?4:0,i=t>=200&&t<300||304===t,b&&(h=function(t,e,n){for(var o,p,M,b,c=t.contents,r=t.dataTypes;"*"===r[0];)r.shift(),void 0===o&&(o=t.mimeType||e.getResponseHeader("Content-Type"));if(o)for(p in c)if(c[p]&&c[p].test(o)){r.unshift(p);break}if(r[0]in n)M=r[0];else{for(p in n){if(!r[0]||t.converters[p+" "+r[0]]){M=p;break}b||(b=p)}M=M||b}if(M)return M!==r[0]&&r.unshift(M),n[M]}(s,R,b)),!i&&g.inArray("script",s.dataTypes)>-1&&g.inArray("json",s.dataTypes)<0&&(s.converters["text script"]=function(){}),h=function(t,e,n,o){var p,M,b,c,r,z={},a=t.dataTypes.slice();if(a[1])for(b in t.converters)z[b.toLowerCase()]=t.converters[b];for(M=a.shift();M;)if(t.responseFields[M]&&(n[t.responseFields[M]]=e),!r&&o&&t.dataFilter&&(e=t.dataFilter(e,t.dataType)),r=M,M=a.shift())if("*"===M)M=r;else if("*"!==r&&r!==M){if(!(b=z[r+" "+M]||z["* "+M]))for(p in z)if((c=p.split(" "))[1]===M&&(b=z[r+" "+c[0]]||z["* "+c[0]])){!0===b?b=z[p]:!0!==z[p]&&(M=c[0],a.unshift(c[1]));break}if(!0!==b)if(b&&t.throws)e=b(e);else try{e=b(e)}catch(t){return{state:"parsererror",error:b?t:"No conversion from "+r+" to "+M}}}return{state:"success",data:e}}(s,h,R,i),i?(s.ifModified&&((W=R.getResponseHeader("Last-Modified"))&&(g.lastModified[p]=W),(W=R.getResponseHeader("etag"))&&(g.etag[p]=W)),204===t||"HEAD"===s.type?v="nocontent":304===t?v="notmodified":(v=h.state,O=h.data,i=!(q=h.error))):(q=v,!t&&v||(v="error",t<0&&(t=0))),R.status=t,R.statusText=(e||v)+"",i?l.resolveWith(A,[O,v,R]):l.rejectWith(A,[R,v,q]),R.statusCode(f),f=void 0,a&&u.trigger(i?"ajaxSuccess":"ajaxError",[R,s,i?O:q]),d.fireWith(A,[R,v]),a&&(u.trigger("ajaxComplete",[R,s]),--g.active||g.event.trigger("ajaxStop")))}return R},getJSON:function(t,e,n){return g.get(t,e,n,"json")},getScript:function(t,e){return g.get(t,void 0,e,"script")}}),g.each(["get","post"],(function(t,e){g[e]=function(t,n,o,p){return d(n)&&(p=p||o,o=n,n=void 0),g.ajax(g.extend({url:t,type:e,dataType:p,data:n,success:o},g.isPlainObject(t)&&t))}})),g.ajaxPrefilter((function(t){var e;for(e in t.headers)"content-type"===e.toLowerCase()&&(t.contentType=t.headers[e]||"")})),g._evalUrl=function(t,e,n){return g.ajax({url:t,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,converters:{"text script":function(){}},dataFilter:function(t){g.globalEval(t,e,n)}})},g.fn.extend({wrapAll:function(t){var e;return this[0]&&(d(t)&&(t=t.call(this[0])),e=g(t,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&e.insertBefore(this[0]),e.map((function(){for(var t=this;t.firstElementChild;)t=t.firstElementChild;return t})).append(this)),this},wrapInner:function(t){return d(t)?this.each((function(e){g(this).wrapInner(t.call(this,e))})):this.each((function(){var e=g(this),n=e.contents();n.length?n.wrapAll(t):e.append(t)}))},wrap:function(t){var e=d(t);return this.each((function(n){g(this).wrapAll(e?t.call(this,n):t)}))},unwrap:function(t){return this.parent(t).not("body").each((function(){g(this).replaceWith(this.childNodes)})),this}}),g.expr.pseudos.hidden=function(t){return!g.expr.pseudos.visible(t)},g.expr.pseudos.visible=function(t){return!!(t.offsetWidth||t.offsetHeight||t.getClientRects().length)},g.ajaxSettings.xhr=function(){try{return new o.XMLHttpRequest}catch(t){}};var Qe={0:200,1223:204},Je=g.ajaxSettings.xhr();l.cors=!!Je&&"withCredentials"in Je,l.ajax=Je=!!Je,g.ajaxTransport((function(t){var e,n;if(l.cors||Je&&!t.crossDomain)return{send:function(p,M){var b,c=t.xhr();if(c.open(t.type,t.url,t.async,t.username,t.password),t.xhrFields)for(b in t.xhrFields)c[b]=t.xhrFields[b];for(b in t.mimeType&&c.overrideMimeType&&c.overrideMimeType(t.mimeType),t.crossDomain||p["X-Requested-With"]||(p["X-Requested-With"]="XMLHttpRequest"),p)c.setRequestHeader(b,p[b]);e=function(t){return function(){e&&(e=n=c.onload=c.onerror=c.onabort=c.ontimeout=c.onreadystatechange=null,"abort"===t?c.abort():"error"===t?"number"!=typeof c.status?M(0,"error"):M(c.status,c.statusText):M(Qe[c.status]||c.status,c.statusText,"text"!==(c.responseType||"text")||"string"!=typeof c.responseText?{binary:c.response}:{text:c.responseText},c.getAllResponseHeaders()))}},c.onload=e(),n=c.onerror=c.ontimeout=e("error"),void 0!==c.onabort?c.onabort=n:c.onreadystatechange=function(){4===c.readyState&&o.setTimeout((function(){e&&n()}))},e=e("abort");try{c.send(t.hasContent&&t.data||null)}catch(t){if(e)throw t}},abort:function(){e&&e()}}})),g.ajaxPrefilter((function(t){t.crossDomain&&(t.contents.script=!1)})),g.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(t){return g.globalEval(t),t}}}),g.ajaxPrefilter("script",(function(t){void 0===t.cache&&(t.cache=!1),t.crossDomain&&(t.type="GET")})),g.ajaxTransport("script",(function(t){var e,n;if(t.crossDomain||t.scriptAttrs)return{send:function(o,p){e=g(" {{-- Get IPTABLES --}} diff --git a/resources/views/livewire/project/application/deployment/index.blade.php b/resources/views/livewire/project/application/deployment/index.blade.php index 001499b71..f6fdb64ab 100644 --- a/resources/views/livewire/project/application/deployment/index.blade.php +++ b/resources/views/livewire/project/application/deployment/index.blade.php @@ -31,11 +31,12 @@ @endif @forelse ($deployments as $deployment)
+ 'dark:bg-coolgray-100 p-2 border-l-2 transition-colors hover:no-underline box-without-bg-without-border bg-white flex-col cursor-pointer dark:hover:text-neutral-400 dark:hover:bg-coolgray-200', + 'border-warning border-dashed ' => data_get($deployment, 'status') === 'in_progress' || data_get($deployment, 'status') === 'cancelled-by-user', - 'border-error' => data_get($deployment, 'status') === 'failed', + 'border-error border-dashed ' => + data_get($deployment, 'status') === 'failed', 'border-success' => data_get($deployment, 'status') === 'finished', ]) x-on:click.stop="goto('{{ $current_url . '/' . data_get($deployment, 'deployment_uuid') }}')"> @@ -106,7 +107,7 @@
@if ($deployment->status !== 'in_progress') - Finished 0s in + Finished 0s ago in 0s @else Running for 0s @@ -157,7 +158,7 @@ } }, measure_since_started() { - return dayjs.utc(created_at).fromNow(); + return dayjs.utc(created_at).fromNow(true); // "true" prevents the "ago" suffix }, })) diff --git a/resources/views/livewire/project/application/deployment/show.blade.php b/resources/views/livewire/project/application/deployment/show.blade.php index f97914ec2..9d9301d5c 100644 --- a/resources/views/livewire/project/application/deployment/show.blade.php +++ b/resources/views/livewire/project/application/deployment/show.blade.php @@ -9,6 +9,7 @@ fullscreen: false, alwaysScroll: false, intervalId: null, + showTimestamps: true, makeFullscreen() { this.fullscreen = !this.fullscreen; if (this.fullscreen === false) { @@ -53,63 +54,69 @@ class="dark:text-warning">{{ Str::headline(data_get($application_deployment_queue, 'status')) }}.
@endif -
+
- - - + class="flex flex-col-reverse w-full p-2 px-4 mt-4 overflow-y-auto bg-white dark:text-white dark:bg-coolgray-100 scrollbar dark:border-coolgray-300" + :class="fullscreen ? '' : 'min-h-14 max-h-[40rem] border border-dotted rounded'"> +
+
+ + + + + +
+
-
- @if (decode_remote_command_output($application_deployment_queue)->count() > 0) - @foreach (decode_remote_command_output($application_deployment_queue) as $line) + @forelse ($this->logLines as $line) +
$line['command'] ?? false, + 'flex gap-2 dark:hover:bg-coolgray-500 hover:bg-gray-100', + ])> + {{ $line['timestamp'] }} $line['hidden'], - 'text-red-500 font-bold whitespace-pre-line' => $line['type'] == 'stderr', - ])>[{{ $line['timestamp'] }}] @if ($line['hidden']) -

[COMMAND] {{ $line['command'] }}
[OUTPUT] - @endif @if (str($line['output'])->contains('http://') || str($line['output'])->contains('https://')) - @php - $line['output'] = preg_replace( - '/(https?:\/\/[^\s]+)/', - '$1', - $line['output'], - ); - @endphp {!! $line['output'] !!} - @else - {{ $line['output'] }} - - @endif -
- @endforeach - @else - No logs yet. - @endif + 'text-coollabs dark:text-warning' => $line['hidden'], + 'text-red-500' => $line['stderr'], + 'font-bold' => $line['command'] ?? false, + 'whitespace-pre-wrap', + ])>{!! $line['line'] !!} +
+ @empty + No logs yet. + @endforelse
diff --git a/resources/views/livewire/project/application/general.blade.php b/resources/views/livewire/project/application/general.blade.php index e21f2a38a..c2c5127e0 100644 --- a/resources/views/livewire/project/application/general.blade.php +++ b/resources/views/livewire/project/application/general.blade.php @@ -263,6 +263,11 @@ helper="You need to modify the docker compose file." monacoEditorLanguage="yaml" useMonacoEditor /> @else + @if ($application->compose_parsing_version === '3') + + @endif diff --git a/resources/views/livewire/project/application/heading.blade.php b/resources/views/livewire/project/application/heading.blade.php index a49860371..7aceaec44 100644 --- a/resources/views/livewire/project/application/heading.blade.php +++ b/resources/views/livewire/project/application/heading.blade.php @@ -1,7 +1,7 @@