diff --git a/.env.production b/.env.production index 96833c253..fe3c8370e 100644 --- a/.env.production +++ b/.env.production @@ -14,3 +14,5 @@ PUSHER_APP_SECRET= ROOT_USERNAME= ROOT_USER_EMAIL= ROOT_USER_PASSWORD= + +REGISTRY_URL=ghcr.io diff --git a/CHANGELOG.md b/CHANGELOG.md index fa6a28264..a2d370ce6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,337 @@ All notable changes to this project will be documented in this file. ### 🚀 Features +- *(api)* Update OpenAPI spec for services (#5448) + +### 🐛 Bug Fixes + +- *(api)* Used ssh keys can be deleted +- *(email)* Transactional emails not sending + +### 📚 Documentation + +- Update changelog + +### ⚙️ Miscellaneous Tasks + +- *(versions)* Bump version to 406 + +## [4.0.0-beta.404] - 2025-04-03 + +### 🚀 Features + +- *(lang)* Added Azerbaijani language updated turkish language. (#5497) +- *(lang)* Added Portuguese from Brazil language (#5500) +- *(lang)* Add Indonesian language translations (#5513) + +### 🐛 Bug Fixes + +- *(docs)* Comment out execute for now +- *(installation)* Mount the docker config +- *(installation)* Path to config file for docker login +- *(service)* Add health check to Bugsink service (#5512) +- *(email)* Emails are not sent in multiple cases +- *(deployments)* Use graceful shutdown instead of `rm` +- *(docs)* Contribute service url (#5517) +- *(proxy)* Proxy restart does not work on domain +- *(ui)* Only show copy button on https +- *(database)* Custom config for MongoDB (#5471) + +### 📚 Documentation + +- Update changelog +- Update changelog +- Update changelog +- Update changelog + +### ⚙️ Miscellaneous Tasks + +- *(service)* Remove unused code in Bugsink service +- *(versions)* Update version to 404 +- *(versions)* Bump version to 403 (#5520) +- *(versions)* Bump version to 404 + +## [4.0.0-beta.402] - 2025-04-01 + +### 🚀 Features + +- *(deployments)* Add list application deployments api route +- *(deploy)* Add pull request ID parameter to deploy endpoint +- *(api)* Add pull request ID parameter to applications endpoint +- *(api)* Add endpoints for retrieving application logs and deployments +- *(lang)* Added Norwegian language (#5280) +- *(dep)* Bump all dependencies + +### 🐛 Bug Fixes + +- Only get apps for the current team +- *(DeployController)* Cast 'pr' query parameter to integer +- *(deploy)* Validate team ID before deployment +- *(wakapi)* Typo in env variables and add some useful variables to wakapi.yaml (#5424) +- *(ui)* Instance Backup settings + +### 🚜 Refactor + +- *(dev)* Remove OpenAPI generation functionality +- *(migration)* Enhance local file volumes migration with logging + +### ⚙️ Miscellaneous Tasks + +- *(service)* Update minecraft service ENVs +- *(service)* Add more vars to infisical.yaml (#5418) +- *(service)* Add google variables to plausible.yaml (#5429) +- *(service)* Update authentik.yaml versions (#5373) +- *(core)* Remove redocs +- *(versions)* Update coolify version numbers to 4.0.0-beta.403 and 4.0.0-beta.404 + +## [4.0.0-beta.401] - 2025-03-28 + +### 📚 Documentation + +- Update changelog +- Update changelog + +## [4.0.0-beta.400] - 2025-03-27 + +### 🚀 Features + +- *(database)* Disable MongoDB SSL by default in migration +- *(database)* Add CA certificate generation for database servers +- *(application)* Add SPA configuration and update Nginx generation logic + +### 🐛 Bug Fixes + +- *(file-storage)* Double save on compose volumes +- *(parser)* Add logging support for applications in services + +### 🚜 Refactor + +- *(proxy)* Improve port availability checks with multiple methods +- *(database)* Update MongoDB SSL configuration for improved security +- *(database)* Enhance SSL configuration handling for various databases +- *(notifications)* Update Telegram button URL for staging environment +- *(models)* Remove unnecessary cloud check in isEnabled method +- *(database)* Streamline event listeners in Redis General component +- *(database)* Remove redundant database status display in MongoDB view +- *(database)* Update import statements for Auth in database components +- *(database)* Require PEM key file for SSL certificate regeneration +- *(database)* Change MySQL daemon command to MariaDB daemon +- *(nightly)* Update version numbers and enhance upgrade script +- *(versions)* Update version numbers for coolify and nightly +- *(email)* Validate team membership for email recipients +- *(shared)* Simplify deployment status check logic +- *(shared)* Add logging for running deployment jobs +- *(shared)* Enhance job status check to include 'reserved' +- *(email)* Improve error handling by passing context to handleError +- *(email)* Streamline email sending logic and improve configuration handling +- *(email)* Remove unnecessary whitespace in email sending logic +- *(email)* Allow custom email recipients in email sending logic +- *(email)* Enhance sender information formatting in email logic +- *(proxy)* Remove redundant stop call in restart method +- *(file-storage)* Add loadStorageOnServer method for improved error handling +- *(docker)* Parse and sanitize YAML compose file before encoding +- *(file-storage)* Improve layout and structure of input fields +- *(email)* Update label for test email recipient input +- *(database-backup)* Remove existing Docker container before backup upload +- *(database)* Improve decryption and deduplication of local file volumes +- *(database)* Remove debug output from volume update process + +### 📚 Documentation + +- Update changelog +- Update changelog + +### ⚙️ Miscellaneous Tasks + +- *(versions)* Update version numbers for coolify and nightly + +### ◀️ Revert + +- Encrypting mount and fs_path + +## [4.0.0-beta.399] - 2025-03-25 + +### 🚀 Features + +- *(service)* Neon +- *(migration)* Add `ssl_certificates` table and model +- *(migration)* Add ssl setting to `standalone_postgresqls` table +- *(ui)* Add ssl settings to Postgres ui +- *(db)* Add ssl mode to Postgres URLs +- *(db)* Setup ssl during Postgres start +- *(migration)* Encrypt local file volumes content and paths +- *(ssl)* Ssl generation helper +- *(ssl)* Migrate to `ECC`certificates using `secp521r1` +- *(ssl)* Improve SSL helper +- *(ssl)* Add a Coolify CA Certificate to all servers +- *(seeder)* Call CA SSL seeder in prod and dev +- *(ssl)* Add Coolify CA Certificate when adding a new server +- *(installer)* Create CA folder during installation +- *(ssl)* Improve SSL helper +- *(ssl)* Use new improved helper for SSL generation +- *(ui)* Add CA cert UI +- *(ui)* New copy button component +- *(ui)* Use new copy button component everywhere +- *(ui)* Improve server advanced view +- *(migration)* Add CN and alternative names to DB +- *(databases)* Add CA SSL crt location to Postgres URLs +- *(ssl)* Improve ssl generation +- *(ssl)* Regenerate SSL certs job +- *(ssl)* Regenerate certificate and valid until UI +- *(ssl)* Regenerate CA cert and all other certs logic +- *(ssl)* Add full MySQL SSL Support +- *(ssl)* Add full MariaDB SSL support +- *(ssl)* Add `openssl.conf` to configure SSL extension properly +- *(ssl)* Improve SSL generation and security a lot +- *(ssl)* Check for SSL renewal twice daily +- *(ssl)* Add SSL relationships to all DBs +- Add full SSL support to MongoDB +- *(ssl)* Fix some issues and improve ssl generation helper +- *(ssl)* Ability to create `.pem` certs and add `clientAuth` to `extendedKeyUsage` +- *(ssl)* New modes for MongoDB and get `caCert` and `mountPath` correctly +- *(ssl)* Full SSL support for Redis +- New mode implementation for MongoDB +- *(ssl)* Improve Redis and remove modes +- Full SSL support for DrangonflyDB +- SSL notification +- *(github-source)* Enhance GitHub App configuration with manual and private key support +- *(ui)* Improve GitHub repository selection and styling +- *(database)* Implement two-step confirmation for database deletion +- *(assets)* Add new SVG logo for Coolify +- *(install)* Enhance Docker address pool configuration and validation +- *(install)* Improve Docker address pool management and service restart logic +- *(install)* Add missing env variable to install script +- *(LocalFileVolume)* Add binary file detection and update UI logic +- *(templates)* Change glance for v0.7 +- *(templates)* Add Freescout service template +- *(service)* Add Evolution API template +- *(service)* Add evolution-api and neon-ws-proxy templates +- *(svg)* Add coolify and evolution-api SVG logos +- *(api)* Add api to create custom services +- *(api)* Separate create and one-click routes +- *(api)* Update Services api routes and handlers +- *(api)* Unify service creation endpoint and enhance validation +- *(notifications)* Add discord ping functionality and settings +- *(user)* Implement session deletion on password reset +- *(github)* Enhance repository loading and validation in applications + +### 🐛 Bug Fixes + +- *(api)* Docker compose based apps creationg through api +- *(database)* Improve database type detection for Supabase Postgres images +- *(ssl)* Permission of ssl crt and key inside the container +- *(ui)* Make sure file mounts do not showing the encrypted values +- *(ssl)* Make default ssl mode require not verify-full as it does not need a ca cert +- *(ui)* Select component should not always uses title case +- *(db)* SSL certificates table and model +- *(migration)* Ssl certificates table +- *(databases)* Fix database name users new `uuid` instead of DB one +- *(database)* Fix volume and file mounts and naming +- *(migration)* Store subjectAlternativeNames as a json array in the db +- *(ssl)* Make sure the subjectAlternativeNames are unique and stored correctly +- *(ui)* Certificate expiration data is null before starting the DB +- *(deletion)* Fix DB deletion +- *(ssl)* Improve SSL cert file mounts +- *(ssl)* Always create ca crt on disk even if it is already there +- *(ssl)* Use mountPath parameter not a hardcoded path +- *(ssl)* Use 1 instead of on for mysql +- *(ssl)* Do not remove SSL directory +- *(ssl)* Wrong ssl cert is loaded to the server and UI error when regenerating SSL +- *(ssl)* Make sure when regenerating the CA cert it is not overwritten with a server cert +- *(ssl)* Regenerating certs for a specific DB +- *(ssl)* Fix MariaDB and MySQL need CA cert +- *(ssl)* Add mount path to DB to fix regeneration of certs +- *(ssl)* Fix SSL regeneration to sign with CA cert and use mount path +- *(ssl)* Get caCert correctly +- *(ssl)* Remove caCert even if it is a folder by accident +- *(ssl)* Ger caCert and `mountPath` correctly +- *(ui)* Only show Regenerate SSL Certificates button when there is a cert +- *(ssl)* Server id +- *(ssl)* When regenerating SSL certs the cert is not singed with the new CN +- *(ssl)* Adjust ca paths for MySQL +- *(ssl)* Remove mode selection for MariaDB as it is not supported +- *(ssl)* Permission issue with MariDB cert and key and paths +- *(ssl)* Rename Redis mode to verify-ca as it is not verify-full +- *(ui)* Remove unused mode for MongoDB +- *(ssl)* KeyDB port and caCert args are missing +- *(ui)* Enable SSL is not working correctly for KeyDB +- *(ssl)* Add `--tls` arg to DrangflyDB +- *(notification)* Always send SSL notifications +- *(database)* Change default value of enable_ssl to false for multiple tables +- *(ui)* Correct grammatical error in 404 page +- *(seeder)* Update GitHub app name in GithubAppSeeder +- *(plane)* Update APP_RELEASE to v0.25.2 in environment configuration +- *(domain)* Dispatch refreshStatus event after successful domain update +- *(database)* Correct container name generation for service databases +- *(database)* Limit container name length for database proxy +- *(database)* Handle unsupported database types in StartDatabaseProxy +- *(database)* Simplify container name generation in StartDatabaseProxy +- *(install)* Handle potential errors in Docker address pool configuration +- *(backups)* Retention settings +- *(redis)* Set default redis_username for new instances +- *(core)* Improve instantSave logic and error handling +- *(general)* Correct link to framework specific documentation +- *(core)* Redirect healthcheck route for dockercompose applications +- *(api)* Use name from request payload +- *(issue#4746)* Do not use setGitImportSettings inside of generateGitLsRemoteCommands +- Correct some spellings +- *(service)* Replace deprecated credentials env variables on keycloak service +- *(keycloak)* Update keycloak image version to 26.1 +- *(console)* Handle missing root user in password reset command +- *(ssl)* Handle missing CA certificate in SSL regeneration job +- *(copy-button)* Ensure text is safely passed to clipboard + +### 💼 Other + +- Bump Coolify to 4.0.0-beta.400 +- *(migration)* Add SSL fields to database tables +- SSL Support for KeyDB + +### 🚜 Refactor + +- *(ui)* Unhide log toggle in application settings +- *(nginx)* Streamline default Nginx configuration and improve error handling +- *(install)* Clean up install script and enhance Docker installation logic +- *(ScheduledTask)* Clean up code formatting and remove unused import +- *(app)* Remove unused MagicBar component and related code +- *(database)* Streamline SSL configuration handling across database types +- *(application)* Streamline healthcheck parsing from Dockerfile +- *(notifications)* Standardize getRecipients method signatures +- *(configuration)* Centralize configuration management in ConfigurationRepository +- *(docker)* Update image references to use centralized registry URL +- *(env)* Add centralized registry URL to environment configuration +- *(storage)* Simplify file storage iteration in Blade template +- *(models)* Add is_directory attribute to LocalFileVolume model +- *(modal)* Add ignoreWire attribute to modal-confirmation component +- *(invite-link)* Adjust layout for better responsiveness in form +- *(invite-link)* Enhance form layout for improved responsiveness +- *(network)* Enhance docker network creation with ipv6 fallback +- *(network)* Check for existing coolify network before creation +- *(database)* Enhance encryption process for local file volumes + +### 📚 Documentation + +- Update changelog +- Update changelog +- *(CONTRIBUTING)* Add note about Laravel Horizon accessibility +- Update changelog + +### ⚙️ Miscellaneous Tasks + +- *(migration)* Remove unused columns +- *(ssl)* Improve code in ssl helper +- *(migration)* Ssl cert and key should not be nullable +- *(ssl)* Rename CA cert to `coolify-ca.crt` because of conflicts +- Rename ca crt folder to ssl +- *(ui)* Improve valid until handling +- Improve code quality suggested by code rabbit +- *(supabase)* Update Supabase service template and Postgres image version +- *(versions)* Update version numbers for coolify and nightly + +## [4.0.0-beta.398] - 2025-03-01 + +### 🚀 Features + - *(billing)* Add Stripe past due subscription status tracking - *(ui)* Add past due subscription warning banner @@ -6223,6 +6554,40 @@ All notable changes to this project will be documented in this file. ### 🚀 Features +- Use tags in update +- New update process (#115) +- VaultWarden service +- Www <-> non-www redirection for apps +- Www <-> non-www redirection +- Follow logs +- Generate www & non-www SSL certs +- Basic password reset form +- Scan for lock files and set right commands +- Public port range (WIP) +- Ports range +- Random subdomain for demo +- Random domain for services +- Astro buildpack +- 11ty buildpack +- Registration page +- Languagetool service +- Send version with update request +- Service secrets +- Webhooks inititate all applications with the correct branch +- Check ssl for new apps/services first +- Autodeploy pause +- Install pnpm into docker image if pnpm lock file is used +- Add PHP modules +- Use compose instead of normal docker cmd +- Be able to redeploy PRs +- Add n8n.io service +- Add update kuma service +- Ghost service +- Initial python support +- Add loading on register button +- *(dev)* Allow windows users to use pnpm dev +- MeiliSearch service +- Add abilitry to paste env files - Wordpress on-demand SFTP - Finalize on-demand sftp for wp - PHP Composer support @@ -6231,9 +6596,817 @@ All notable changes to this project will be documented in this file. - Able to change service version/tag - Basic white labeled version - Able to modify database passwords +- Add persistent storage for services +- Multiply dockerfile locations for docker buildpack +- Testing fluentd logging driver +- Fluentbit investigation +- Initial deno support +- Deno DB migration +- Show exited containers on UI & better UX +- Query container state periodically +- Install svelte-18n and init setup +- Umami service +- Coolify auto-updater +- Autoupdater +- Select base image for buildpacks +- Hasura as a service +- Gzip compression +- Laravel buildpack is working! +- Laravel +- Fider service +- Database and services logs +- DNS check settings for SSL generation +- Cancel builds! +- Basic server usage on dashboard +- Show usage trends +- Usage on dashboard +- Custom script path for Plausible +- WP could have custom db +- Python image selection +- PageLoader +- Database + service usage +- Ability to change deployment type for nextjs +- Ability to change deployment type for nuxtjs +- Gitpod ready code(almost) +- Add Docker buildpack exposed port setting +- Custom port for git instances +- Gitpod integration +- Init moodle and separate stuffs to shared package +- Moodle init +- Remote docker engine init +- Working on remote docker engine +- Rde +- Remote docker engine +- Ipv4 and ipv6 +- Contributors +- Add arch to database +- Stop preview deployment +- Persistent storage for all services +- Cleanup clickhouse db +- Init heroku buildpacks +- Databases on ARM +- Mongodb arm support +- New dashboard +- Appwrite service +- Heroku deployments +- Deploy bots (no domains) +- Custom dns servers +- Import public repos (wip) +- Public repo deployment +- Force rebuild + env.PORT for port + public repo build +- Add GlitchTip service +- Searxng service +- *(ui)* Rework home UI and with responsive design +- New service - weblate +- Restart application +- Show elapsed time on running builds +- Github allow fual branches +- Gitlab dual branch +- Taiga +- *(routes)* Rework ui from login and register page +- Add traefik acme json to coolify container +- Database secrets +- New servers view +- Add queue reset button +- Previewapplications init +- PreviewApplications finalized +- Fluentbit +- Show remote servers +- *(layout)* Added drawer when user is in mobile +- Re-apply ui improves +- *(ui)* Improve header of pages +- *(styles)* Make header css component +- *(routes)* Improve ui for apps, databases and services logs +- Add migration button to appwrite +- Custom certificate +- Ssl cert on traefik config +- Refresh resource status on dashboard +- Ssl certificate sets custom ssl for applications +- System-wide github apps +- Cleanup unconfigured applications +- Cleanup unconfigured services and databases +- Docker compose support +- Docker compose +- Docker compose +- Monitoring by container +- Initial support for specific git commit +- Add default to latest commit and support for gitlab +- Redirect catch-all rule +- Rollback coolify +- Only show expose if no proxy conf defined in template +- Custom/private docker registries +- Use registry for building +- Docker registries working +- Custom docker compose file location in repo +- Save doNotTrackData to db +- Add default sentry +- Do not track in settings +- System wide git out of beta +- Custom previewseparator +- Sentry frontend +- Able to host static/php sites on arm +- Save application data before deploying +- SimpleDockerfile deployment +- Able to push image to docker registry +- Revert to remote image +- *(api)* Name label +- Add Openblocks icon +- Adding icon for whoogle +- *(ui)* Add libretranslate service icon +- Handle invite_only plausible analytics +- Init h2c (http2/grpc) support +- Http + h2c paralel +- Github raw icon url +- Remove svg support +- Add host path to any container +- Able to control multiplexing +- Add runRemoteCommandSync +- Github repo with deployment key +- Add persistent volumes +- Debuggable executeNow commands +- Add private gh repos +- Delete gh app +- Installation/update github apps +- Auto-deploy +- Deploy key based deployments +- Resource limits +- Long running queue with 1 hour of timeout +- Add arm build to dev +- Disk cleanup threshold by server +- Notify user of disk cleanup init +- Pricing plans ans subs +- Add s3 storages +- Init postgresql database +- Add backup notifications +- Dockerfile build pack +- Cloud +- Force password reset + waitlist +- Send internal notification to discord +- Monitor server connection +- Invite by email from waitlist +- Rolling update +- Add resend as transactional emails +- Send request in cloud +- Add discord notifications +- Public database +- Telegram topics separation +- Developer view for env variables +- Cache team settings +- Generate public key from private keys +- Able to invite more people at once +- Trial +- Dynamic trial period +- Ssh-agent instead of filesystem based ssh keys +- New container status checks +- Generate ssh key +- Sentry add email for better support +- Healthcheck for apps +- Add cloudflare tunnel support +- Services +- Image tag for services +- Container logs +- Reset root password +- Attach Coolify defined networks to services +- Delete resource command +- Multiselect removable resources +- Disable service, required version +- Basedir / monorepo initial support +- Init version of any git deployment +- Deploy private repo with ssh key +- Add email verification for cloud +- Able to deploy docker images +- Add dockerfile location +- Proxy logs on the ui +- Add custom redis conf +- Use docker login credentials from server +- Able to customize docker labels on applications +- Show if config is not applied +- Standalone mongodb +- Cloning project +- Api tokens + deploy webhook +- Start all kinds of things +- Simple search functionality +- Mysql, mariadb +- Lock environment variables +- Download local backups +- Improve deployment time by a lot +- Deployment logs fullscreen +- Service database backups +- Make service databases public +- Log drain (wip) +- Enable/disable log drain by service +- Log drainer container check +- Add docker engine support install script to rhel based systems +- Save timestamp configuration for logs +- Custom log drain endpoints +- Auto-restart tcp proxies for databases +- Execute command in container +- Autoupdate env during seed +- Disable autoupdate +- Randomly sleep between executions +- Pull latest images for services +- Custom docker compose commands +- Add environment description + able to change name +- Raw docker compose deployments +- Add www-non-www redirects to traefik +- Import backups +- Search between resources +- Move resources between projects / environments +- Clone any resource +- Shared environments +- Concurrent builds / server +- Able to deploy multiple resources with webhook +- Add PR comments +- Dashboard live deployment view +- Added manual webhook support for bitbucket +- Add initial support for custom docker run commands +- Cleanup unreachable servers +- Tags and tag deploy webhooks +- Clone to env +- Multi deployments +- Cleanup queue +- Magic for traefik redirectregex in services +- Revalidate server +- Disable gzip compression on service applications +- Save github app permission locally +- Minversion for services +- Able to add dynamic configurations from proxy dashboard +- Custom server limit +- Delay container/server jobs +- Add static ipv4 ipv6 support +- Server disabled by overflow +- Preview deployment logs +- Collect webhooks during maintenance +- Logs and execute commands with several servers +- Domains api endpoint +- Resources api endpoint +- Team api endpoint +- Add deployment details to deploy endpoint +- Add deployments api +- Experimental caddy support +- Dynamic configuration for caddy +- Reset password +- Show resources on source page +- Able to run scheduler/horizon programatically +- Change page width +- Watch paths +- Able to make rsa/ed ssh keys +- *(application)* Update submodules after git checkout +- Add amazon linux 2023 +- Upload large backups +- Edit domains easier for compose +- Able to delete configuration from server +- Configuration checker for all resources +- Allow tab in textarea +- Dynamic mux time +- Literal env variables +- Lazy load stuffs + tell user if compose based deployments have missing envs +- Can edit file/dir volumes from ui in compose based apps +- Upgrade Appwrite service template to 1.5 +- Upgrade Appwrite service template to 1.5 +- Add db name to backup notifications +- Initial datalist +- Update service contribution docs URL +- The final pricing plan, pay-as-you-go +- Add container name to network aliases in ApplicationDeploymentJob +- Add lazy loading for images in General.php and improve Docker Compose file handling in Application.php +- Experimental sentinel +- Start Sentinel on servers. +- Pull new sentinel image and restart container +- Init metrics +- Add AdminRemoveUser command to remove users from the database +- Adding new COOLIFY_ variables +- Save commit message and better view on deployments +- Toggle label escaping mechanism +- Shows the latest deployment commit + message on status +- New manual update process + remove next_channel +- Add lastDeploymentInfo and lastDeploymentLink props to breadcrumbs and status components +- Sort envs alphabetically and creation date +- Improve sorting of environment variables in the All component +- Update healthcheck test in StartMongodb action +- Add pull_request_id filter to get_last_successful_deployment method in Application model +- Add hc logs to healthchecks +- Add SerpAPI as a Github Sponsor +- Admin view for deleting users +- Scheduled task failed notification +- If the time seems too long it remains at 0s +- Improve Docker Engine start logic in ServerStatusJob +- If proxy stopped manually, it won't start back again +- Exclude_from_hc magic +- Gitea manual webhooks +- Add container logs in case the container does not start healthy +- Handle incomplete expired subscriptions in Stripe webhook +- Add more persistent storage types +- Add PHP memory limit environment variable to docker-compose.prod.yml +- Add manual update option to UpdateCoolify handle method +- Add port configuration for Vaultwarden service +- Able to change database passwords on the UI. It won't sync to the database. +- Able to add several domains to compose based previews +- Add bounty program link to bug report template +- Add titles +- Db proxy logs +- Easily redirect between www-and-non-www domains +- Add logos for new sponsors +- Add homepage template +- Update homepage.yaml with environment variables and volumes +- Spanish translation +- Cancelling a deployment will check if new could be started. +- Add supaguide logo to donations section +- Nixpacks now could reach local dbs internally +- Add Tigris logo to other/logos directory +- COOLIFY_CONTAINER_NAME predefined variable +- Charts +- Sentinel + charts +- Container metrics +- Add high priority queue +- Add metrics warning for servers without Sentinel enabled +- Add blacksmith logo to donations section +- Preselect server and destination if only one found +- More api endpoints +- Add API endpoint to update application by UUID +- Update statusnook logo filename in compose template +- Local fonts +- More API endpoints +- Bulk env update api endpoint +- Update server settings metrics history days to 7 +- New app API endpoint +- Private gh deployments through api +- Lots of api endpoints +- Api api api api api api +- Rename CloudCleanupSubs to CloudCleanupSubscriptions +- Early fraud warning webhook +- Improve internal notification message for early fraud warning webhook +- Add schema for uuid property in app update response +- Cleanup unused docker networks from proxy +- Compose parser v2 +- Display time interval for rollback images +- Add security and storage access key env to twenty template +- Add new logo for Latitude +- Enable legacy model binding in Livewire configuration +- Improve error handling in loadComposeFile method +- Add readonly labels +- Preserve git repository +- Force cleanup server +- Create/delete project endpoints +- Add patch request to projects +- Add server api endpoints +- Add branddev logo to README.md +- Update API endpoint summaries +- Update Caddy button label in proxy.blade.php +- Check custom internal name through server's applications. +- New server check job +- Delete team in cloud without subscription +- Coolify init should cleanup stuck networks in proxy +- Add manual update check functionality to settings page +- Update auto update and update check frequencies in settings +- Update Upgrade component to check for latest version of Coolify +- Improve homepage service template +- Support map fields in Directus +- Labels by proxy type +- Able to generate only the required labels for resources +- Preserve git repository with advanced file storages +- Added Windmill template +- Added Budibase template +- Add shm-size for custom docker commands +- Add custom docker container options to all databases +- Able to select different postgres database +- Add new logos for jobscollider and hostinger +- Order scheduled task executions +- Add Code Server environment variables to Service model +- Add coolify build env variables to building phase +- Add new logos for GlueOps, Ubicloud, Juxtdigital, Saasykit, and Massivegrid +- Add new logos for GlueOps, Ubicloud, Juxtdigital, Saasykit, and Massivegrid +- Update server_settings table to force docker cleanup +- Update Docker Compose file with DB_URL environment variable +- Refactor shared.php to improve environment variable handling +- Expose project description in API response +- Add elixir finetunes to the deployment job +- Make coolify full width by default +- Fully functional terminal for command center +- Custom terminal host +- Add buddy logo +- Add nullable constraint to 'fingerprint' column in private_keys table +- *(api)* Add an endpoint to execute a command +- *(api)* Add endpoint to execute a command +- Add ContainerStatusTypes enum for managing container status +- Allow specify use_build_server when creating/updating an application +- Add support for `use_build_server` in API endpoints for creating/updating applications +- Add Mixpost template +- Update resource deletion job to allow configurable options through API +- Add query parameters for deleting configurations, volumes, docker cleanup, and connected networks +- Add command to check application deployment queue +- Support Hetzner S3 +- Handle HTTPS domain in ConfigureCloudflareTunnels +- Backup all databases for mysql,mariadb,postgresql +- Restart service without pulling the latest image +- Add strapi template +- Add it-tools service template and logo +- Add homarr service tamplate and logo +- Add Argilla service configuration to Service model +- Add Invoice Ninja service configuration to Service model +- Project search on frontend +- Add ollama service with open webui and logo +- Update setType method to use slug value for type +- Refactor setType method to use slug value for type +- Refactor setType method to use slug value for type +- Add Supertokens template +- Add easyappointments service template +- Add dozzle template +- Adds forgejo service with runners +- Add Mautic 4 and 5 to service templates +- Add keycloak template +- Add onedev template +- Improve search functionality in project selection +- Add customHelper to stack-form +- Add cloudbeaver template +- Add ntfy template +- Add qbittorrent template +- Add Homebox template +- Add owncloud service and logo +- Add immich service +- Auto generate url +- Refactored to work with coolify auto env vars +- Affine service template and logo +- Add LibreTranslate template +- Open version in a new tab +- Add Transmission template +- Add transmission healhcheck +- Add zipline template +- Dify template +- Required envs +- Add EdgeDB +- Show warning if people would like to use sslip with https +- Add is shared to env variables +- Variabel sync and support shared vars +- Add notification settings to server_disk_usage +- Add coder service tamplate and logo +- Debug mode for sentinel +- Add jitsi template +- Add --gpu support for custom docker command +- Add Firefox template +- Add template for Wiki.js +- Add upgrade logs to /data/coolify/source +- Custom nginx configuration for static deployments + fix 404 redirects in nginx conf +- Check local horizon scheduler deployments +- Add internal api docs to /docs/api with auth +- Add proxy type change to create/update apis +- Add MacOS template +- Add Windows template +- *(service)* :sparkles: add mealie +- Add hex magic env var +- Add deploy-only token permission +- Able to deploy without cache on every commit +- Update private key nam with new slug as well +- Allow disabling default redirect, set status to 503 +- Add TLS configuration for default redirect in Server model +- Slack notifications +- Introduce root permission +- Able to download schedule task logs +- Migrate old email notification settings from the teams table +- Migrate old discord notification settings from the teams table +- Migrate old telegram notification settings from the teams table +- Add slack notifications to a new table +- Enable success messages again +- Use new notification stuff inside team model +- Some more notification settings and better defaults +- New email notification settings +- New shared function name `is_transactional_emails_enabled()` +- New shared notifications functions +- Email Notification Settings Model +- Telegram notification settings Model +- Discord notification settings Model +- Slack notification settings Model +- New Discord notification UI +- New Slack notification UI +- New telegram UI +- Use new notification event names +- Always sent notifications +- Scheduled task success notification +- Notification trait +- Get discord Webhook form new table +- Get Slack Webhook form new table +- Use new table or instance settings for email +- Use new place for settings and topic IDs for telegram +- Encrypt instance email settings +- Use encryption in instance settings model +- Scheduled task success and failure notifications +- Add docker cleanup success and failure notification settings columns +- UI for docker cleanup success and failure notification +- Docker cleanup email views +- Docker cleanup success and failure notification files +- Scheduled task success email +- Send new docker cleanup notifications +- :passport_control: integrate Authentik authentication with Coolify +- *(notification)* Add Pushover +- Add seeder command and configuration for database seeding +- Add new password magic env with symbols +- Add documenso service +- New ServerReachabilityChanged event +- Use new ServerReachabilityChanged event instead of isDirty +- Add infomaniak oauth +- Add server disk usage check frequency +- Add environment_uuid support and update API documentation +- Add service/resource/project labels +- Add coolify.environment label +- Add database subtype +- Migrate to new encryption options +- New encryption options +- Able to import full db backups for pg/mysql/mariadb +- Restore backup from server file +- Docker volume data cloning +- Move volume data cloning to a Job +- Volume cloning for ResourceOperations +- Remote server volume cloning +- Add horizon server details to queue +- Enhance horizon:manage command with worker restart check +- Add is_coolify_host to the server api responses +- DB migration for Backup retention +- UI for backup retention settings +- New global s3 and local backup deletion function +- Use new backup deletion functions +- Add calibre-web service +- Add actual-budget service +- Add rallly service +- Template for Gotenberg, a Docker-powered stateless API for PDF files +- Enhance import command options with additional guidance and improved checkbox label +- Purify for better sanitization +- Move docker cleanup to its own tab +- DB and Model for docker cleanup executions +- DockerCleanupExecutions relationship +- DockerCleanupDone event +- Get command and output for logs from CleanupDocker +- New sidebar menu and order +- Docker cleanup executions UI +- Add execution log to dockerCleanupJob +- Improve deployment UI +- Root user envs and seeding +- Email, username and password validation when they are set via envs +- Improved error handling and log output +- Add root user configuration variables to production environment +- Add log file check message in upgrade script for better troubleshooting +- Add root user details to install script +- *(core)* Wip version of coolify.json +- *(core)* Add SOURCE_COMMIT variable to build environment in ApplicationDeploymentJob +- *(service)* Update affine.yaml with AI environment variables (#4918) +- *(service)* Add new service Flipt (#4875) +- *(docs)* Update tech stack +- *(terminal)* Show terminal unavailable if the container does not have a shell on the global terminal UI +- *(ui)* Improve deployment UI +- *(template)* Add Open Web UI +- *(templates)* Add Open Web UI service template +- *(ui)* Update GitHub source creation advanced section label +- *(core)* Add dynamic label reset for application settings +- *(ui)* Conditionally enable advanced application settings based on label readonly status +- *(env)* Added COOLIFY_RESOURCE_UUID environment variable +- *(vite)* Add Cloudflare async script and style tag attributes +- *(meta)* Add comprehensive SEO and social media meta tags +- *(core)* Add name to default proxy configuration +- Add application api route +- Container logs +- Remove ansi color from log +- Add lines query parameter +- *(changelog)* Add git cliff for automatic changelog generation +- *(workflows)* Improve changelog generation and workflows +- *(ui)* Add periodic status checking for services +- *(deployment)* Ensure private key is stored in filesystem before deployment +- *(slack)* Show message title in notification previews (#5063) +- *(i18n)* Add Arabic translations (#4991) +- *(i18n)* Add French translations (#4992) +- *(services)* Update `service-templates.json` +- *(ui)* Add top padding to pricing plans view +- *(core)* Add error logging and cron parsing to docker/server schedules +- *(core)* Prevent using servers with existing resources as build servers +- *(ui)* Add textarea switching option in service compose editor +- *(ui)* Add wire:key to two-step confirmation settings +- *(database)* Add index to scheduled task executions for improved query performance +- *(database)* Add index to scheduled database backup executions +- *(billing)* Add Stripe past due subscription status tracking +- *(ui)* Add past due subscription warning banner +- *(service)* Neon +- *(migration)* Add `ssl_certificates` table and model +- *(migration)* Add ssl setting to `standalone_postgresqls` table +- *(ui)* Add ssl settings to Postgres ui +- *(db)* Add ssl mode to Postgres URLs +- *(db)* Setup ssl during Postgres start +- *(migration)* Encrypt local file volumes content and paths +- *(ssl)* Ssl generation helper +- *(ssl)* Migrate to `ECC`certificates using `secp521r1` +- *(ssl)* Improve SSL helper +- *(ssl)* Add a Coolify CA Certificate to all servers +- *(seeder)* Call CA SSL seeder in prod and dev +- *(ssl)* Add Coolify CA Certificate when adding a new server +- *(installer)* Create CA folder during installation +- *(ssl)* Improve SSL helper +- *(ssl)* Use new improved helper for SSL generation +- *(ui)* Add CA cert UI +- *(ui)* New copy button component +- *(ui)* Use new copy button component everywhere +- *(ui)* Improve server advanced view +- *(migration)* Add CN and alternative names to DB +- *(databases)* Add CA SSL crt location to Postgres URLs +- *(ssl)* Improve ssl generation +- *(ssl)* Regenerate SSL certs job +- *(ssl)* Regenerate certificate and valid until UI +- *(ssl)* Regenerate CA cert and all other certs logic +- *(ssl)* Add full MySQL SSL Support +- *(ssl)* Add full MariaDB SSL support +- *(ssl)* Add `openssl.conf` to configure SSL extension properly +- *(ssl)* Improve SSL generation and security a lot +- *(ssl)* Check for SSL renewal twice daily +- *(ssl)* Add SSL relationships to all DBs +- Add full SSL support to MongoDB +- *(ssl)* Fix some issues and improve ssl generation helper +- *(ssl)* Ability to create `.pem` certs and add `clientAuth` to `extendedKeyUsage` +- *(ssl)* New modes for MongoDB and get `caCert` and `mountPath` correctly +- *(ssl)* Full SSL support for Redis +- New mode implementation for MongoDB +- *(ssl)* Improve Redis and remove modes +- Full SSL support for DrangonflyDB +- SSL notification +- *(github-source)* Enhance GitHub App configuration with manual and private key support +- *(ui)* Improve GitHub repository selection and styling +- *(database)* Implement two-step confirmation for database deletion +- *(assets)* Add new SVG logo for Coolify +- *(install)* Enhance Docker address pool configuration and validation +- *(install)* Improve Docker address pool management and service restart logic +- *(install)* Add missing env variable to install script +- *(LocalFileVolume)* Add binary file detection and update UI logic +- *(templates)* Change glance for v0.7 +- *(templates)* Add Freescout service template +- *(service)* Add Evolution API template +- *(service)* Add evolution-api and neon-ws-proxy templates +- *(svg)* Add coolify and evolution-api SVG logos +- *(api)* Add api to create custom services +- *(api)* Separate create and one-click routes +- *(api)* Update Services api routes and handlers +- *(api)* Unify service creation endpoint and enhance validation +- *(notifications)* Add discord ping functionality and settings +- *(user)* Implement session deletion on password reset +- *(github)* Enhance repository loading and validation in applications +- *(database)* Disable MongoDB SSL by default in migration +- *(database)* Add CA certificate generation for database servers +- *(application)* Add SPA configuration and update Nginx generation logic +- *(deployments)* Add list application deployments api route +- *(deploy)* Add pull request ID parameter to deploy endpoint +- *(api)* Add pull request ID parameter to applications endpoint +- *(api)* Add endpoints for retrieving application logs and deployments +- *(lang)* Added Norwegian language (#5280) +- *(dep)* Bump all dependencies +- *(proxy)* Enhance proxy handling and port conflict detection +- *(lang)* Added Azerbaijani language updated turkish language. (#5497) +- *(lang)* Added Portuguese from Brazil language (#5500) +- *(lang)* Add Indonesian language translations (#5513) +- *(api)* Update OpenAPI spec for services (#5448) ### 🐛 Bug Fixes +- Secrets join +- ENV variables set differently +- Capture non-error as error +- Only delete id.rsa in case of it exists +- Status is not available yet +- Docker Engine bug related to live-restore and IPs +- Version +- PreventDefault on a button, thats all +- Haproxy check should not throw error +- Delete all build files +- Cleanup images +- More error handling in proxy configuration + cleanups +- Local static assets +- Check sentry +- Typo +- Package.json +- Build secrets should be visible in runtime +- New secret should have default values +- Validate secrets +- Truncate git clone errors +- Branch used does not throw error +- Typo +- Error handling +- Stopping service without proxy +- Coolify proxy start +- Window error in SSR +- GitHub sync PR's +- Load more button +- Small fixes +- Typo +- Error with follow logs +- IsDomainConfigured +- TransactionIds +- Coolify image cleanup +- Cleanup every 10 mins +- Cleanup images +- Add no user redis to uri +- Secure cookie disabled by default +- Buggy svelte-kit-cookie-session +- Login issues +- SSL app off +- Local docker host +- Typo +- Lets encrypt +- Remove SSL with stop +- SSL off for services +- Grr +- Running state css +- Minor fixes +- Remove force SSL when doing let's encrypt request +- GhToken in session now +- Random port for certbot +- Follow icon +- Plausible volume fixed +- Database connection strings +- Gitlab webhooks fixed +- If DNS not found, do not redirect +- Github token +- Move tokens from session to cookie/store +- Email is lowercased in login +- Lowercase email everywhere +- Use normal docker-compose in dev +- Random network name for demo +- Settings fqdn grr +- Revert default network +- Http for demo, oops +- Docker scanner +- Improvement on image pulls +- Coolify image pulls +- Remove wrong/stuck proxy configurations +- Always use a buildpack +- Add icons for eleventy + astro +- Fix proxy every 10 secs +- Do not remove coolify proxy +- Update version +- Be sure .env exists +- Missing fqdn for services +- Default npm command +- Add coolify-image label for build images +- Cleanup old images, > 3 days +- Better proxy check +- Ssl + sslrenew +- Null proxyhash on restart +- Reconfigure proxy on restart +- Update process +- Reload proxy on ssl cert +- Volume name +- Update process +- Check when a container is running +- Reload haproxy if new cert is added +- Cleanup coolify images +- Application state in UI +- Do not error if proxy is not running +- Personal Gitlab repos +- Autodeploy true by default for GH repos +- No cookie found +- Missing session data +- No error if GitSource is missing +- No webhook secret found? +- Basedir for dockerfiles +- Better queue system + more support on monorepos +- Remove build logs in case of app removed +- Cleanup old builds +- Only cleanup same app +- Add nginx + htaccess files +- Skip ssl cert in case of error +- Volumes +- Cleanup only 2 hours+ old images +- Ghost logo size +- Ghost icon, remove console.log +- List ghost services +- Reload window on settings saved +- Persistent storage on webhooks +- Add license +- Space in repo names +- Gitlab repo url +- No need to dashify anymore +- Registration enabled/disabled +- Add PROTO headers +- Haproxy errors +- Build variables +- Use NodeJS for sveltekit for now +- Ignore coolify proxy error for now +- Python no wsgi +- If user not found +- Rename envs to secrets +- Infinite loop on www domains +- No need to paste clear text env for previews +- Build log fix attempt #1 +- Small UI fix on logs +- Lets await! +- Async progress +- Remove console.log +- Build log +- UI +- Gitlab & Github urls +- Secrets build/runtime coudl be changed after save +- Default configuration +- *(php)* If .htaccess file found use apache +- Add default webhook domain for n8n +- Add git lfs while deploying +- Try to update build status several times +- Update stucked builds +- Update stucked builds on startup +- Revert seed +- Lame fixing +- Remove asyncUntil - Add openssl to image - Permission issues - On-demand sFTP for wp @@ -6255,9 +7428,2259 @@ All notable changes to this project will be documented in this file. - Html/apiUrls cannot end with / - Typo - Missing buildpack +- Enable https for Ghost +- Postgres root passwor shown and set +- Able to change postgres user password from ui +- DB Connecting string generator +- Missing install repositories GitHub +- Return own and other sources better +- Show config missing on sources +- Remove unnecessary save button haha +- Update dockerfile +- Haproxy build stuffs +- Proxy +- Types +- Invitations +- Timeout values +- Cleanup images older than a day +- Meilisearch service +- Load all branches, not just the first 30 +- ProjectID for Github +- DNS check before creating SSL cert +- Try catch me +- Restart policy for resources +- No permission on first registration +- Reverting postgres password for now +- Destinations to HAProxy +- Register should happen if coolify proxy cannot be started +- GitLab typo +- Remove system wide pw reset +- Postgres root pw is pw field +- Teams view +- Improved tcp proxy monitoring for databases/ftp +- Add HTTP proxy checks +- Loading of new destinations +- Better performance for cleanup images +- Remove proxy container in case of dependent container is down +- Restart local docker coolify proxy in case of something happens to it +- Id of service container +- Switch from bitnami/redis to normal redis +- Use redis-alpine +- Wordpress extra config +- Stop sFTP connection on wp stop +- Change user's id in sftp wp instance +- Use arm based certbot on arm +- Buildlog line number is not string +- Application logs paginated +- Switch to stream on applications logs +- Scroll to top for logs +- Pull new images for services all the time it's started. +- White-labeled custom logo +- Application logs +- Deno configurations +- Text on deno buildpack +- Correct branch shown in build logs +- Vscode permission fix +- I18n +- Locales +- Application logs is not reversed and queried better +- Do not activate i18n for now +- GitHub token cleanup on team switch +- No logs found +- Code cleanups +- Reactivate posgtres password +- Contribution guide +- Simplify list services +- Contribution +- Contribution guide +- Contribution guide +- Packagemanager finder +- Unami svg size +- Team switching moved to IAM menu +- Always use IP address for webhooks +- Remove unnecessary test endpoint +- UI +- Migration +- Fider envs +- Checking low disk space +- Build image +- Update autoupdate env variable +- Renew certificates +- Webhook build images +- Missing node versions +- ExposedPorts +- Logos for dbs +- Do not run SSL renew in development +- Check domain for coolify before saving +- Remove debug info +- Cancel jobs +- Cancel old builds in database +- Better DNS check to prevent errors +- Check DNS in prod only +- DNS check +- Disable sentry for now +- Cancel +- Sentry +- No image for Docker buildpack +- Default packagemanager +- Server usage only shown for root team +- Expose ports for services +- UI +- Navbar UI +- UI +- UI +- Remove RC python +- UI +- UI +- UI +- Default Python package +- WP custom db +- UI +- Gastby buildpack +- Service checks +- Remove console.log +- Traefik +- Remove debug things +- WIP Traefik +- Proxy for http +- PR deployments view +- Minio urls + domain checks +- Remove gh token on git source changes +- Do not fetch app state in case of missconfiguration +- Demo instance save domain instantly +- Instant save on demo instance +- New source canceled view +- Lint errors in database services +- Otherfqdns +- Host key verification +- Ftp connection +- GitHub fixes +- TrustProxy +- Force restart proxy +- Only restart coolify proxy in case of version prior to 2.9.2 +- Force restart proxy on seeding +- Add GIT ENV variable for submodules +- Recurisve clone instead of submodule +- Versions +- Only reconfigure coolify proxy if its missconfigured +- Demo version forms +- Typo +- Revert gh and gl cloning +- Proxy stop missing argument +- Fider changed an env variable name +- Pnpm command +- Plausible custom script +- Plausible script and middlewares +- Remove console log +- Remove comments +- Traefik middleware +- Persistent nocodb +- Nocodb persistency +- Host and reload for uvicorn +- Remove package-lock +- Be able to change database + service versions +- Lock file +- Seeding +- Forgot that the version bump changed 😅 +- New destination can be created +- Include post +- New destinations +- Domain check +- Domain check +- TrustProxy for Fastify +- Hostname issue +- GitLab pagination load data +- Service domain checker +- Wp missing ftp solution +- Ftp WP issues +- Ftp?! +- Gitpod updates +- Gitpod +- Gitpod +- Wordpress FTP permission issues +- GitLab search fields +- GitHub App button +- GitLab loop on misconfigured source +- Gitpod +- Cleanup less often and can do it manually +- Admin password reset should not timeout +- Message for double branches +- Turn off autodeploy if double branch is configured +- More types for API +- More types +- Do not rebuild in case image exists and sha not changed +- Gitpod urls +- Remove new service start process +- Remove shared dir, deployment does not work +- Gitlab custom url +- Location url for services and apps +- Settings from api +- Selectable destinations +- Gitpod hardcodes +- Typo +- Typo +- Expose port checker +- States and exposed ports +- CleanupStorage +- Remote traefik webhook +- Remote engine ip address +- RemoteipAddress +- Explanation for remote engine url +- Tcp proxy +- Lol +- Webhook +- Dns check for rde +- Gitpod +- Revert last commit +- Dns check +- Dns checker +- Webhook +- Df and more debug +- Webhooks +- Load previews async +- Destination icon +- Pr webhook +- Cache image +- No ssh key found +- Prisma migration + update of docker and stuffs +- Ui +- Ui +- Only 1 ssh-agent is needed +- Reuse ssh connection +- Ssh tunnel +- Dns checking +- Fider BASE_URL set correctly +- Rde local ports +- Empty remote destinations could be removed +- Tips +- Lowercase issues fider +- Tooltip colors +- Update clickhouse configuration +- Cleanup command +- Enterprise Github instance endpoint +- Follow/cancel buttons +- Only remove coolify managed containers +- White-labeled env +- Schema +- Coolify-network on verification +- Cleanup stucked prisma-engines +- Toast +- Secrets +- Cleanup prisma engine if there is more than 1 +- !isARM to isARM +- Enterprise GH link +- Empty buildpack icons +- Debounce dashboard status requests +- Decryption errors +- Postgresql on ARM +- Make it public button +- Loading indicator +- Replace docker compose with docker-compose on CSB +- Dashboard ui +- Create coolify-infra, if it does not exists +- Gitpod conf and heroku buildpacks +- Appwrite +- Autoimport + readme +- Services import +- Heroku icon +- Heroku icon +- Dns button ui +- Bot deployments +- Bots +- AutoUpdater & cleanupStorage jobs +- Revert docker compose version to 2.6.1 +- Trim secrets +- Restart containers on-failure instead of always +- Show that Ghost values could be changed +- Bots without exposed ports +- Missing commas +- ExposedPort is just optional +- Port checker +- Cancel build after 5 seconds +- ExposedPort checker +- Batch secret = +- Dashboard for non-root users +- Stream build logs +- Show build log start/end +- Ui buttons +- Clear queue on cancelling jobs +- Cancelling jobs +- Dashboard for admins +- Never stop deplyo queue +- Build queue system +- High cpu usage +- Worker +- Better worker system +- Secrets decryption +- UI thinkgs +- Delete team while it is active +- Team switching +- Queue cleanup +- Decrypt secrets +- Cleanup build cache as well +- Pr deployments + remove public gits +- Copy all files during install process +- Typo +- Process +- White labeled icon on navbar +- Whitelabeled icon +- Next/nuxt deployment type +- Again +- Pr deployment +- CompareVersions +- Include +- Include +- Gitlab apps +- Oh god Prisma +- Glitchtip things +- Loading state on start +- Ui +- Submodule +- Gitlab webhooks +- UI + refactor +- Exposedport on save +- Appwrite letsencrypt +- Traefik appwrite +- Traefik +- Finally works! :) +- Rename components + remove PR/MR deployment from public repos +- Settings missing id +- Explainer component +- Database name on logs view +- Taiga +- Ssh pid agent name +- Repository link trim +- Fqdn or expose port required +- Service deploymentEnabled +- Expose port is not required +- Remote verification +- Dockerfile +- Debug api logging + gh actions +- Workdir +- Move restart button to settings +- Gitlab webhook +- Use ip address instead of window location +- Use ip instead of window location host +- Service state update +- Add initial DNS servers +- Revert last change with domain check +- Service volume generation +- Minio default env variables +- Add php 8.1/8.2 +- Edgedb ui +- Edgedb stuff +- Edgedb +- Pr previews +- DnsServer formatting +- Settings for service +- Change to execa from utils +- Save search input +- Ispublic status on databases +- Port checkers +- Ui variables +- Glitchtip env to pyhton boolean +- Autoupdater +- Show restarting apps +- Show restarting application & logs +- Remove unnecessary gitlab group name +- Secrets for PR +- Volumes for services +- Build secrets for apps +- Delete resource use window location +- Changing umami image URL to get latest version +- Gitlab importer for public repos +- Show error logs +- Umami init sql +- Plausible analytics actions +- Login +- Dev url +- UpdateMany build logs +- Fallback to db logs +- Fluentbit configuration +- Coolify update +- Fluentbit and logs +- Canceling build +- Logging +- Load more +- Build logs +- Versions of appwrite +- Appwrite?! +- Get building status +- Await +- Await #2 +- Update PR building status +- Appwrite default version 1.0 +- Undead endpoint does not require JWT +- *(routes)* Improve design of application page +- *(routes)* Improve design of git sources page +- *(routes)* Ui from destinations page +- *(routes)* Ui from databases page +- *(routes)* Ui from databases page +- *(routes)* Ui from databases page +- *(routes)* Ui from services page +- *(routes)* More ui tweaks +- *(routes)* More ui tweaks +- *(routes)* More ui tweaks +- *(routes)* More ui tweaks +- *(routes)* Ui from settings page +- *(routes)* Duplicates classes in services page +- *(routes)* Searchbar ui +- Github conflicts +- *(routes)* More ui tweaks +- *(routes)* More ui tweaks +- *(routes)* More ui tweaks +- *(routes)* More ui tweaks +- Ui with headers +- *(routes)* Header of settings page in databases +- *(routes)* Ui from secrets table +- Ui +- Tooltip +- Dropdown +- Ssl certificate distribution +- Db migration +- Multiplex ssh connections +- Able to search with id +- Not found redirect +- Settings db requests +- Error during saving logs +- Consider base directory in heroku bp +- Basedirectory should be empty if null +- Allow basedirectory for heroku +- Stream logs for heroku bp +- Debug log for bp +- Scp without host verification & cert copy +- Base directory & docker bp +- Laravel php chooser +- Multiplex ssh and ssl copy +- Seed new preview secret types +- Error notification +- Empty preview value +- Error notification +- Seed +- Service logs +- Appwrite function network is not the default +- Logs in docker bp +- Able to delete apps in unconfigured state +- Disable development low disk space +- Only log things to console in dev mode +- Do not get status of more than 10 resources defined by category +- BaseDirectory +- Dashboard statuses +- Default buildImage and baseBuildImage +- Initial deploy status +- Show logs better +- Do not start tcp proxy without main container +- Cleanup stucked tcp proxies +- Default 0 pending invitations +- Handle forked repositories +- Typo +- Pr branches +- Fork pr previews +- Remove unnecessary things +- Meilisearch data dir +- Verify and configure remote docker engines +- Add buildkit features +- Nope if you are not logged in +- Do not use npx +- Pure docker based development +- Do not show nope as ip address for dbs +- Add git sha to build args +- Smart search for new services +- Logs for not running containers +- Update docker binaries +- Gh release +- Dev container +- Gitlab auth and compose reload +- Check compose domains in general +- Port required if fqdn is set +- Appwrite v1 missing containers +- Dockerfile +- Pull does not work remotely on huge compose file +- Single container logs and usage with compose +- Secret errors +- Service logs +- Heroku bp +- Expose port is readonly on the wrong condition +- Toast +- Traefik proxy q 10s +- App logs view +- Tooltip +- Toast, rde, webhooks +- Pathprefix +- Load public repos +- Webhook simplified +- Remote webhooks +- Previews wbh +- Webhooks +- Websecure redirect +- Wb for previews +- Pr stopps main deployment +- Preview wbh +- Wh catchall for all +- Remove old minio proxies +- Template files +- Compose icon +- Templates +- Confirm restart service +- Template +- Templates +- Templates +- Plausible analytics things +- Appwrite webhook +- Coolify instance proxy +- Migrate template +- Preview webhooks +- Simplify webhooks +- Remove ghost-mariadb from the list +- More simplified webhooks +- Umami + ghost issues +- Remove contribution docs +- Umami template +- Compose webhooks fixed +- Variable replacements +- Doc links +- For rollback +- N8n and weblate icon +- Expose ports for services +- Wp + mysql on arm +- Show rollback button loading +- No tags error +- Update on mobile +- Dashboard error +- GetTemplates +- Docker compose persistent volumes +- Application persistent storage things +- Volume names for undefined volume names in compose +- Empty secrets on UI +- Ports for services +- Default icon for new services +- IsBot issue +- Local dev api/ws urls +- Wrong template/type +- Gitea icon is svg +- Gh actions +- Gh actions +- Replace $$generate vars +- Webhook traefik +- Exposed ports +- Wrong icons on dashboard +- Escape % in secrets +- Move debug log settings to build logs +- Storage for compose bp + debug on +- Hasura admin secret +- Logs +- Mounts +- Load logs after build failed +- Accept logged and not logged user in /base +- Remote haproxy password/etc +- Remove hardcoded sentry dsn +- Nope in database strings +- 0 destinations redirect after creation +- Seed +- Sentry dsn update +- Dnt +- Ui +- Only visible with publicrepo +- Migrations +- Prevent webhook errors to be logged +- Login error +- Remove beta from systemwide git +- Git checkout +- Remove sentry before migration +- Webhook previewseparator +- Apache on arm +- Update PR/MRs with new previewSeparator +- Static for arm +- Failed builds should not push images +- Turn off autodeploy for simpledockerfiles +- Security hole +- Rde +- Delete resource on dashboard +- Wrong port in case of docker compose +- Public db icon on dashboard +- Cleanup +- Build commands +- Migration file +- Adding missing appwrite volume +- Appwrite tmp volume +- Do not replace secret +- Root user for dbs on arm +- Escape secrets +- Escape env vars +- Envs +- Docker buildpack env +- Secrets with newline +- Secrets +- Add default node_env variable +- Add default node_env variable +- Secrets +- Secrets +- Gh actions +- Duplicate env variables +- Cleanupstorage +- Remove unused imports +- Parsing secrets +- Read-only permission +- Read-only iam +- $ sign in secrets +- Custom gitlab git user +- Add documentation link again +- Remove prefetches +- Doc link +- Temporary disable dns check with dns servers +- Local images for reverting +- Secrets +- Compose file location +- Docker log sequence +- Delete apps with previews +- Do not cleanup compose applications as unconfigured +- Build env variables with docker compose +- Public gh repo reload compose +- Build args docker compose +- Grpc +- Secrets +- Www redirect +- Cleanup function +- Cleanup stucked containers +- Deletion + cleanupStuckedContainers +- Stucked containers +- CleanupStuckedContainers +- CleanupStuckedContainers +- Typos in docs +- Url +- Network in compose files +- Escape new line chars in wp custom configs +- Applications cannot be deleted +- Arm servics +- Base directory not found +- Cannot delete resource when you are not on root team +- Empty port in docker compose +- Set PACK_VERSION to 0.27.0 +- PublishDirectory +- Host volumes +- Replace . & .. & $PWD with ~ +- Handle log format volumes +- Nestjs buildpack +- Show ip address as host in public dbs +- Revert from dockerhub if ghcr.io does not exists +- Logo of CCCareers +- Typo +- Ssh +- Nullable name on deploy_keys +- Enviroments +- Remove dd - oops +- Add inprogress activity +- Application view +- Only set status in case the last command block is finished +- Poll activity +- Small typo +- Show activity on load +- Deployment should fail on error +- Tests +- Version +- Status not needed +- No project redirect +- Gh actions +- Set status +- Seeders +- Do not modify localhost +- Deployment_uuid -> type_uuid +- Read env from config, bc of cache +- Private key change view +- New destination +- Do not update next channel all the time +- Cancel deployment button +- Public repo limit shown + branch should be preselected. +- Better status on ui for apps +- Arm coolify version +- Formatting +- Gh actions +- Show github app secrets +- Do not force next version updates +- Debug log button +- Deployment key based works +- Deployment cancel/debug buttons +- Upgrade button +- Changing static build changes port +- Overwrite default nginx configuration +- Do not overlap docker image names +- Oops +- Found image name +- Name length +- Semicolons encoding by traefik +- Base_dir wip & outputs +- Cleanup docker images +- Nginx try_files +- Master is the default, not main +- No ms in rate limit resets +- Loading after button text +- Default value +- Localhost is usable +- Update docker-compose prod +- Cloud/checkoutid/lms +- Type of license code +- More verbose error +- Version lol +- Update prod compose +- Version +- Remove buggregator from dev +- Able to change localhost's private key +- Readonly input box +- Notifications +- Licensing +- Subscription link +- Migrate db schema for smtp + discord +- Text field +- Null fqdn notifications +- Remove old modal +- Proxy stop/start ui +- Proxy UI +- Empty description +- Input and textarea +- Postgres_username name to not name, lol +- DatabaseBackupJob.php +- No storage +- Backup now button +- Ui + subscription +- Self-hosted +- Make coolify-db backups unique dir +- Limits & server creation page +- Fqdn on apps +- DockerCleanupjob +- Validation +- Webhook endpoint in cloud and no system wide gh app +- Subscriptions +- Password confirmation +- Proxy start job +- Dockerimage jobs are not overlapping +- Sentry bug +- Button loading animation +- Form address +- Show hosted email service, just disable for non pro subs +- Add navbar for source + keys +- Add docker network to build process +- Overlapping apps +- Do not show system wide git on cloud +- Lowercase image names +- Typo +- SaveModel email settings +- Bug +- Db backup job +- Sentry 4459819517 +- Sentry 4451028626 +- Ui +- Retry notifications +- Instance email settings +- Ui +- Test email on for admins or custom smtp +- Coolify already exists should not throw error +- Delete database related things when delete database +- Remove -q from docker compose +- Errors in views +- Only send internal notifcations to enabled channels +- Recovery code +- Email sending error +- Sentry 4469575117 +- Old docker version error +- Errors +- Proxy check, reduce jobs, etc +- Queue after commit +- Remove nixpkgarchive +- Remove nixpkgarchive from ui +- Webhooks should not run if server is not functional +- Server is functional check +- Confirm email before sending +- Help should send cc on email +- Sub type +- Show help modal everywhere +- Forgot password +- Disable dockerfile based healtcheck for now +- Add timeout for ssh commands +- Prevent weird ui bug for validateServer +- Lowercase email in forgot password +- Lower case email on waitlist +- Encrypt jobs +- ProcessWithEnv()->run +- Plus boarding step about Coolify +- SaveConfigurationSync +- Help uri +- Sub for root +- Redirect on server not found +- Ip check +- Uniqueips +- Simply reply to help messages +- Help +- Rate limit +- Collect billing address +- Invitation +- Smtp view +- Ssh-agent revert +- Restarting container state on ui +- Generate new key +- Missing upgrade js +- Team error +- 4.0.0-beta.37 +- Localhost +- Proxy start (if not proxy defined, use Traefik) +- Do not remove localhost in boarding +- Allow non ip address (DNS) +- InstallDocker id not found +- Boarding +- Errors +- Proxy container status +- Proxy configuration saving +- Convert startProxy to action +- Stop/start UI on apps and dbs +- Improve localhost boarding process +- Try to use old docker-compose +- Boarding again +- Send internal notifications of email errors +- Add github app change on new app view +- Delete environment variables on app/db delete +- Save proxy configuration +- Add proxy to network with periodic check +- Proxy connections +- Delete persistent storages on resource deletion +- Prevent overwrite already existing env variables in services +- Mappings +- Sentry issue 4478125289 +- Make sure proxy path created +- StartProxy +- Server validation with cf tunnels +- Only show traefik dashboard if its available +- Services +- Database schema +- Report livewire errors +- Links with path +- Add traefik labels no matter if traefik is selected or not +- Add expose port for containers +- Also check docker socks permission on validation +- Applications with port mappins do a normal update (not rolling update) +- Put back build pack chooser +- Proxy configuration + starter +- Show real storage name on services +- New service template layout +- Containerstatusjob +- Aaaaaaaaaaaaaaaaa +- Services view +- Services +- Manually create network for services +- Disable early updates +- Sslip for localhost +- ContainerStatusJob +- Cannot delete env with available services +- Sync command +- Install script drops an error +- Prevent sync version (it needs an option) +- Instance fqdn setting +- Sentry 4510197209 +- Sentry 4504136641 +- Sentry 4502634789 +- Next helper image +- Service templates +- Sync:bunny +- Update process if server has been renamed +- Reporting handler +- Localhost privatekey update +- Remove private key in case you removed a github app +- Only show manually added private keys on server view +- Show source on all type of applications +- Docker cleanup should be a job by server +- File/dir based volumes are now read from the server +- Respect server fqdn +- If public repository does not have a main branch +- Preselect branc on private repos +- Deploykey branch +- Backups are now working again +- Not found base_branch in git webhooks +- Coolify db backup +- Preview deployments name, status etc +- Services should have destination as well +- Dockerfile expose is not overwritten +- If app settings is not saved to db +- Do not show subscription cancelled noti +- Show real volume names +- Only parse expose in dockerfiles if ports_exposes is empty +- Add uuid to volume names +- New volumes for services should have - instead of _ +- Always pull helper image in dev +- Only show last 1000 lines +- Service status +- If waitlist is disabled, redirect to register +- Add destination to new services +- Predefined content for files +- Move /data to ./_data in dev +- UI +- Show all storages in one place for services +- Ui +- Add _data to vite ignore +- Only use _ in volume names for services +- Volume names in services +- Volume names +- Service logs visible if the whole service stack is not running +- Ui +- Compose magic +- Compose parser updated +- Dev compose files +- Traefik labels for multiport deployments +- Visible version number +- Remove SERVICE_ from deployable compose +- Delete event to deleting +- Move dev data to volumes to prevent permission issues +- Traefik labelling in case of several http and https domain added +- PR deployments use the first fqdn as base +- Email notifications subscription fixed +- Services - do not remove unnecessary things for now +- Decrease max horizon processes to get lower memory usage +- Test emails only available for user owned smtp/resend +- Ui for self-hosted email settings +- Set smtp notifications on by default +- Select branch on other git +- Private repository +- Contribution guide +- Public repository names +- *(create)* Flex wrap on server & network selection +- Better unreachable/revived server statuses +- Able to set base dir for Dockerfile build pack +- Server validation process +- Fqdn could be null +- Small +- Server unreachable count +- Do not reset unreachable count +- Contact docs +- Check connection +- Server saving +- No env goto envs from dashboard +- Goto +- Tcp proxy for dbs +- Database backups +- Only send email if transactional email set +- Backupfailed notification is forced +- Use port exposed for reverse proxy +- Contact link +- Use only ip addresses for servers +- Deleted team and it is the current one +- Add new team button +- Transactional email link +- Dashboard goto link +- Only require registry image in case of dockerimage bp +- Instant save build pack change +- Public git +- Cannot remove localhost +- Check localhost connection +- Send unreachable/revived notifications +- Boarding + verification +- Make sure proxy wont start in NONE mode +- Service check status 10 sec +- IsCloud in production seeder +- Make sure to use IP address +- Dockerfile location feature +- Server ip could be hostname in self-hosted +- Urls should be password fields +- No backup for redis +- Show database logs in case of its not healthy and running +- Proxy check for ports, do not kill anything listening on port 80/443 +- Traefik dashboard ip +- Db labels +- Docker cleanup jobs +- Timeout for instant remote processes +- Dev containerjobs +- Backup database one-by-one. +- Turn off static deployment if you switch buildpacks +- Docker hub URL +- Redis URL generated +- Build image before starting dockerfile buildpacks +- Service status check is a bit better +- Generate fqdn if you deleted a service app, but it requires fqdn +- Cancel any deployments + queue next +- Add internal domain names during build process +- Noindex meta tag +- Show docker build logs +- Only include config.json if its exists and a file +- Always start proxy if not NONE is selected +- Proxy start process +- Setup:dev script & contribution guide +- Do not show configuration changed if config_hash is null +- Add config_hash if its null (old deployments) +- Label generation +- Labels +- Email channel no recepients +- Limit horizon processes to 2 by default +- Add custom port as ssh option to deploy_key based commands +- Remove custom port from git repo url +- ContainerStatus job +- Service docs links +- Add PGUSER to prevent HC warning +- Preselect s3 storage if available +- Port exposes change, shoud regenerate label +- Boarding +- Clone to with the same environment name +- Cleanup stucked resources on start +- Do not allow to delete env if a resource is defined +- Service template generator + appwrite +- Mongodb backup +- Make sure coolfiy network exists on install +- Syncbunny command +- Encrypt mongodb password +- Mongodb healtcheck command +- Rate limit for api + add mariadb + mysql +- Server settings guarded +- Space in build args +- Lock SERVICE_FQDN envs +- If user is invited, that means its email is verified +- Force password reset on invited accounts +- Add ssh options to git ls-remote +- Git ls-remote +- Remove coolify labels from ui +- Missing environment variables prevewi on service +- Invoice.paid should sleep for 5 seconds +- Local dev repo +- Deployments ui +- Dockerfile build pack fix +- Set labels on generate domain +- Network service parse +- Notification url in containerstatusjob +- Gh webhook response 200 to installation_repositories +- Delete destination +- No id found +- Missing $mailMessage +- Set default from/sender names +- No environments +- Telegram text +- Private key not found error +- UI +- Resourcesdelete command +- Port number should be int +- Separate delete with validation of server +- Add nixpacks info +- Remove filter +- Container logs are now followable in full-screen and sorted by timestamp +- Ui for labels +- Ui +- Deletions +- Build_image not found +- Github source view +- Github source view +- Dockercleanupjob should be released back +- Ui +- Local ip address +- Revert workdir to basedir +- Container status jobs for old pr deployments +- Service updates +- *(fider template)* Use the correct docs url +- Fqdn for minio +- Generate service fields +- Mariadb backups +- When to pull image +- Do not allow to enter local ip addresses +- Reset password +- Only report nonruntime errors +- Handle different label formats in services +- Server adding process +- Show defined resources in server tab, so you will know what you need to delete before you can delete the server. +- Lots of regarding git + docker compose deployments +- Pull request build variables +- Double default password length +- Do not remove deployment in case compose based failed +- No container servers +- Sentry issue +- Dockercompose save ./ volumes under /data/coolify +- Server view for link() +- Default value do not overwrite existing env value +- Use official install script with rancher (one will work for sure) +- Add cf tunnel to boarding server view +- Prevent autorefresh of proxy status +- Missing docker image thing +- Add hc for soketi +- Deploy the right compose file +- Bind volumes for compose bp +- Use hc port 80 in case of static build +- Switching to static build +- Container selection +- Service navbar using new realtime events +- Do not create duplicated networks +- Live event +- Service start + event +- Service deletion job +- Double ws connection +- Boarding view +- Do not send telegram noti on intent payment failed +- Database ui is realtime based +- Live mode for github webhooks +- Ui +- Realtime connection popup could be disabled +- Realtime check +- Add new destination +- Proxy logs +- Db status check +- Pusher host +- Add ipv6 +- Realtime connection?! +- Websocket +- Better handling of errors with install script +- Install script parse version +- Only allow to modify in .env file if AUTOUPDATE is set +- Is autoupdate not null +- Run init command after production seeder +- Init +- Comma in traefik custom labels +- Ignore if dynamic config could not be set +- Service env variable ovewritten if it has a default value +- Labelling +- Non-ascii chars in labels +- Labels +- Init script echos +- Update Coolify script +- Null notify +- Check queued deployments as well +- Copy invitation +- Password reset / invitation link requests +- Add catch all route +- Revert random container job delay +- Backup executions view +- Only check server status in container status job +- Improve server status check times +- Handle other types of generated values +- Server checking status +- Ui for adding new destination +- Reset domains on compose file change +- Domains for compose bp +- No action in webhooks +- Add debug output to gitlab webhooks +- Do not push dockerimage +- Add alpha to swarm +- Server not found +- Do not autovalidate server on mount +- Server update schedule +- Swarm support ui +- Server ready +- Get swarm service logs +- Docker compose apps env rewritten +- Storage error on dbs +- Why?! +- Stay tuned +- Cpu limit to float from int +- Add source commit to final envs +- Routing, switch back to old one +- Deploy instead of restart in case swarm is used +- Button title +- Restore falsely deleted coolify-db-backup +- Sub +- Wrong env variable parsing +- Deploy key + docker compose +- Horizon +- Duplicate compose variable +- Set deployment failed if new container is not healthy +- Nixpacks cache +- Only add restart policy if its empty (compose) +- Nixpacks buildpack +- File storage save +- Database env variables +- Healthy status +- Show framework based notification in build logs +- Traefik labels +- Use ip for sslip in dev if remote server is used +- Service labels without ports (unknown ports) +- Sort and rename (unique part) of labels +- Settings menu +- Remove traefik debug in dev mode +- Php pgsql to 8.2 +- Static buildpack should set port 80 +- Update navbar on build_pack change +- Do not include thegameplan.json into build image +- Submit error on postgresql +- Email verification / forgot password +- Escape build envs properly for nixpacks + docker build +- Undead endpoint +- Upload limit on ui +- Save cmd output propely (merge) +- Load profile on remote commands +- Load profile and set envs on remote cmd +- Restart should not update config hash +- Preview deployments with nixpacks +- Cleanup docker stuffs before upgrading +- Service deletion command +- Cpuset limits was determined in a way that apps only used 1 CPU max, ehh, sorry. +- Service stack view +- Change proxy view +- Checkbox click +- Git pull command for deploy key based previews +- Server status job +- Service deletion bug! +- Links +- Redis custom conf +- Sentry error +- Restrict concurrent deployments per server +- Queue +- Change env variable length +- Bitbucket manual deployments +- Webhooks for multiple apps +- Unhealthy deployments should be failed +- Add env variables for wordpress template without database +- Service deletion function +- Service deletion fix +- Dns validation + duplicated fqdns +- Validate server navbar upated +- Regenerate labels on application clone +- Service deletion +- Not able to use other shared envs +- Sentry fix +- Sentry +- Sentry error +- Sentry +- Sentry error +- Create dynamic directory +- Migrate to new modal +- Duplicate domain check +- Tags +- Wrap tags and avoid horizontal overflow +- Stripe webhooks +- Feedback from self-hosted envs to discord +- New menu on navbar +- Make sure resources are deleted in async mode +- Go to prod env from dashboard if there is no other envs defined +- User proper image_tag, if set +- New menu ui +- Lock logdrain configuration when one of them are enabled +- Add docker compose check during server validation +- Get service stack as uuid, not name +- Menu +- Flex wrap deployment previews +- Boolean docker options +- Only add 'networks' key if 'network_mode' is absent +- Cleanup scheduled tasks +- Padding left on input boxes +- Use ls / command instead ls +- Do not add the same server twice +- Only show redeployment required if status is not exited +- Add openbsd ssh server check +- Resources +- Empty build variables +- *(server)* Revalidate server button not showing in server's page +- Fluent bit ident level +- Submodule cloning +- Database status +- Permission change updates from webhook +- Server validation +- Connections being stuck and not processed until proxy restarts +- Use latest image if nothing is specified +- No coolify.yaml found +- Server validation +- Statuses +- Unknown image of service until it is uploaded +- Subscription / plan switch, etc +- Firefly service +- Force enable/disable server in case ultimate package quantity decreases +- Server disabled +- Custom dockerfile location always checked +- Import to mysql and mariadb +- Resource tab not loading if server is not reachable +- Load unmanaged async +- Do not show n/a networsk +- Service container status updates +- Public prs should not be commented +- Pull request deployments + build servers +- Env value generation +- Sentry error +- Service status updated +- Should note delete personal teams +- Make sure to show some buttons +- Sort repositories by name +- Deploy api messages +- Fqdn null in case docker compose bp +- Reload caddy issue +- /realtime endpoint +- Proxy switch +- Service ports for services + caddy +- Failed deployments should send failed email/notification +- Consider custom healthchecks in dockerfile +- Create initial files async +- Docker compose validation +- Duplicate dockerfile +- Multiline env variables +- Server stopped, service page not reachable +- Empty get logs number of lines +- Only escape envs after v239+ +- 0 in env value +- Consistent container name +- Custom ip address should turn off rolling update +- Multiline input +- Raw compose deployment +- Dashboard view if no project found +- Volumes for prs +- Shared env variable parsing +- Compose env has SERVICE, but not defined for Coolify +- Public service database +- Make sure service db proxy restarted +- Restart service db proxies +- Two factor +- Ui for tags +- Update resources view +- Realtime connection check +- Multline env in dev mode +- Scheduled backup for other service databases (supabase) +- PR deployments should not be distributed to 2 servers +- Name/from address required for resend +- Autoupdater +- Async service loads +- Disabled inputs are not trucated +- Duplicated generated fqdns are now working +- Uis +- Ui for cftunnels +- Search services +- Trial users subscription page +- Async public key loading +- Unfunctional server should see resources +- Warning if you use multiple domains for a service +- New github app creation +- Always rebuild Dockerfile / dockerimage buildpacks +- Do not rebuild dockerfile based apps twice +- Make sure if envs are changed, rebuild is needed +- Members cannot manage subscriptions +- IsMember +- Storage layout +- How to update docker-compose, environment variables and fqdns +- Git submodule update +- Unintended left padding on sidebar +- Hashed random delimeter in ssh commands + make sure to remove the delimeter from the command +- Service config hash update +- Redeploy if image not found in restart only mode +- Check each required binaries one-by-one +- Helper image only pulled if required, not every 10 mins +- Make sure that confs when checking if it is changed sorted +- Respect .env file (for default values) +- Remove temporary cloudflared config +- Remove lazy loading until bug figured out +- Rollback feature +- Base64 encode .env +- $ in labels escaped +- .env saved to deployment server, not to build server +- Do no able to delete gh app without deleting resources +- 500 error on edge case +- Able to select server when creating new destination +- N8n template +- Refresh public ips on start +- Move s3 storages to separate view +- Mongo db backup +- Backups +- Autoupdate +- Respect start period and chekc interval for hc +- Parse HEALTHCHECK from dockerfile +- Make s3 name and endpoint required +- Able to update source path for predefined volumes +- Get logs with non-root user +- Mongo 4.0 db backup +- Formbricks image origin +- Add port even if traefik is used +- Typo in tags.blade.php +- Install.sh error +- Env file +- Comment out internal notification in email_verify method +- Confirmation for custom labels +- Change permissions on newly created dirs +- Color for resource operation server and project name +- Only show realtime error on non-cloud instances +- Only allow push and mr gitlab events +- Improve scheduled task adding/removing +- Docker compose dependencies for pr previews +- Properly populating dependencies +- Use commit hash on webhooks +- Commit message length +- Hc from localhost to 127.0.0.1 +- Use rc in hc +- Telegram group chat notifications +- PR deployments have good predefined envs +- Optimize new resource creation +- Show it docker compose has syntax errors +- Wrong time during a failed deployment +- Removal of the failed deployment condition, addition of since started instead of finished time +- Use local versions + service templates and query them every 10 minutes +- Check proxy functionality before removing unnecessary coolify.yaml file and checking Docker Engine +- Show first 20 users only in admin view +- Add subpath for services +- Ghost subdir +- Do not pull templates in dev +- Templates +- Update error message for invalid token to mention invalid signature +- Disable containerStopped job for now +- Disable unreachable/revived notifications for now +- JSON_UNESCAPED_UNICODE +- Add wget to nixpacks builds +- Pre and post deployment commands +- Bitbucket commits link +- Better way to add curl/wget to nixpacks +- Root team able to download backups +- Build server should not have a proxy +- Improve build server functionalities +- Sentry issue +- Sentry +- Sentry error + livewire downgrade +- Sentry +- Sentry +- Sentry error +- Sentry +- Force load services from cdn on reload list +- Do not allow service storage mount point modifications +- Volume adding +- Sync upgrade process +- Publish horizon +- Add missing team model +- Test new upgrade process? +- Throw exception +- Build server dirs not created on main server +- Compose load with non-root user +- Able to redeploy dockerfile based apps without cache +- Compose previews does have env variables +- Fine-tune cdn pulls +- Spamming :D +- Parse docker version better +- Compose issues +- SERVICE_FQDN has source port in it +- Logto service +- Allow invitations via email +- Sort by defined order + fixed typo +- Only ignore volumes with driver_opts +- Check env in args for compose based apps +- Custom docker compose commands, add project dir if needed +- Autoupdate process +- Backup executions view +- Handle previously defined compose previews +- Sort backup executions +- Supabase service, newest versions +- Set default name for Docker volumes if it is null +- Multiline variable should be literal + should be multiline in bash with \ +- Gitlab merge request should close PR +- Multiline build args +- Setup script doesnt link to the correct source code file +- Install.sh do not reinstall packages on arch +- Just restart +- Stripprefix middleware correctly labeled to http +- Bitbucket link +- Compose generator +- Do no truncate repositories wtih domain (git) in it +- In services should edit compose file for volumes and envs +- Handle laravel deployment better +- Db proxy status shown better in the UI +- Show commit message on webhooks + prs +- Metrics parsing +- Charts +- Application custom labels reset after saving +- Static build with new nixpacks build process +- Make server charts one livewire component with one interval selector +- You can now add env variable from ui to services +- Update compose environment with UI defined variables +- Refresh deployable compose without reload +- Remove cloud stripe notifications +- App deployment should be in high queue +- Remove zoom from modals +- Get envs before sortby +- MB is % lol +- Projects with 0 envs +- Run user commands on high prio queue +- Load js locally +- Remove lemon + paddle things +- Run container commands on high priority +- Image logo +- Remove both option for api endpoints. it just makes things complicated +- Cleanup subs in cloud +- Show keydbs/dragonflies/clickhouses +- Only run cloud clean on cloud + remove root team +- Force cleanup on busy servers +- Check domain on new app via api +- Custom container name will be the container name, not just internal network name +- Api updates +- Yaml everywhere +- Add newline character to private key before saving +- Add validation for webhook endpoint selection +- Database input validators +- Remove own app from domain checks +- Return data of app update +- Do not overwrite hardcoded variables if they rely on another variable +- Remove networks when deleting a docker compose based app +- Api +- Always set project name during app deployments +- Remove volumes as well +- Gitea pr previews +- Prevent instance fqdn persisting to other servers dynamic proxy configs +- Better volume cleanups +- Cleanup parameter +- Update redirect URL in unauthenticated exception handler +- Respect top-level configs and secrets +- Service status changed event +- Disable sentinel until a few bugs are fixed +- Service domains and envs are properly updated +- *(reactive-resume)* New healthcheck command for MinIO +- *(MinIO)* New command healthcheck +- Update minio hc in services +- Add validation for missing docker compose file +- Typo in is_literal helper +- Env is_literal helper text typo +- Update docker compose pull command with --policy always +- Plane service template +- Vikunja +- Docmost template +- Drupal +- Improve github source creation +- Tag deployments +- New docker compose parsing +- Handle / in preselecting branches +- Handle custom_internal_name check in ApplicationDeploymentJob.php +- If git limit reached, ignore it and continue with a default selection +- Backup downloads +- Missing input for api endpoint +- Volume detection (dir or file) is fixed +- Supabase +- Create file storage even if content is empty +- Preview deployments should be stopped properly via gh webhook +- Deleting application should delete preview deployments +- Plane service images +- Fix issue with deployment start command in ApplicationDeploymentJob +- Directory will be created by default for compose host mounts +- Restart proxy does not work + status indicator on the UI +- Uuid in api docs type +- Raw compose deployment .env not found +- Api -> application patch endpoint +- Remove pull always when uploading backup to s3 +- Handle array env vars +- Link in task failed job notifications +- Random generated uuid will be full length (not 7 characters) +- Gitlab service +- Gitlab logo +- Bitbucket repository url +- By default volumes that we cannot determine if they are directories or files are treated as directories +- Domain update on services on the UI +- Update SERVICE_FQDN/URL env variables when you change the domain +- Several shared environment variables in one value, parsed correctly +- Members of root team should not see instance admin stuff +- Parse docker composer +- Service env parsing +- Service env variables +- Activity type invalid +- Update env on ui +- Only append docker network if service/app is running +- Remove lazy load from scheduled tasks +- Plausible template +- Service_url should not have a trailing slash +- If usagebefore cannot be determined, cleanup docker with force +- Async remote command +- Only run logdrain if necessary +- Remove network if it is only connected to coolify proxy itself +- Dir mounts should have proper dirs +- File storages (dir/file mount) handled properly +- Do not use port exposes on docker compose buildpacks +- Minecraft server template fixed +- Graceful shutdown +- Stop resources gracefully +- Handle null and empty disk usage in DockerCleanupJob +- Show latest version on manual update view +- Empty string content should be saved as a file +- Update Traefik labels on init +- Add missing middleware for server check job +- Scheduledbackup not found +- Manual update process +- Timezone not updated when systemd is missing +- If volumes + file mounts are defined, should merge them together in the compose file +- All mongo v4 backups should use the different backup command +- Database custom environment variables +- Connect compose apps to the right predefined network +- Docker compose destination network +- Server status when there are multiple servers +- Sync fqdn change on the UI +- Pr build names in case custom name is used +- Application patch request instant_deploy +- Canceling deployment on build server +- Backup of password protected postgresql database +- Docker cleanup job +- Storages with preserved git repository +- Parser parser parser +- New parser only in dev +- Parser parser +- Numberoflines should be number +- Docker cleanup job +- Fix directory and file mount headings in file-storage.blade.php +- Preview fqdn generation +- Revert a few lines +- Service ui sync bug +- Setup script doesn't work on rhel based images with some curl variant already installed +- Let's wait for healthy container during installation and wait an extra 20 seconds (for migrations) +- Infra files +- Log drain only for Applications +- Copy large compose files through scp (not ssh) +- Check if array is associative or not +- Openapi endpoint urls +- Convert environment variables to one format in shared.php +- Logical volumes could be overwritten with new path +- Env variable in value parsed +- Pull coolify image only when the app needs to be updated +- Wrong executions order +- Handle project not found error in environment_details API endpoint +- Deployment running for - without "ago" +- Update helper image pulling logic to only pull if the version is newer +- Parser +- Plunk NEXT_PUBLIC_API_URI +- Reenable overlapping servercheckjob +- Appwrite template + parser +- Don't add `networks` key if `network_mode` is used +- Remove debug statement in shared.php +- Scp through cloudflare +- Delete older versions of the helper image other than the latest one +- Update remoteProcess.php to handle null values in logItem properties +- Disable mux_enabled during server validation +- Move mc command to coolify image from helper +- Keydb. add `:` delimiter for connection string +- Cloudflare tunnel with new multiplexing feature +- Keep-alive ws connections +- Add build.sh to debug logs +- Update Coolify installer +- Terminal +- Generate https for minio +- Install script +- Handle WebSocket connection close in terminal.blade.php +- Able to open terminal to any containers +- Refactor run-command +- If you exit a container manually, it should close the underlying tty as well +- Move terminal to separate view on services +- Only update helper image in DB +- Generated fqdn for SERVICE_FQDN_APP_3000 magic envs +- Proxy status +- Coolify-db should not be in the managed resources +- Store original root key in the original location +- Logto service +- Cloudflared service +- Migrations +- Cloudflare tunnel configuration, ui, etc +- Parser +- Exited services statuses +- Make sure to reload window if app status changes +- Deploy key based deployments +- Proxy fixes +- Proxy +- *(templates)* Filebrowser FQDN env variable +- Handle edge case when build variables and env variables are in different format +- Compose based terminal +- Filebrowser template +- Edit is_build_server_enabled upon creating application on other application type +- Save settings after assigning value +- In dev mode do not ask confirmation on delete +- Mixpost +- Handle deletion of 'hello' in confirmation modal for dev environment +- Remove autofocuses +- Ipv6 scp should use -6 flag +- Cleanup stucked applicationdeploymentqueue +- Realtime watch in development mode +- Able to select root permission easier +- Able to support more database dynamically from Coolify's UI +- Strapi template +- Bitcoin core template +- Api useBuildServer +- Service application view +- Add new supported database images +- Parse proxy config and check the set ports usage +- Update FQDN +- Scheduled backup for services view +- Parser, espacing container labels +- Reset description and subject fields after submitting feedback +- Tag mass redeployments +- Service env orders, application env orders +- Proxy conf in dev +- One-click services +- Use local service-templates in dev +- New services +- Remove not used extra host +- Chatwoot service +- Directus +- Database descriptions +- Update services +- Soketi +- Select server view +- Update mattermost image tag and add default port +- Remove env, change timezone +- Postgres healthcheck +- Azimutt template - still not working haha +- New parser with SERVICE_URL_ envs +- Improve service template readability +- Update password variables in Service model +- Scheduled database server +- Select server view +- Signup +- Application domains should be http and https only +- Validate and sanitize application domains +- Sanitize and validate application domains +- Use correct env variable for invoice ninja password +- Make sure caddy is not removed by cleanup +- Libretranslate +- Do not allow to change number of lines when streaming logs +- Plunk +- No manual timezones +- Helper push +- Format +- Add port metadata and Coolify magic to generate the domain +- Sentinel +- Metrics +- Generate sentinel url +- Only enable Sentinel for new servers +- Is_static through API +- Allow setting standalone redis variables via ENVs (team variables...) +- Check for username separately form password +- Encrypt all existing redis passwords +- Pull helper image on helper_version change +- Redis database user and password +- Able to update ipv4 / ipv6 instance settings +- Metrics for dbs +- Sentinel start fixed +- Validate sentinel custom URL when enabling sentinel +- Should be able to reset labels in read-only mode with manual click +- No sentinel for swarm yet +- Charts ui +- Volume +- Sentinel config changes restarts sentinel +- Disable sentinel for now +- Disable Sentinel temporarily +- Disable Sentinel temporarily for non-dev environments +- Access team's github apps only +- Admins should now invite owner +- Add experimental flag +- GenerateSentinelUrl method +- NumberOfLines could be null +- Login / register view +- Restart sentinel once a day +- Changing private key manually won't trigger a notification +- Grammar for helper +- Fix my own grammar +- Add telescope only in dev mode +- New way to update container statuses +- Only run server storage every 10 mins if sentinel is not active +- Cloud admin view +- Queries in kernel.php +- Lower case emails only +- Change emails to lowercase on init +- Do not error on update email +- Always authenticate with lowercase emails +- Dashboard refactor +- Add min/max length to input/texarea +- Remove livewire legacy from help view +- Remove unnecessary endpoints (magic) +- Transactional email livewire +- Destinations livewire refactor +- Refactor destination/docker view +- Logdrains validation +- Reworded +- Use Auth(), add new db proxy stop event refactor clickhouse view +- Add user/pw to db view +- Sort servers by name +- Keydb view +- Refactor tags view / remove obsolete one +- Send discord/telegram notifications on high job queue +- Server view refresh on validation +- ShowBoarding +- Show docker installation logs & ubuntu 24.10 notification +- Do not overlap servercheckjob +- Server limit check +- Server validation +- Clear route / view +- Only skip docker installation on 24.10 if its not installed +- For --gpus device support +- Db/service start should be on high queue +- Do not stop sentinel on Coolify restart +- Run resourceCheck after new serviceCheckJob +- Mongodb in dev +- Better invitation errors +- Loading indicator for db proxies +- Do not execute gh workflow on template changes +- Only use sentry in cloud +- Update packagejson of coolify-realtime + add lock file +- Update last online with old function +- Seeder should not start sentinel +- Start sentinel on seeder +- Notifications ui +- Disable wire:navigate +- Confirmation Settings css for light mode +- Server wildcard +- Saving resend api key +- Wildcard domain save +- Disable cloudflare tunnel on "localhost" +- Define separate volumes for mattermost service template +- Github app name is too long +- ServerTimezone update +- Trigger.dev db host & sslmode=disable +- Manual update should be executed only once + better UX +- Upgrade.sh +- Missing privateKey +- Show proper error message on invalid Git source +- Convert HTTP to SSH source when using deploy key on GitHub +- Cloud + stripe related +- Terminal view loading in async +- Cool 500 error (thanks hugodos) +- Update schema in code decorator +- Openapi docs +- Add tests for git url converts +- Minio / logto url generation +- Admin view +- Min docker version 26 +- Pull latest service-templates.json on init +- Workflow files for coolify build +- Autocompletes +- Timezone settings validation +- Invalid tz should not prevent other jobs to be executed +- Testing-host should be built locally +- Poll with modal issue +- Terminal opening issue +- If service img not found, use github as a source +- Fallback to local coolify.png +- Gather private ips +- Cf tunnel menu should be visible when server is not validated +- Deployment optimizations +- Init script + optimize laravel +- Default docker engine version + fix install script +- Pull helper image on init +- SPA static site default nginx conf +- Modal-input +- Modal (+ add) on dynamic config was not opening, removed x-cloak +- AUTOUPDATE + checkbox opacity +- Improve helper text for metrics input fields +- Refine helper text for metrics input fields +- If mux conn fails, still use it without mux + save priv key with better logic +- Migration +- Always validate ssh key +- Make sure important jobs/actions are running on high prio queue +- Do not send internal notification for backups and status jobs +- Validateconnection +- View issue +- Heading +- Remove mux cleanup +- Db backup for services +- Version should come from constants + fix stripe webhook error reporting +- Undefined variable +- Remove version.php as everything is coming from constants.php +- Sentry error +- Websocket connections autoreconnect +- Sentry error +- Sentry +- Empty server API response +- Incorrect server API patch response +- Missing `uuid` parameter on server API patch +- Missing `settings` property on servers API +- Move servers API `delete_unused_*` properties +- Servers API returning `port` as a string -> integer +- Only return server uuid on server update +- Service generate includes yml files as well (haha) +- ServercheckJob should run every 5 minutes on cloud +- New resource icons +- Search should be more visible on scroll on new resource +- Logdrain settings +- Ui +- Email should be retried with backoff +- Alpine in body layout +- Application view loading +- Postiz service +- Only able to select the right keys +- Test email should not be required +- A few inputs +- Api endpoint +- Resolve undefined searchInput reference in Alpine.js component +- URL and sync new app name +- Typos and naming +- Client and webhook secret disappear after sync +- Missing `mysql_password` API property +- Incorrect MongoDB init API property +- Old git versions does not have --cone implemented properly +- Don't allow editing traefik config +- Restart proxy +- Dev mode +- Ui +- Display actual values for disk space checks in installer script +- Proxy change behaviour +- Add warning color +- Import NotificationSlack correctly +- Add middleware to new abilities, better ux for selecting permissions, etc. +- Root + read:sensive could read senstive data with a middlewarew +- Always have download logs button on scheduled tasks +- Missing css +- Development image +- Dockerignore +- DB migration error +- Drop all unused smtp columns +- Backward compatibility +- Email notification channel enabled function +- Instance email settins +- Make sure resend is false if SMTP is true and vice versa +- Email Notification saving +- Slack and discord url now uses text filed because encryption makes the url very long +- Notification trait +- Encryption fixes +- Docker cleanup email template +- Add missing deployment notifications to telegram +- New docker cleanup settings are now saved to the DB correctly +- Ui + migrations +- Docker cleanup email notifications +- General notifications does not go through email channel +- Test notifications to only send it to the right channel +- Remove resale_license from db as well +- Nexus service +- Fileflows volume names +- --cone +- Provider error +- Database migration +- Seeder +- Migration call +- Slack helper +- Telegram helper +- Discord helper +- Telegram topic IDs +- Make pushover settings more clear +- Typo in pushover user key +- Use Livewire refresh method and lock properties +- Create pushover settings for existing teams +- Update token permission check from 'write' to 'root' +- Pushover +- Oauth seeder +- Correct heading display for OAuth settings in settings-oauth.blade.php +- Adjust spacing in login form for improved layout +- Services env values should be sensitive +- Documenso +- Dolibarr +- Typo +- Update OauthSettingSeeder to handle new provider definitions and ensure authentik is recreated if missing +- Improve OauthSettingSeeder to correctly delete non-existent providers and ensure proper handling of provider definitions +- Encrypt resend API key in instance settings +- Resend api key is already a text column +- Monaco editor light and dark mode switching +- Service status indicator + oauth saving +- Socialite for azure and authentik +- Saving oauth +- Fallback for copy button +- Copy the right text +- Maybe fallback is now working +- Only show copy button on secure context +- Render html on error page correctly +- Invalid API response on missing project +- Applications API response code + schema +- Applications API writing to unavailable models +- If an init script is renamed the old version is still on the server +- Oauthseeder +- Compose loading seq +- Resource clone name + volume name generation +- Update Dockerfile entrypoint path to /etc/entrypoint.d +- Debug mode +- Unreachable notifications +- Remove duplicated ServerCheckJob call +- Few fixes and use new ServerReachabilityChanged event +- Use serverStatus not just status +- Oauth seeder +- Service ui structure +- Check port 8080 and fallback to 80 +- Refactor database view +- Always use docker cleanup frequency +- Advanced server UI +- Html css +- Fix domain being override when update application +- Use nixpacks predefined build variables, but still could update the default values from Coolify +- Use local monaco-editor instead of Cloudflare +- N8n timezone +- Smtp encryption +- Bind() to 0.0.0.0:80 failed +- Oauth seeder +- Unreachable notifications +- Instance settings migration +- Only encrypt instance email settings if there are any +- Error message +- Update healthcheck and port configurations to use port 8080 +- Compose envs +- Scheduled tasks and backups are executed by server timezone. +- Show backup timezone on the UI +- Disappearing UI after livewire event received +- Add default vector db for anythingllm +- We need XSRF-TOKEN for terminal +- Prevent default link behavior for resource and settings actions in dashboard +- Increase default php memory limit +- Show if only build servers are added to your team +- Update Livewire button click method to use camelCase +- Local dropzonejs +- Import backups due to js stuff should not be navigated +- Install inetutils on Arch Linux +- Use ip in place of hostname from inetutils in arch +- Update import command to append file redirection for database restoration +- Ui bug on pw confirmation +- Exclude system and computed fields from model replication +- Service cloning on a separate server +- Application cloning +- `Undefined variable $fs_path` for databases +- Service and database cloning and label generation +- Labels and URL generation when cloning +- Clone naming for different database data volumes +- Implement all the cloneMe changes for ResourceOperations as well +- Volume and fileStorages cloning +- View text and helpers +- Teable +- Trigger with external db +- Set `EXPERIMENTAL_FEATURES` to false for labelstudio +- Monaco editor disabled state +- Edge case where executions could be null +- Create destination properly +- Getcontainer status should timeout after 30s +- Enable response for temporary unavailability in sentinel push endpoint +- Use timeout in cleanup resources +- Add timeout to sentinel process checks for improved reliability +- Horizon job checker +- Update response message for sentinel push route +- Add own servers on cloud +- Application deployment +- Service update statsu +- If $SERVICE found in the service specific configuration, then search for it in the db +- Instance wide GitHub apps are not available on other teams then the source team +- Function calls +- UI +- Deletion of single backup +- Backup job deletion - delete all backups from s3 and local +- Use new removeOldBackups function +- Retention functions and folder deletion for local backups +- Storage retention setting +- Db without s3 should still backup +- Wording +- `Undefined variable $service` when creating a new service +- Nodebb service +- Calibre-web service +- Rallly and actualbudget service +- Removed container_name +- Added healthcheck for gotenberg template +- Gotenberg +- *(template)* Gotenberg healthcheck, use /health instead of /version +- Use wire:navigate on sidebar +- Use wire:navigate on dashboard +- Use wire:navigate on projects page +- More wire:navigate +- Even more wire:navigate +- Service navigation +- Logs icons everywhere + terminal +- Redis DB should use the new resourceable columns +- Joomla service +- Add back letters to prod password requirement +- Check System and GitHub time and throw and error if it is over 50s out of sync +- Error message and server time getting +- Error rendering +- Render html correctly now +- Indent +- Potential fix for permissions update +- Expiration time claim ('exp') must be a numeric value +- Sanitize html error messages +- Production password rule and cleanup code +- Use json as it is just better than string for huge amount of logs +- Use `wire:navigate` on server sidebar +- Use finished_at for the end time instead of created_at +- Cancelled deployments should not show end and duration time +- Redirect to server index instead of show on error in Advanced and DockerCleanup components +- Disable registration after creating the root user +- RootUserSeeder +- Regex username validation +- Add spacing around echo outputs +- Success message +- Silent return if envs are empty or not set. +- Create the private key before the server in the prod seeder +- Update ProductionSeeder to check for private key instead of server's private key +- *(ui)* Missing underline for docs link in the Swarm section (#4860) +- *(service)* Change chatwoot service postgres image from `postgres:12` to `pgvector/pgvector:pg12` +- Docker image parser +- Add public key attribute to privatekey model +- Correct service update logic in Docker Compose parser +- Update CDN URL in install script to point to nightly version +- *(service)* Add healthcheck to Cloudflared service (#4859) +- Remove wire:navigate from import backups +- *(ui)* Backups link should not redirected to general +- Envs with special chars during build +- *(db)* `finished_at` timestamps are not set for existing deployments +- Load service templates on cloud +- *(email)* Transactional email sending +- *(ui)* Add missing save button for new Docker Cleanup page +- *(ui)* Show preview deployment environment variables +- *(ui)* Show error on terminal if container has no shell (bash/sh) +- *(parser)* Resource URL should only be parsed if there is one +- *(core)* Compose parsing for apps +- *(redis)* Update environment variable keys from standalone_redis_id to resourceable_id +- *(routes)* Local API docs not available on domain or IP +- *(routes)* Local API docs not available on domain or IP +- *(core)* Update application_id references to resourable_id and resourable_type for Nixpacks configuration +- *(core)* Correct spelling of 'resourable' to 'resourceable' in Nixpacks configuration for ApplicationDeploymentJob +- *(ui)* Traefik dashboard url not working +- *(ui)* Proxy status badge flashing during navigation +- *(core)* Update environment variable generation logic in ApplicationDeploymentJob to handle different build packs +- *(env)* Shared variables can not be updated +- *(ui)* Metrics stuck in loading state +- *(ui)* Use `wire:navigate` to navigate to the server settings page +- *(service)* Plunk API & health check endpoint (#4925) +- *(service)* Infinite loading and lag with invoiceninja service (#4876) +- *(service)* Invoiceninja service +- *(workflows)* `Waiting for changes` label should also be considered and improved messages +- *(workflows)* Remove tags only if the PR has been merged into the main branch +- *(terminal)* Terminal shows that it is not available, even though it is +- *(labels)* Docker labels do not generated correctly +- *(helper)* Downgrade Nixpacks to v1.29.0 +- *(labels)* Generate labels when they are empty not when they are already generated +- *(storage)* Hetzner storage buckets not working +- *(ui)* Update database control UI to check server functionality before displaying actions +- *(ui)* Typo in upgrade message +- *(ui)* Cloudflare tunnel configuration should be an info, not a warning +- *(s3)* DigitalOcean storage buckets do not work +- *(ui)* Correct typo in container label helper text +- Disable certain parts if readonly label is turned off +- Cleanup old scheduled_task_executions +- Validate cron expression in Scheduled Task update +- *(core)* Check cron expression on save +- *(database)* Detect more postgres database image types +- *(templates)* Update service templates +- Remove quotes in COOLIFY_CONTAINER_NAME +- *(templates)* Update Trigger.dev service templates with v3 configuration +- *(database)* Adjust MongoDB restore command and import view styling +- *(core)* Improve public repository URL parsing for branch and base directory +- *(core)* Increase HTTP/2 max concurrent streams to 250 (default) +- *(ui)* Update docker compose file helper text to clarify repository modification +- *(ui)* Skip SERVICE_FQDN and SERVICE_URL variables during update +- *(core)* Stopping database is not disabling db proxy +- *(core)* Remove --remove-orphans flag from proxy startup command to prevent other proxy deletions (db) +- *(api)* Domain check when updating domain +- *(ui)* Always redirect to dashboard after team switch +- *(backup)* Escape special characters in database backup commands +- *(core)* Improve deployment failure Slack notification formatting +- *(core)* Update Slack notification formatting to use bold correctly +- *(core)* Enhance Slack deployment success notification formatting +- *(ui)* Simplify service templates loading logic +- *(ui)* Align title and add button vertically in various views +- Handle pullrequest:updated for reliable preview deployments +- *(ui)* Fix typo on team page (#5105) +- Cal.com documentation link give 404 (#5070) +- *(slack)* Notification settings URL in `HighDiskUsage` message (#5071) +- *(ui)* Correct typo in Storage delete dialog (#5061) +- *(lang)* Add missing italian translations (#5057) +- *(service)* Improve duplicati.yaml (#4971) +- *(service)* Links in homepage service (#5002) +- *(service)* Added SMTP credentials to getoutline yaml template file (#5011) +- *(service)* Added `KEY` Variable to Beszel Template (#5021) +- *(cloudflare-tunnels)* Dead links to docs (#5104) +- System-wide GitHub apps (#5114) +- Pull latest image from registry when using build server +- *(deployment)* Improve server selection for deployment cancellation +- *(deployment)* Improve log line rendering and formatting +- *(s3-storage)* Optimize team admin notification query +- *(core)* Improve connection testing with dynamic disk configuration for s3 backups +- *(core)* Update service status refresh event handling +- *(ui)* Adjust polling intervals for database and service status checks +- *(service)* Update Fider service template healthcheck command +- *(core)* Improve server selection error handling in Docker component +- *(core)* Add server functionality check before dispatching container status +- *(ui)* Disable sticky scroll in Monaco editor +- *(ui)* Add literal and multiline env support to services. +- *(services)* Owncloud docs link +- *(template)* Remove db-migration step from `infisical.yaml` (#5209) +- *(service)* Penpot (#5047) +- *(core)* Production dockerfile +- *(ui)* Update storage configuration guidance link +- *(ui)* Set default SMTP encryption to starttls +- *(notifications)* Correct environment URL path in application notifications +- *(config)* Update default PostgreSQL host to coolify-db instead of postgres +- *(docker)* Improve Docker compose file validation process +- *(ui)* Restrict service retrieval to current team +- *(core)* Only validate custom compose files +- *(mail)* Set default mailer to array when not specified +- *(ui)* Correct redirect routes after task deletion +- *(core)* Adding a new server should not try to make the default docker network +- *(core)* Clean up unnecessary files during application image build +- *(core)* Improve label generation and merging for applications and services +- *(billing)* Handle 'past_due' subscription status in Stripe processing +- *(revert)* Label parsing +- *(helpers)* Initialize command variable in parseCommandFromMagicEnvVariable +- *(billing)* Restrict Stripe subscription status update to 'active' only +- *(api)* Docker compose based apps creationg through api +- *(database)* Improve database type detection for Supabase Postgres images +- *(ssl)* Permission of ssl crt and key inside the container +- *(ui)* Make sure file mounts do not showing the encrypted values +- *(ssl)* Make default ssl mode require not verify-full as it does not need a ca cert +- *(ui)* Select component should not always uses title case +- *(db)* SSL certificates table and model +- *(migration)* Ssl certificates table +- *(databases)* Fix database name users new `uuid` instead of DB one +- *(database)* Fix volume and file mounts and naming +- *(migration)* Store subjectAlternativeNames as a json array in the db +- *(ssl)* Make sure the subjectAlternativeNames are unique and stored correctly +- *(ui)* Certificate expiration data is null before starting the DB +- *(deletion)* Fix DB deletion +- *(ssl)* Improve SSL cert file mounts +- *(ssl)* Always create ca crt on disk even if it is already there +- *(ssl)* Use mountPath parameter not a hardcoded path +- *(ssl)* Use 1 instead of on for mysql +- *(ssl)* Do not remove SSL directory +- *(ssl)* Wrong ssl cert is loaded to the server and UI error when regenerating SSL +- *(ssl)* Make sure when regenerating the CA cert it is not overwritten with a server cert +- *(ssl)* Regenerating certs for a specific DB +- *(ssl)* Fix MariaDB and MySQL need CA cert +- *(ssl)* Add mount path to DB to fix regeneration of certs +- *(ssl)* Fix SSL regeneration to sign with CA cert and use mount path +- *(ssl)* Get caCert correctly +- *(ssl)* Remove caCert even if it is a folder by accident +- *(ssl)* Ger caCert and `mountPath` correctly +- *(ui)* Only show Regenerate SSL Certificates button when there is a cert +- *(ssl)* Server id +- *(ssl)* When regenerating SSL certs the cert is not singed with the new CN +- *(ssl)* Adjust ca paths for MySQL +- *(ssl)* Remove mode selection for MariaDB as it is not supported +- *(ssl)* Permission issue with MariDB cert and key and paths +- *(ssl)* Rename Redis mode to verify-ca as it is not verify-full +- *(ui)* Remove unused mode for MongoDB +- *(ssl)* KeyDB port and caCert args are missing +- *(ui)* Enable SSL is not working correctly for KeyDB +- *(ssl)* Add `--tls` arg to DrangflyDB +- *(notification)* Always send SSL notifications +- *(database)* Change default value of enable_ssl to false for multiple tables +- *(ui)* Correct grammatical error in 404 page +- *(seeder)* Update GitHub app name in GithubAppSeeder +- *(plane)* Update APP_RELEASE to v0.25.2 in environment configuration +- *(domain)* Dispatch refreshStatus event after successful domain update +- *(database)* Correct container name generation for service databases +- *(database)* Limit container name length for database proxy +- *(database)* Handle unsupported database types in StartDatabaseProxy +- *(database)* Simplify container name generation in StartDatabaseProxy +- *(install)* Handle potential errors in Docker address pool configuration +- *(backups)* Retention settings +- *(redis)* Set default redis_username for new instances +- *(core)* Improve instantSave logic and error handling +- *(general)* Correct link to framework specific documentation +- *(core)* Redirect healthcheck route for dockercompose applications +- *(api)* Use name from request payload +- *(issue#4746)* Do not use setGitImportSettings inside of generateGitLsRemoteCommands +- Correct some spellings +- *(service)* Replace deprecated credentials env variables on keycloak service +- *(keycloak)* Update keycloak image version to 26.1 +- *(console)* Handle missing root user in password reset command +- *(ssl)* Handle missing CA certificate in SSL regeneration job +- *(copy-button)* Ensure text is safely passed to clipboard +- *(file-storage)* Double save on compose volumes +- *(parser)* Add logging support for applications in services +- Only get apps for the current team +- *(DeployController)* Cast 'pr' query parameter to integer +- *(deploy)* Validate team ID before deployment +- *(wakapi)* Typo in env variables and add some useful variables to wakapi.yaml (#5424) +- *(database)* Custom config for MongoDB (#5471) +- *(ui)* Instance Backup settings +- *(docs)* Comment out execute for now +- *(installation)* Mount the docker config +- *(installation)* Path to config file for docker login +- *(service)* Add health check to Bugsink service (#5512) +- *(email)* Emails are not sent in multiple cases +- *(deployments)* Use graceful shutdown instead of `rm` +- *(docs)* Contribute service url (#5517) +- *(proxy)* Proxy restart does not work on domain +- *(ui)* Only show copy button on https +- *(api)* Used ssh keys can be deleted +- *(email)* Transactional emails not sending ### 💼 Other +- Only allow cleanup in production +- Make copy/password visible +- Dns check +- Remote docker engine +- Colorful states +- Application start +- Colors on svelte-select +- Improvements - Fix - Better layout for root team - Fix @@ -6276,569 +9699,1273 @@ All notable changes to this project will be documented in this file. - Fix - Fixes - Fixes +- Show extraconfig if wp is running +- Umami service +- Base image selector +- Laravel +- Appwrite +- Testing WS +- Traefik?! +- Traefik +- Traefik +- Traefik migration +- Traefik +- Traefik +- Traefik +- Notifications and application usage +- *(fix)* Traefik +- Css +- Error message https://github.com/coollabsio/coolify/issues/502 +- Changes +- Settings +- For removing app +- Local ssh port +- Redesign a lot +- Fixes +- Loading indicator for plausible buttons +- Fix +- Fider +- Typing +- Fixes here and there +- Dashboard fine-tunes +- Fine-tune +- Fixes +- Fix +- Dashbord fixes +- Fixes +- Fixes +- Route to the correct path when creating destination from db config +- Fixes +- Change tooltips and info boxes +- Added rc release +- Database_branches +- Login page +- Fix login/register page +- Update devcontainer +- Add debug log +- Fix initial loading icon bg +- Fix loading start/stop db/services +- Dashboard updates and a lot more +- Dashboard updates +- Fix tooltip +- Fix button +- Fix follow button +- Arm should be on next all the time +- Fix plausible +- Fix cleanup button +- Fix buttons +- Responsive! +- Fixes +- Fix git icon +- Dropdown as infobox +- Small logs on mobile +- Improvements +- Fix destination view +- Settings view +- More UI improvements +- Fixes +- Fixes +- Fix +- Fixes +- Beta features +- Fix button +- Service fixes +- Fix basedirectory meaning +- Resource button fix +- Main resource search +- Dev logs +- Loading button +- Fix gitlab importer view +- Small fix +- Beta flag +- Hasura console notification +- Fix +- Fix +- Fixes +- Inprogress version of iam +- Fix indicato +- Iam & settings update +- Send 200 for ping and installation wh +- Settings icon +- Docker-compose support +- Docker compose +- Remove worker jobs +- One less worker thread +- New resource label +- Secrets on apps +- Fix +- Fixes +- Reload compose loading +- Pocketbase release +- Trpc +- Trpc +- Trpc +- Trpc +- Trpc +- Trpc +- Trpc +- Trpc +- Trpc +- Trpc +- Conditional on environment +- Add missing variables +- Trpc +- Trpc +- Trpc +- Trpc +- Trpc +- Trpc +- Trpc +- Extract process handling from async job. +- Extract process handling from async job. +- Extract process handling from async job. +- Extract process handling from async job. +- Extract process handling from async job. +- Extract process handling from async job. +- Extract process handling from async job. +- Persisting data +- Scheduled backups +- Boarding +- Backup existing database +- User should know that the public key +- Services are not availble yet +- Show registered users on waitlist page +- Nixpacksarchive +- Add Plausible analytics +- Global env variables +- Fix +- Trial emails +- Server check instead of app check +- Show trial instead of sub +- Server lost connection +- Services +- Services +- Services +- Ui for services +- Services +- Services +- Services +- Fixes +- Fix typo +- Fixed z-index for version link. +- Add source button +- Fixed z-index for magicbar +- A bit better error +- More visible feedback button +- Update help modal +- Help +- Marketing emails +- Fix previews to preview +- Uptime kume hc updated +- Switch back to /data (volume errors) +- Notifications +- Add shared email option to everyone +- Dockerimage +- Updated dashboard +- Fix +- Fix +- Coolify proxy access logs exposed in dev +- Able to select environment on new resource +- Delete server +- Redis +- Wordpress +- Add helper to service domains +- PAT by team +- Generate services +- Mongodb backup +- Mongodb backup +- Updates +- Fix subs +- New deployment jobs +- Compose based apps +- Swarm +- Swarm +- Swarm +- Swarm +- Disable trial +- Meilisearch +- Broadcast +- 🌮 +- Env vars +- Migrate to livewire 3 +- Fix for comma in labels +- Add image name to service stack + better options visibility +- Swarm +- Swarm +- Send notification email if payment +- New modal component +- Specific about newrelic logdrains +- Updates +- Change + icon to hamburger. +- Redesign +- Redesign +- Run cleanup every day +- Fix +- Fix log outputs +- Automatic cloudflare tunnels +- Backup executions +- Light buttons +- Multiple server view +- New pricing +- Fix allowTab logic +- Use 2 space instead of tab +- Non-root user for remote servers +- Non-root +- Update resource operations view +- Fix tag view +- Fix a few boxes here and there +- Responsive here and there +- Rocketchat +- New services based git apps +- Unnecessary notification +- Update process +- Glances service +- Glances +- Able to update application +- Add basedir + compose file in new compose based apps +- Formbricks template add required CRON_SECRET +- Add required CRON_SECRET to Formbricks template +- Service env parsing +- Actually update timezone on the server +- Cron jobs are executed based on the server timezone +- Server timezone seeder +- Recent backups UI +- Use apt-get instead of apt +- Typo +- Only pull helper image if the version is newer than the one +- Plunk svg +- Pull helper image if not available otherwise s3 backup upload fails +- Set a default server timezone +- Implement SSH Multiplexing +- Enabel mux +- Cleanup stale multiplexing connections +- Remote servers with port and user +- Do not change localhost server name on revalidation +- Release.md file +- SSH Multiplexing on docker desktop on Windows +- Remove labels and assignees on issue close +- Make sure this action is also triggered on PR issue close +- Volumes on development environment +- Clean new volume name for dev volumes +- Persist DBs, services and so on stored in data/coolify +- Add SSH Key fingerprint to DB +- Add a fingerprint to every private key on save, create... +- Make sure invalid private keys can not be added +- Encrypt private SSH keys in the DB +- Add is_sftp and is_server_ssh_key coloums +- New ssh key file name on disk +- Store all keys on disk by default +- Populate SSH key folder +- Populate SSH keys in dev +- Use new function names and logic everywhere +- Create a Multiplexing Helper +- SSH multiplexing +- Remove unused code form multiplexing +- SSH Key cleanup job +- Private key with ID 2 on dev +- Move more functions to the PrivateKey Model +- Add ssh key fingerprint and generate one for existing keys +- ID issues on dev seeders +- Server ID 0 +- Make sure in use private keys are not deleted +- Do not delete SSH Key from disk during server validation error +- UI bug, do not write ssh key to disk in server dialog +- SSH Multiplexing for Jobs +- SSH algorhytm text +- Few multiplexing things +- Clear mux directory +- Multiplexing do not write file manually +- Integrate tow step process in the modal component WIP +- Ability to hide labels +- DB start, stop confirm +- Del init script +- General confirm +- Preview deployments and typos +- Service confirmation +- Confirm file storage +- Stop service confirm +- DB image cleanup +- Confirm ressource operation +- Environment variabel deletion +- Confirm scheduled tasks +- Confirm API token +- Confirm private key +- Confirm server deletion +- Confirm server settings +- Proxy stop and restart confirmation +- GH app deletion confirmation +- Redeploy all confirmation +- User deletion confirmation +- Team deletion confirmation +- Backup job confirmation +- Delete volume confirmation +- More conformations and fixes +- Delete unused private keys button +- Ray error because port is not uncommented +- #3322 deploy DB alterations before updating +- Css issue with advanced settings and remove cf tunnel in onboarding +- New cf tunnel install flow +- Made help text more clear +- Cloudflare tunnel +- Make helper text more clean to use a FQDN and not an URL +- Manual cleanup button and unused volumes and network deletion +- Force helper image removal +- Use the new confirmation flow +- Typo +- Typo in install script +- If API is disabeled do not show API token creation stuff +- Disable API by default +- Add debug bar +- Remove memlock as it caused problems for some users +- Server storage check +- Show backup button on supported db service stacks +- Update helper version +- Outline +- Directus +- Supertokens +- Supertokens json +- Rabbitmq +- Easyappointments +- Soketi +- Dozzle +- Windmill +- Coolify.json +- Keycloak +- Other DB options for freshrss +- Nextcloud MariaDB and MySQL versions +- Add peppermint +- Loggy +- Add UI for redis password and username +- Wireguard-easy template +- Https://github.com/coollabsio/coolify/issues/4186 +- Separate resources by type in projects view +- Improve s3 add view +- Caddy docker labels do not honor "strip prefix" option +- Test rename GitHub app +- Checkmate service and fix prowlar slogan (too long) +- Arrrrr +- Dep +- Docker dep +- Trigger.dev templates - wrong key length issue +- Trigger.dev template - missing ports and wrong env usage +- Trigger.dev template - fixed otel config +- Trigger.dev template - fixed otel config +- Trigger.dev template - fixed port config +- Bump all dependencies (#5216) +- Bump Coolify to 4.0.0-beta.398 +- Bump Coolify to 4.0.0-beta.400 +- *(migration)* Add SSL fields to database tables +- SSL Support for KeyDB + +### 🚜 Refactor + +- Code +- Env variable generator +- Service logs are now on one page +- Application status changed realtime +- Custom labels +- Clone project +- Compose file and install script +- Add SCHEDULER environment variable to StartSentinel.php +- Update edit-domain form in project service view +- Add Huly services to compose file +- Remove redundant heading in backup settings page +- Add isBuildServer method to Server model +- Update docker network creation in ApplicationDeploymentJob +- Update destination.blade.php to add group class for better styling +- Applicationdeploymentjob +- Improve code structure in ApplicationDeploymentJob.php +- Remove unnecessary debug statement in ApplicationDeploymentJob.php +- Remove unnecessary debug statements and improve code structure in RunRemoteProcess.php and ApplicationDeploymentJob.php +- Remove unnecessary logging statements from UpdateCoolify +- Update storage form inputs in show.blade.php +- Improve Docker Compose parsing for services +- Remove unnecessary port appending in updateCompose function +- Remove unnecessary form class in profile index.blade.php +- Update form layout in invite-link.blade.php +- Add log entry when starting new application deployment +- Improve Docker Compose parsing for services +- Update Docker Compose parsing for services +- Update slogan in shlink.yaml +- Improve display of deployment time in index.blade.php +- Remove commented out code for clearing Ray logs +- Update save_environment_variables method to use application's environment_variables instead of environment_variables_preview +- Append utm_source parameter to documentation URL +- Update save_environment_variables method to use application's environment_variables instead of environment_variables_preview +- Update deployment previews heading to "Deployments" +- Remove unused variables and improve code readability +- Initialize null properties in Github Change component +- Improve pre and post deployment command inputs +- Improve handling of Docker volumes in parseDockerComposeFile function +- Replaces duplications in code with a single function +- Update text color for stderr output in deployment show view +- Update text color for stderr output in deployment show view +- Remove debug code for saving environment variables +- Update Docker build commands for better performance and flexibility +- Update image sizes and add new logos to README.md +- Update README.md with new logos and fix styling +- Update shared.php to use correct key for retrieving sentinel version +- Update container name assignment in Application model +- Remove commented code for docker container removal +- Update Application model to include getDomainsByUuid method +- Update Project/Show component to sort environments by created_at +- Update profile index view to display 2FA QR code in a centered container +- Update dashboard.blade.php to use project's default environment for redirection +- Update gitCommitLink method to handle null values in source.html_url +- Update docker-compose generation to use multi-line literal block +- Update Service model's saveComposeConfigs method +- Add default environment to Service model's saveComposeConfigs method +- Improve handling of default environment in Service model's saveComposeConfigs method +- Remove commented out code in Service model's saveComposeConfigs method +- Update stack-form.blade.php to include wire:target attribute for submit button +- Update code to use str() instead of Str::of() for string manipulation +- Improve formatting and readability of source.blade.php +- Add is_build_time property to nixpacks_php_fallback_path and nixpacks_php_root_dir +- Simplify code for retrieving subscription in Stripe webhook +- Add force parameter to StartProxy handle method +- Comment out unused code for network cleanup +- Reset default labels when docker_compose_domains is modified +- Webhooks view +- Tags view +- Only get instanceSettings once from db +- Update Dockerfile to set CI environment variable to true +- Remove unnecessary code in AppServiceProvider.php +- Update Livewire configuration views +- Update Webhooks.php to use nullable type for webhook URLs +- Add lazy loading to tags in Livewire configuration view +- Update metrics.blade.php to improve alert message clarity +- Update version numbers to 4.0.0-beta.312 +- Update version numbers to 4.0.0-beta.314 +- Remove unused code and fix storage form layout +- Update Docker Compose build command to include --pull flag +- Update DockerCleanupJob to handle nullable usageBefore property +- Server status job and docker cleanup job +- Update DockerCleanupJob to use server settings for force cleanup +- Update DockerCleanupJob to use server settings for force cleanup +- Disable health check for Rust applications during deployment +- Update CleanupDatabase.php to adjust keep_days based on environment +- Adjust keep_days in CleanupDatabase.php based on environment +- Remove commented out code for cleaning up networks in CleanupDocker.php +- Update livewire polling interval in heading.blade.php +- Remove unused code for checking server status in Heading.php +- Simplify log drain installation in ServerCheckJob +- Remove unnecessary debug statement in ServerCheckJob +- Simplify log drain installation and stop log drain if necessary +- Cleanup unnecessary dynamic proxy configuration in Init command +- Remove unnecessary debug statement in ApplicationDeploymentJob +- Update timeout for graceful_shutdown_container in ApplicationDeploymentJob +- Remove unused code and optimize CheckForUpdatesJob +- Update ProxyTypes enum values to use TRAEFIK instead of TRAEFIK_V2 +- Update Traefik labels on init and cleanup unnecessary dynamic proxy configuration +- Update StandalonePostgresql database initialization and backup handling +- Update cron expressions and add helper text for scheduled tasks +- Update Server model getContainers method to use collect() for containers and containerReplicates +- Import ProxyTypes enum and use TRAEFIK instead of TRAEFIK_V2 +- Update event listeners in Show components +- Refresh application to get latest database changes +- Update RabbitMQ configuration to use environment variable for port +- Remove debug statement in parseDockerComposeFile function +- ParseServiceVolumes +- Update OpenApi command to generate documentation +- Remove unnecessary server status check in destination view +- Remove unnecessary admin user email and password in budibase.yaml +- Improve saving of custom internal name in Advanced.php +- Add conditional check for volumes in generate_compose_file() +- Improve storage mount forms in add.blade.php +- Load environment variables based on resource type in sortEnvironmentVariables() +- Remove unnecessary network cleanup in Init.php +- Remove unnecessary environment variable checks in parseDockerComposeFile() +- Add null check for docker_compose_raw in parseCompose() +- Update dockerComposeParser to use YAML data from $yaml instead of $compose +- Convert service variables to key-value pairs in parseDockerComposeFile function +- Update database service name from mariadb to mysql +- Remove unnecessary code in DatabaseBackupJob and BackupExecutions +- Update Docker Compose parsing function to convert service variables to key-value pairs +- Update Docker Compose parsing function to convert service variables to key-value pairs +- Remove unused server timezone seeder and related code +- Remove unused server timezone seeder and related code +- Remove unused PullCoolifyImageJob from schedule +- Update parse method in Advanced, All, ApplicationPreview, General, and ApplicationDeploymentJob classes +- Remove commented out code for getIptables() in Dashboard.php +- Update .env file path in install.sh script +- Update SELF_HOSTED environment variable in docker-compose.prod.yml +- Remove unnecessary code for creating coolify network in upgrade.sh +- Update environment variable handling in StartClickhouse.php and ApplicationDeploymentJob.php +- Improve handling of COOLIFY_URL in shared.php +- Update build_args property type in ApplicationDeploymentJob +- Update background color of sponsor section in README.md +- Update Docker Compose location handling in PublicGitRepository +- Upgrade process of Coolify +- Improve handling of server timezones in scheduled backups and tasks +- Improve handling of server timezones in scheduled backups and tasks +- Improve handling of server timezones in scheduled backups and tasks +- Update cleanup schedule to run daily at midnight +- Skip returning volume if driver type is cifs or nfs +- Improve environment variable handling in shared.php +- Improve handling of environment variable merging in upgrade script +- Remove unnecessary code in ExecuteContainerCommand.php +- Improve Docker network connection command in StartService.php +- Terminal / run command +- Add authorization check in ExecuteContainerCommand mount method +- Remove unnecessary code in Terminal.php +- Remove unnecessary code in Terminal.blade.php +- Update WebSocket connection initialization in terminal.blade.php +- Remove unnecessary console.log statements in terminal.blade.php +- Update Docker cleanup label in Heading.php and Navbar.php +- Remove commented out code in Navbar.php +- Remove CleanupSshKeysJob from schedule in Kernel.php +- Update getAJoke function to exclude offensive jokes +- Update getAJoke function to use HTTPS for API request +- Update CleanupHelperContainersJob to use more efficient Docker command +- Update PrivateKey model to improve code readability and maintainability +- Remove unnecessary code in PrivateKey model +- Update PrivateKey model to use ownedByCurrentTeam() scope for cleanupUnusedKeys() +- Update install.sh script to check if coolify-db volume exists before generating SSH key +- Update ServerSeeder and PopulateSshKeysDirectorySeeder +- Improve attribute sanitization in Server model +- Update confirmation button text for deletion actions +- Remove unnecessary code in shared.php file +- Update environment variables for services in compose files +- Update select.blade.php to improve trademarks policy display +- Update select.blade.php to improve trademarks policy display +- Fix typo in subscription URLs +- Add Postiz service to compose file (disabled for now) +- Update shared.php to include predefined ports for services +- Simplify SSH key synchronization logic +- Remove unused code in DatabaseBackupStatusJob and PopulateSshKeysDirectorySeeder +- Remove commented out code and improve environment variable handling in newParser function +- Improve label positioning in input and checkbox components +- Group and sort fields in StackForm by service name and password status +- Improve layout and add checkbox for task enablement in scheduled task form +- Update checkbox component to support full width option +- Update confirmation label in danger.blade.php template +- Fix typo in execute-container-command.blade.php +- Update OS_TYPE for Asahi Linux in install.sh script +- Add localhost as Server if it doesn't exist and not in cloud environment +- Add localhost as Server if it doesn't exist and not in cloud environment +- Update ProductionSeeder to fix issue with coolify_key assignment +- Improve modal confirmation titles and button labels +- Update install.sh script to remove redirection of upgrade output to /dev/null +- Fix modal input closeOutside prop in configuration.blade.php +- Add support for IPv6 addresses in sslip function +- Update environment variable name for uptime-kuma service +- Improve start proxy script to handle existing containers gracefully +- Update delete server confirmation modal buttons +- Remove unnecessary code +- Update search input placeholder in resource index view +- Remove deployment queue when deleting an application +- Improve SSH command generation in Terminal.php and terminal-server.js +- Fix indentation in modal-confirmation.blade.php +- Improve parsing of commands for sudo in parseCommandsByLineForSudo +- Improve popup component styling and button behavior +- Encode delimiter in SshMultiplexingHelper +- Remove inactivity timer in terminal-server.js +- Improve socket reconnection interval in terminal.js +- Remove unnecessary watch command from soketi service entrypoint +- Update Traefik configuration for improved security and logging +- Improve proxy configuration and code consistency in Server model +- Rename name method to sanitizedName in BaseModel for clarity +- Improve migration command and enhance application model with global scope and status checks +- Unify notification icon +- Remove unused Azure and Authentik service configurations from services.php +- Change email column types in instance_settings migration from string to text +- Change OauthSetting creation to updateOrCreate for better handling of existing records +- Rename `coolify.environment` to `coolify.environmentName` +- Rename parameter in DatabaseBackupJob for clarity +- Improve checkbox component accessibility and styling +- Remove unused tags method from ApplicationDeploymentJob +- Improve deployment status check in isAnyDeploymentInprogress function +- Extend HorizonServiceProvider from HorizonApplicationServiceProvider +- Streamline job status retrieval and clean up repository interface +- Enhance ApplicationDeploymentJob and HorizonServiceProvider for improved job handling +- Remove commented-out unsubscribe route from API +- Update redirect calls to use a consistent navigation method in deployment functions +- AppServiceProvider +- Github.php +- Improve data formatting and UI +- Comment out RootUserSeeder call in ProductionSeeder for clarity +- Streamline ProductionSeeder by removing debug logs and unnecessary checks, while ensuring essential seeding operations remain intact +- Remove debug echo statements from Init command to clean up output and improve readability +- *(workflows)* Replace jq with PHP script for version retrieval in workflows +- *(s3)* Improve S3 bucket endpoint formatting +- *(vite)* Improve environment variable handling in Vite configuration +- *(ui)* Simplify GitHub App registration UI and layout +- Simplify service start and restart workflows +- Use pull flag on docker compose up +- *(ui)* Simplify file storage modal confirmations +- *(notifications)* Improve transactional email settings handling +- *(scheduled-tasks)* Improve scheduled task creation and management +- *(billing)* Enhance Stripe subscription status handling and notifications +- *(ui)* Unhide log toggle in application settings +- *(nginx)* Streamline default Nginx configuration and improve error handling +- *(install)* Clean up install script and enhance Docker installation logic +- *(ScheduledTask)* Clean up code formatting and remove unused import +- *(app)* Remove unused MagicBar component and related code +- *(database)* Streamline SSL configuration handling across database types +- *(application)* Streamline healthcheck parsing from Dockerfile +- *(notifications)* Standardize getRecipients method signatures +- *(configuration)* Centralize configuration management in ConfigurationRepository +- *(docker)* Update image references to use centralized registry URL +- *(env)* Add centralized registry URL to environment configuration +- *(storage)* Simplify file storage iteration in Blade template +- *(models)* Add is_directory attribute to LocalFileVolume model +- *(modal)* Add ignoreWire attribute to modal-confirmation component +- *(invite-link)* Adjust layout for better responsiveness in form +- *(invite-link)* Enhance form layout for improved responsiveness +- *(network)* Enhance docker network creation with ipv6 fallback +- *(network)* Check for existing coolify network before creation +- *(database)* Enhance encryption process for local file volumes +- *(proxy)* Improve port availability checks with multiple methods +- *(database)* Update MongoDB SSL configuration for improved security +- *(database)* Enhance SSL configuration handling for various databases +- *(notifications)* Update Telegram button URL for staging environment +- *(models)* Remove unnecessary cloud check in isEnabled method +- *(database)* Streamline event listeners in Redis General component +- *(database)* Remove redundant database status display in MongoDB view +- *(database)* Update import statements for Auth in database components +- *(database)* Require PEM key file for SSL certificate regeneration +- *(database)* Change MySQL daemon command to MariaDB daemon +- *(nightly)* Update version numbers and enhance upgrade script +- *(versions)* Update version numbers for coolify and nightly +- *(email)* Validate team membership for email recipients +- *(shared)* Simplify deployment status check logic +- *(shared)* Add logging for running deployment jobs +- *(shared)* Enhance job status check to include 'reserved' +- *(email)* Improve error handling by passing context to handleError +- *(email)* Streamline email sending logic and improve configuration handling +- *(email)* Remove unnecessary whitespace in email sending logic +- *(email)* Allow custom email recipients in email sending logic +- *(email)* Enhance sender information formatting in email logic +- *(proxy)* Remove redundant stop call in restart method +- *(file-storage)* Add loadStorageOnServer method for improved error handling +- *(docker)* Parse and sanitize YAML compose file before encoding +- *(file-storage)* Improve layout and structure of input fields +- *(email)* Update label for test email recipient input +- *(database-backup)* Remove existing Docker container before backup upload +- *(database)* Improve decryption and deduplication of local file volumes +- *(database)* Remove debug output from volume update process +- *(dev)* Remove OpenAPI generation functionality +- *(migration)* Enhance local file volumes migration with logging +- *(CheckProxy)* Replace 'which' with 'command -v' for command availability checks ### 📚 Documentation - Contribution guide - -### ⚙️ Miscellaneous Tasks - -- Version++ - -## [2.3.3] - 2022-04-05 - -### 🐛 Bug Fixes - -- Add git lfs while deploying -- Try to update build status several times -- Update stucked builds -- Update stucked builds on startup -- Revert seed -- Lame fixing -- Remove asyncUntil - -### ⚙️ Miscellaneous Tasks - -- Version++ - -## [2.3.2] - 2022-04-04 - -### 🐛 Bug Fixes - -- *(php)* If .htaccess file found use apache -- Add default webhook domain for n8n - -### ⚙️ Miscellaneous Tasks - -- Version++ - -## [2.3.1] - 2022-04-04 - -### 🐛 Bug Fixes - -- Secrets build/runtime coudl be changed after save -- Default configuration - -### ⚙️ Miscellaneous Tasks - -- Version++ - -## [2.3.0] - 2022-04-04 - -### 🚀 Features - -- Initial python support -- Add loading on register button -- *(dev)* Allow windows users to use pnpm dev -- MeiliSearch service -- Add abilitry to paste env files - -### 🐛 Bug Fixes - -- Ignore coolify proxy error for now -- Python no wsgi -- If user not found -- Rename envs to secrets -- Infinite loop on www domains -- No need to paste clear text env for previews -- Build log fix attempt #1 -- Small UI fix on logs -- Lets await! -- Async progress -- Remove console.log -- Build log -- UI -- Gitlab & Github urls - -### 💼 Other - -- Improvements - -### ⚙️ Miscellaneous Tasks - -- Version++ -- Version++ -- Lock file + fix packages - -## [2.2.7] - 2022-04-01 - -### 🐛 Bug Fixes - -- Haproxy errors -- Build variables -- Use NodeJS for sveltekit for now - -## [2.2.6] - 2022-03-31 - -### 🐛 Bug Fixes - -- Add PROTO headers - -## [2.2.5] - 2022-03-31 - -### 🐛 Bug Fixes - -- Registration enabled/disabled - -### ⚙️ Miscellaneous Tasks - -- Version++ - -## [2.2.4] - 2022-03-31 - -### 🐛 Bug Fixes - -- Gitlab repo url -- No need to dashify anymore - -### ⚙️ Miscellaneous Tasks - -- Version++ - -## [2.2.3] - 2022-03-31 - -### 🐛 Bug Fixes - -- List ghost services -- Reload window on settings saved -- Persistent storage on webhooks -- Add license -- Space in repo names - -### ⚙️ Miscellaneous Tasks - -- Version++ -- Version++ -- Version++ -- Fixed typo on New Git Source view - -## [2.2.0] - 2022-03-27 - -### 🚀 Features - -- Add n8n.io service -- Add update kuma service -- Ghost service - -### 🐛 Bug Fixes - -- Ghost logo size -- Ghost icon, remove console.log - -### 💼 Other - -- Colors on svelte-select - -### ⚙️ Miscellaneous Tasks - -- Version ++ - -## [2.1.1] - 2022-03-25 - -### 🐛 Bug Fixes - -- Cleanup only 2 hours+ old images - -### ⚙️ Miscellaneous Tasks - -- Version++ - -## [2.1.0] - 2022-03-23 - -### 🚀 Features - -- Use compose instead of normal docker cmd -- Be able to redeploy PRs - -### 🐛 Bug Fixes - -- Skip ssl cert in case of error -- Volumes - -### ⚙️ Miscellaneous Tasks - -- Version++ - -## [2.0.31] - 2022-03-20 - -### 🚀 Features - -- Add PHP modules - -### 🐛 Bug Fixes - -- Cleanup old builds -- Only cleanup same app -- Add nginx + htaccess files - -### ⚙️ Miscellaneous Tasks - -- Version++ - -## [2.0.30] - 2022-03-19 - -### 🐛 Bug Fixes - -- No cookie found -- Missing session data -- No error if GitSource is missing -- No webhook secret found? -- Basedir for dockerfiles -- Better queue system + more support on monorepos -- Remove build logs in case of app removed - -### ⚙️ Miscellaneous Tasks - -- Version++ - -## [2.0.29] - 2022-03-11 - -### 🚀 Features - -- Webhooks inititate all applications with the correct branch -- Check ssl for new apps/services first -- Autodeploy pause -- Install pnpm into docker image if pnpm lock file is used - -### 🐛 Bug Fixes - -- Personal Gitlab repos -- Autodeploy true by default for GH repos - -### ⚙️ Miscellaneous Tasks - -- Version++ - -## [2.0.28] - 2022-03-04 - -### 🚀 Features - -- Service secrets - -### 🐛 Bug Fixes - -- Do not error if proxy is not running - -### ⚙️ Miscellaneous Tasks - -- Version++ - -## [2.0.27] - 2022-03-02 - -### 🚀 Features - -- Send version with update request - -### 🐛 Bug Fixes - -- Check when a container is running -- Reload haproxy if new cert is added -- Cleanup coolify images -- Application state in UI - -### ⚙️ Miscellaneous Tasks - -- Version++ - -## [2.0.26] - 2022-03-02 - -### 🐛 Bug Fixes - -- Update process - -## [2.0.25] - 2022-03-02 - -### 🚀 Features - -- Languagetool service - -### 🐛 Bug Fixes - -- Reload proxy on ssl cert -- Volume name - -### ⚙️ Miscellaneous Tasks - -- Version++ - -## [2.0.24] - 2022-03-02 - -### 🐛 Bug Fixes - -- Better proxy check -- Ssl + sslrenew -- Null proxyhash on restart -- Reconfigure proxy on restart -- Update process - -## [2.0.23] - 2022-02-28 - -### 🐛 Bug Fixes - -- Be sure .env exists -- Missing fqdn for services -- Default npm command -- Add coolify-image label for build images -- Cleanup old images, > 3 days - -### 💼 Other - -- Colorful states -- Application start - -### ⚙️ Miscellaneous Tasks - -- Version++ - -## [2.0.22] - 2022-02-27 - -### 🐛 Bug Fixes - -- Coolify image pulls -- Remove wrong/stuck proxy configurations -- Always use a buildpack -- Add icons for eleventy + astro -- Fix proxy every 10 secs -- Do not remove coolify proxy -- Update version - -### 💼 Other - -- Remote docker engine - -### ⚙️ Miscellaneous Tasks - -- Version++ - -## [2.0.21] - 2022-02-24 - -### 🚀 Features - -- Random subdomain for demo -- Random domain for services -- Astro buildpack -- 11ty buildpack -- Registration page - -### 🐛 Bug Fixes - -- Http for demo, oops -- Docker scanner -- Improvement on image pulls - -### ⚙️ Miscellaneous Tasks - -- Version++ - -## [2.0.20] - 2022-02-23 - -### 🐛 Bug Fixes - -- Revert default network - -### 💼 Other - -- Dns check - -### ⚙️ Miscellaneous Tasks - -- Version++ - -## [2.0.19] - 2022-02-23 - -### 🐛 Bug Fixes - -- Random network name for demo -- Settings fqdn grr - -## [2.0.18] - 2022-02-22 - -### 🚀 Features - -- Ports range - -### 🐛 Bug Fixes - -- Email is lowercased in login -- Lowercase email everywhere -- Use normal docker-compose in dev - -### 💼 Other - -- Make copy/password visible - -### ⚙️ Miscellaneous Tasks - -- Version++ - -## [2.0.17] - 2022-02-21 - -### 🐛 Bug Fixes - -- Move tokens from session to cookie/store - -### ⚙️ Miscellaneous Tasks - -- Version++ - -## [2.0.14] - 2022-02-18 - -### 🚀 Features - -- Basic password reset form -- Scan for lock files and set right commands -- Public port range (WIP) - -### 🐛 Bug Fixes - -- SSL app off -- Local docker host -- Typo -- Lets encrypt -- Remove SSL with stop -- SSL off for services -- Grr -- Running state css -- Minor fixes -- Remove force SSL when doing let's encrypt request -- GhToken in session now -- Random port for certbot -- Follow icon -- Plausible volume fixed -- Database connection strings -- Gitlab webhooks fixed -- If DNS not found, do not redirect -- Github token - -### ⚙️ Miscellaneous Tasks - -- Version++ -- Version ++ - -## [2.0.13] - 2022-02-17 - -### 🐛 Bug Fixes - -- Login issues - -## [2.0.11] - 2022-02-15 - -### 🚀 Features - -- Follow logs -- Generate www & non-www SSL certs - -### 🐛 Bug Fixes - -- Window error in SSR -- GitHub sync PR's -- Load more button -- Small fixes -- Typo -- Error with follow logs -- IsDomainConfigured -- TransactionIds -- Coolify image cleanup -- Cleanup every 10 mins -- Cleanup images -- Add no user redis to uri -- Secure cookie disabled by default -- Buggy svelte-kit-cookie-session - -### 💼 Other - -- Only allow cleanup in production - -### ⚙️ Miscellaneous Tasks - -- Version++ -- Version++ - -## [2.0.10] - 2022-02-15 - -### 🐛 Bug Fixes - -- Typo -- Error handling -- Stopping service without proxy -- Coolify proxy start - -### ⚙️ Miscellaneous Tasks - -- Version++ - -## [2.0.8] - 2022-02-14 - -### 🐛 Bug Fixes - -- Validate secrets -- Truncate git clone errors -- Branch used does not throw error - -## [2.0.7] - 2022-02-13 - -### 🚀 Features - -- Www <-> non-www redirection for apps -- Www <-> non-www redirection - -### 🐛 Bug Fixes - -- Package.json -- Build secrets should be visible in runtime -- New secret should have default values - -## [2.0.5] - 2022-02-11 - -### 🚀 Features - -- VaultWarden service - -### 🐛 Bug Fixes - -- PreventDefault on a button, thats all -- Haproxy check should not throw error -- Delete all build files -- Cleanup images -- More error handling in proxy configuration + cleanups -- Local static assets -- Check sentry -- Typo - -### ⚙️ Miscellaneous Tasks - -- Version -- Version - -## [2.0.4] - 2022-02-11 - -### 🚀 Features - -- Use tags in update -- New update process (#115) - -### 🐛 Bug Fixes - -- Docker Engine bug related to live-restore and IPs -- Version - -## [2.0.3] - 2022-02-10 - -### 🐛 Bug Fixes - -- Capture non-error as error -- Only delete id.rsa in case of it exists -- Status is not available yet +- How to add new services +- Update +- Update +- Update Plunk documentation link in compose/plunk.yaml +- Update link to deploy api docs +- Add TECH_STACK.md (#4883) +- *(services)* Reword nitropage url and slogan +- *(readme)* Add Convex to special sponsors section +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- *(CONTRIBUTING)* Add note about Laravel Horizon accessibility +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog +- Update changelog + +### 🎨 Styling + +- Linting + +### 🧪 Testing + +- Native binary target +- Dockerfile +- Remove prisma +- More tests +- Setup database for upcoming tests ### ⚙️ Miscellaneous Tasks - Version bump +- Version +- Version +- Version++ +- Version++ +- Version++ +- Version++ +- Version ++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version ++ +- Version++ +- Version++ +- Version++ +- Fixed typo on New Git Source view +- Version++ +- Version++ +- Version++ +- Version++ +- Lock file + fix packages +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Update packages +- Version++ +- Update build scripts +- Update build packages +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Add .pnpm-store in .gitignore +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Minor changes +- Minor changes +- Minor changes +- Whoops +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Update staging release +- Version++ +- Version++ +- Add jda icon for lavalink service +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Version++ +- Update version to 4.0.0-beta.275 +- Update DNS server validation helper text +- Dark mode should be the default +- Improve menu item styling and spacing in service configuration and index views +- Improve menu item styling and spacing in service configuration and index views +- Improve menu item styling and spacing in project index and show views +- Remove docker compose versions +- Add Listmonk service template and logo +- Refactor GetContainersStatus.php for improved readability and maintainability +- Refactor ApplicationDeploymentJob.php for improved readability and maintainability +- Add metrics and logs directories to installation script +- Update sentinel version to 0.0.2 in versions.json +- Update permissions on metrics and logs directories +- Comment out server sentinel check in ServerStatusJob +- Update version numbers to 4.0.0-beta.278 +- Update hover behavior and cursor style in scheduled task executions view +- Refactor scheduled task view to improve code readability and maintainability +- Skip scheduled tasks if application or service is not running +- Remove debug logging statements in Kernel.php +- Handle invalid cron strings in Kernel.php +- Refactor Service.php to handle missing admin user in extraFields() method +- Update twenty CRM template with environment variables and dependencies +- Refactor applications.php to remove unused imports and improve code readability +- Refactor deployment index.blade.php for improved readability and rollback handling +- Refactor GitHub app selection UI in project creation form +- Update ServerLimitCheckJob.php to handle missing serverLimit value +- Remove unnecessary code for saving commit message +- Update DOCKER_VERSION to 26.0 in install.sh script +- Update Docker and Docker Compose versions in Dockerfiles +- Update version numbers to 4.0.0-beta.279 +- Limit commit message length to 50 characters in ApplicationDeploymentJob +- Update version to 4.0.0-beta.283 +- Change pre and post deployment command length in applications table +- Refactor container name logic in GetContainersStatus.php and ForcePasswordReset.php +- Remove unnecessary content from Docker Compose file +- Update Sentry release version to 4.0.0-beta.287 +- Add Thompson Edolo as a sponsor +- Add null checks for team in Stripe webhook +- Update Sentry release version to 4.0.0-beta.288 +- Update for version 289 +- Fix formatting issue in deployment index.blade.php file +- Remove unnecessary wire:navigate attribute in breadcrumbs.blade.php +- Rename docker dirs +- Update laravel/socialite to version v5.14.0 and livewire/livewire to version 3.4.9 +- Update modal styles for better user experience +- Update deployment index.blade.php script for better performance +- Update version numbers to 4.0.0-beta.290 +- Update version numbers to 4.0.0-beta.291 +- Update version numbers to 4.0.0-beta.292 +- Update version numbers to 4.0.0-beta.293 +- Add upgrade guide link to upgrade.blade.php +- Improve upgrade.blade.php with clearer instructions and formatting +- Update version numbers to 4.0.0-beta.294 +- Add Lightspeed.run as a sponsor +- Update Dockerfile to install vim +- Update Dockerfile with latest versions of Docker, Docker Compose, Docker Buildx, Pack, and Nixpacks +- Update version numbers to 4.0.0-beta.295 +- Update supported OS list with almalinux +- Update install.sh to support PopOS +- Update install.sh script to version 1.3.2 and handle Linux Mint as Ubuntu +- Update page title in resource index view +- Update logo file path in logto.yaml +- Update logo file path in logto.yaml +- Remove commented out code for docker container removal +- Add isAnyDeploymentInprogress function to check if any deployments are in progress +- Add ApplicationDeploymentJob and pint.json +- Update version numbers to 4.0.0-beta.298 +- Switch to database sessions from redis +- Update dependencies and remove unused code +- Update tailwindcss and vue versions in package.json +- Update service template URL in constants.php +- Update sentinel version to 0.0.8 +- Update chart styling and loading text +- Update sentinel version to 0.0.9 +- Update Spanish translation for failed authentication messages +- Add portuguese traslation +- Add Turkish translations +- Add Vietnamese translate +- Add Treive logo to donations section +- Update README.md with latest release version badge +- Update latest release version badge in README.md +- Update version to 4.0.0-beta.299 +- Move server delete component to the bottom of the page +- Update version to 4.0.0-beta.301 +- Update version to 4.0.0-beta.302 +- Update version to 4.0.0-beta.303 +- Update version to 4.0.0-beta.305 +- Update version to 4.0.0-beta.306 +- Add log1x/laravel-webfonts package +- Update version to 4.0.0-beta.307 +- Refactor ServerStatusJob constructor formatting +- Update Monaco Editor for Docker Compose and Proxy Configuration +- More details +- Refactor shared.php helper functions +- Update Plausible docker compose template to Plausible 2.1.0 +- Update Plausible docker compose template to Plausible 2.1.0 +- Update livewire/livewire dependency to version 3.4.9 +- Refactor checkIfDomainIsAlreadyUsed function +- Update storage.blade.php view for livewire project service +- Update version to 4.0.0-beta.310 +- Update composer dependencies +- Add new logo for Latitude +- Bump version to 4.0.0-beta.311 +- Update version to 4.0.0-beta.315 +- Update version to 4.0.0-beta.316 +- Update bug report template +- Update repository form with simplified URL input field +- Update width of container in general.blade.php +- Update checkbox labels in general.blade.php +- Update general page of apps +- Handle JSON parsing errors in format_docker_command_output_to_json +- Update Traefik image version to v2.11 +- Update version to 4.0.0-beta.317 +- Update version to 4.0.0-beta.318 +- Update helper message with link to documentation +- Disable health check by default +- Remove commented out code for sending internal notification +- Update APP_BASE_URL to use SERVICE_FQDN_PLANE +- Update resource-limits.blade.php with improved input field helpers +- Update version numbers to 4.0.0-beta.319 +- Remove commented out code for docker image pruning +- Collect/create/update volumes in parseDockerComposeFile function +- Update version to 4.0.0-beta.320 +- Add pull_request image builds to GH actions +- Add comment explaining the purpose of disconnecting the network in cleanup_unused_network_from_coolify_proxy() +- Update formbricks template +- Update registration view to display a notice for first user that it will be an admin +- Update server form to use password input for IP Address/Domain field +- Update navbar to include service status check +- Update navbar and configuration to improve service status check functionality +- Update workflows to include PR build and merge manifest steps +- Update UpdateCoolifyJob timeout to 10 minutes +- Update UpdateCoolifyJob to dispatch CheckForUpdatesJob synchronously +- Update version to 4.0.0-beta.321 +- Update version to 4.0.0-beta.322 +- Update version to 4.0.0-beta.323 +- Update version to 4.0.0-beta.324 +- New compose parser with tests +- Update version to 1.3.4 in install.sh and 1.0.6 in upgrade.sh +- Update memory limit to 64MB in horizon configuration +- Update php packages +- Update axios npm dependency to version 1.7.5 +- Update Coolify version to 4.0.0-beta.324 and fix file paths in upgrade script +- Update Coolify version to 4.0.0-beta.324 +- Update Coolify version to 4.0.0-beta.325 +- Update Coolify version to 4.0.0-beta.326 +- Add cd command to change directory before removing .env file +- Update Coolify version to 4.0.0-beta.327 +- Update Coolify version to 4.0.0-beta.328 +- Update sponsor links in README.md +- Update version.json to versions.json in GitHub workflow +- Cleanup stucked resources and scheduled backups +- Update GitHub workflow to use versions.json instead of version.json +- Update GitHub workflow to use versions.json instead of version.json +- Update GitHub workflow to use versions.json instead of version.json +- Update GitHub workflow to use jq container for version extraction +- Update GitHub workflow to use jq container for version extraction +- Update UI for displaying no executions found in scheduled task list +- Update UI for displaying deployment status in deployment list +- Update UI for displaying deployment status in deployment list +- Ignore unnecessary files in production build workflow +- Update server form layout and settings +- Update Dockerfile with latest versions of PACK and NIXPACKS +- Update coolify-helper.yml to get version from versions.json +- Disable Ray by default +- Enable Ray by default and update Dockerfile with latest versions of PACK and NIXPACKS +- Update Ray configuration and Dockerfile +- Add middleware for updating environment variables by UUID in `api.php` routes +- Expose port 3000 in browserless.yaml template +- Update Ray configuration and Dockerfile +- Update coolify version to 4.0.0-beta.331 +- Update versions.json and sentry.php to 4.0.0-beta.332 +- Update version to 4.0.0-beta.332 +- Update DATABASE_URL in plunk.yaml to use plunk database +- Add coolify.managed=true label to Docker image builds +- Update docker image pruning command to exclude managed images +- Update docker cleanup schedule to run daily at midnight +- Update versions.json to version 1.0.1 +- Update coolify-helper.yml to include "next" branch in push trigger +- Set timeout for ServerCheckJob to 60 seconds +- Update appwrite.yaml to include OpenSSL key variable assignment +- Update version numbers to 4.0.0-beta.333 +- Copy .env file to .env-{DATE} if it exists +- Update .env file with new values +- Update server check job middleware to use server ID instead of UUID +- Add reminder to backup .env file before running install script again +- Copy .env file to backup location during installation script +- Add reminder to backup .env file during installation script +- Update permissions in pr-build.yml and version numbers +- Add minio/mc command to Dockerfile +- Remove itsgoingd/clockwork from require-dev in composer.json +- Update 'key' value of gitlab in Service.php to use environment variable +- Update release version to 4.0.0-beta.335 +- Update constants.ssh.mux_enabled in remoteProcess.php +- Update listeners and proxy settings in server form and new server components +- Remove unnecessary null check for proxy_type in generate_default_proxy_configuration +- Remove unnecessary SSH command execution time logging +- Update release version to 4.0.0-beta.336 +- Update coolify environment variable assignment with double quotes +- Update shared.php to fix issues with source and network variables +- Update terminal styling for better readability +- Update button text for container connection form +- Update Dockerfile and workflow for Coolify Realtime (v4) +- Remove unused entrypoint script and update volume mapping +- Update .env file and docker-compose configuration +- Update APP_NAME environment variable in docker-compose.prod.yml +- Update WebSocket URL in terminal.blade.php +- Update Dockerfile and workflow for Coolify Realtime (v4) +- Update Dockerfile and workflow for Coolify Realtime (v4) +- Update Dockerfile and workflow for Coolify Realtime (v4) +- Rename Command Center to Terminal in code and views +- Update branch restriction for push event in coolify-helper.yml +- Update terminal button text and layout in application heading view +- Refactor terminal component and select form layout +- Update coolify nightly version to 4.0.0-beta.335 +- Update helper version to 1.0.1 +- Fix syntax error in versions.json +- Update version numbers to 4.0.0-beta.337 +- Update Coolify installer and scripts to include a function for fetching programming jokes +- Update docker network connection command in ApplicationDeploymentJob.php +- Add validation to prevent selecting 'default' server or container in RunCommand.php +- Update versions.json to reflect latest version of realtime container +- Update soketi image to version 1.0.1 +- Nightly - Update soketi image to version 1.0.1 and versions.json to reflect latest version of realtime container +- Update version numbers to 4.0.0-beta.339 +- Update version numbers to 4.0.0-beta.340 +- Update version numbers to 4.0.0-beta.341 +- Update version numbers to 4.0.0-beta.342 +- Update remove-labels-and-assignees-on-close.yml +- Add SSH key for localhost in ProductionSeeder +- Update SSH key generation in install.sh script +- Update ProductionSeeder to call OauthSettingSeeder and PopulateSshKeysDirectorySeeder +- Update install.sh to support Asahi Linux +- Update install.sh version to 1.6 +- Remove unused middleware and uniqueId method in DockerCleanupJob +- Refactor DockerCleanupJob to remove unused middleware and uniqueId method +- Remove unused migration file for populating SSH keys and clearing mux directory +- Add modified files to the commit +- Refactor pre-commit hook to improve performance and readability +- Update CONTRIBUTING.md with troubleshooting note about database migrations +- Refactor pre-commit hook to improve performance and readability +- Update cleanup command to use Redis instead of queue +- Update Docker commands to start proxy +- Update version numbers to 4.0.0-beta.343 +- Update version numbers to 4.0.0-beta.344 +- Update version numbers to 4.0.0-beta.345 +- Update version numbers to 4.0.0-beta.346 +- Add autocomplete attribute to input fields +- Refactor API Tokens component to use isApiEnabled flag +- Update versions.json file +- Remove unused .env.development.example file +- Update API Tokens view to include link to Settings menu +- Update web.php to cast server port as integer +- Update backup deletion labels to use language files +- Update database startup heading title +- Update database startup heading title +- Custom vite envs +- Update version numbers to 4.0.0-beta.348 +- Refactor code to improve SSH key handling and storage +- Update Mailpit logo to use SVG format +- Fix docs link in running state +- Update Coolify Realtime workflow to only trigger on the main branch +- Refactor instanceSettings() function to improve code readability +- Update Coolify Realtime image to version 1.0.2 +- Remove unnecessary code in DatabaseBackupJob.php +- Add "Not Usable" indicator for storage items +- Refactor instanceSettings() function and improve code readability +- Update version numbers to 4.0.0-beta.349 and 4.0.0-beta.350 +- Update version numbers to 4.0.0-beta.350 in configuration files +- Update command signature and description for cleanup application deployment queue +- Add missing import for Attribute class in ApplicationDeploymentQueue model +- Update modal input in server form to prevent closing on outside click +- Remove unnecessary command from SshMultiplexingHelper +- Remove commented out code for uploading to S3 in DatabaseBackupJob +- Update soketi service image to version 1.0.3 +- Update version to 4.0.0-beta.352 +- Refactor DatabaseBackupJob to handle missing team +- Update version to 4.0.0-beta.353 +- Update service application view +- Update version to 4.0.0-beta.354 +- Remove debug statement in Service model +- Remove commented code in Server model +- Fix application deployment queue filter logic +- Refactor modal-confirmation component +- Update it-tools service template and port configuration +- Update homarr service template and remove unnecessary code +- Update homarr service template and remove unnecessary code +- Update version to 4.0.0-beta.355 +- Update version to 4.0.0-beta.356 +- Remove commented code for shared variable type validation +- Update MariaDB image to version 11 and fix service environment variable orders +- Update anythingllm.yaml volumes configuration +- Update proxy configuration paths for Caddy and Nginx in dev +- Update password form submission in modal-confirmation component +- Update project query to order by name in uppercase +- Update project query to order by name in lowercase +- Update select.blade.php with improved search functionality +- Add Nitropage service template and logo +- Bump coolify-helper version to 1.0.2 +- Refactor loadServices2 method and remove unused code +- Update version to 4.0.0-beta.357 +- Update service names and volumes in windmill.yaml +- Update version to 4.0.0-beta.358 +- Ignore .ignition.json files in Docker and Git +- Add mattermost logo as svg +- Add mattermost svg to compose +- Update version to 4.0.0-beta.357 +- Fix form submission and keydown event handling in modal-confirmation.blade.php +- Update version numbers to 4.0.0-beta.359 in configuration files +- Disable adding default environment variables in shared.php +- Update laravel/horizon dependency to version 5.29.1 +- Update service extra fields to use dynamic keys +- Update livewire/livewire dependency to version 3.4.9 +- Add transmission template desc +- Update transmission docs link +- Update version numbers to 4.0.0-beta.360 in configuration files +- Update AWS environment variable names in unsend.yaml +- Update AWS environment variable names in unsend.yaml +- Update livewire/livewire dependency to version 3.4.9 +- Update version to 4.0.0-beta.361 +- Update Docker build and push actions to v6 +- Update Docker build and push actions to v6 +- Update Docker build and push actions to v6 +- Sync coolify-helper to dockerhub as well +- Push realtime to dockerhub +- Sync coolify-realtime to dockerhub +- Rename workflows +- Rename development to staging build +- Sync coolify-testing-host to dockerhbu +- Sync coolify prod image to dockerhub as well +- Update Docker version to 26.0 +- Update project resource index page +- Update project service configuration view +- Edit www helper +- Update dep +- Regenerate openapi spec +- Composer dep bump +- Dep bump +- Upgrade cloudflared and minio +- Remove comments and improve DB column naming +- Remove unused seeder +- Remove unused waitlist stuff +- Remove wired.php (not used anymore) +- Remove unused resale license job +- Remove commented out internal notification +- Remove more waitlist stuff +- Remove commented out notification +- Remove more waitlist stuff +- Remove unused code +- Fix typo +- Remove comment out code +- Some reordering +- Remove resale license reference +- Remove functions from shared.php +- Public settings for email notification +- Remove waitlist redirect +- Remove log +- Use new notification trait +- Remove unused route +- Remove unused email component +- Comment status changes as it is disabled for now +- Bump dep +- Reorder navbar +- Rename topicID to threadId like in the telegram API response +- Update PHP configuration to set memory limit using environment variable +- Regenerate API spec, removing notification fields +- Remove ray debugging +- Version ++ +- Improve Penpot healthchecks +- Switch up readonly lables to make more sense +- Remove unused computed fields +- Use the new job dispatch +- Disable volume data cloning for now +- Improve code +- Lowcoder service naming +- Use new functions +- Improve error styling +- Css +- More css as it still looks like shit +- Final css touches +- Ajust time to 50s (tests done) +- Remove debug log, finally found it +- Remove more logging +- Remove limit on commit message +- Remove dayjs +- Remove unused code and fix import +- *(dep)* Bump nixpacks version +- *(dep)* Version++ +- *(dep)* Bump helper version to 1.0.5 +- *(docker)* Add blank line for readability in Dockerfile +- *(versions)* Update coolify versions to v4.0.0-beta.388 +- *(versions)* Update coolify versions to v4.0.0-beta.389 and add helper version retrieval script +- *(versions)* Update coolify versions to v4.0.0-beta.389 +- *(core)* EnvironmentVariable Model now extends BaseModel to remove duplicated code +- *(versions)* Update coolify versions to v4.0.0-beta.3909 +- *(version)* Bump Coolify version to 4.0.0-beta.391 +- *(config)* Increase default PHP memory limit to 256M +- Add openapi response +- *(workflows)* Make naming more clear and remove unused code +- Bump Coolify version to 4.0.0-beta.392/393 +- *(ci)* Update changelog generation workflow to target 'next' branch +- *(ci)* Update changelog generation workflow to target main branch +- Rollback Coolify version to 4.0.0-beta.392 +- Bump Coolify version to 4.0.0-beta.393 +- Bump Coolify version to 4.0.0-beta.394 +- Bump Coolify version to 4.0.0-beta.395 +- Bump Coolify version to 4.0.0-beta.396 +- *(services)* Update zipline to use new Database env var. (#5210) +- *(service)* Upgrade authentik service +- *(service)* Remove unused env from zipline +- Bump helper and realtime version +- *(migration)* Remove unused columns +- *(ssl)* Improve code in ssl helper +- *(migration)* Ssl cert and key should not be nullable +- *(ssl)* Rename CA cert to `coolify-ca.crt` because of conflicts +- Rename ca crt folder to ssl +- *(ui)* Improve valid until handling +- Improve code quality suggested by code rabbit +- *(supabase)* Update Supabase service template and Postgres image version +- *(versions)* Update version numbers for coolify and nightly +- *(versions)* Update version numbers for coolify and nightly +- *(service)* Update minecraft service ENVs +- *(service)* Add more vars to infisical.yaml (#5418) +- *(service)* Add google variables to plausible.yaml (#5429) +- *(service)* Update authentik.yaml versions (#5373) +- *(core)* Remove redocs +- *(versions)* Bump version to 403 (#5520) +- *(versions)* Update coolify version numbers to 4.0.0-beta.403 and 4.0.0-beta.404 +- *(service)* Remove unused code in Bugsink service +- *(versions)* Update version to 404 +- *(versions)* Bump version to 404 +- *(versions)* Bump version to 406 +- *(versions)* Bump version to 407 -## [2.0.2] - 2022-02-10 +### ◀️ Revert -### 🐛 Bug Fixes - -- Secrets join -- ENV variables set differently +- Show usage everytime +- Revert: revert +- Wip +- Variable parsing +- Hc return code check +- Instancesettings +- Pull policy +- Advanced dropdown +- Databasebackup +- Remove Cloudflare async tag attributes +- Encrypting mount and fs_path diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index dba3676cf..1ba4d1876 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -136,6 +136,7 @@ After installing Docker (or Orbstack) and Spin, verify the installation: - Password: `password` 2. Additional development tools: + | Tool | URL | Note | |------|-----|------| | Laravel Horizon (scheduler) | `http://localhost:8000/horizon` | Only accessible when logged in as root user | @@ -237,9 +238,9 @@ After completing these steps, you'll have a fresh development setup. ### 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) +[Adding a New Service](https://coolify.io/docs/get-started/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) +[Contributing to the Coolify Documentation](https://github.com/coollabsio/documentation-coolify/blob/main/readme.md) diff --git a/app/Actions/Database/StartDragonfly.php b/app/Actions/Database/StartDragonfly.php index 4f9f45b7c..38ad99d2e 100644 --- a/app/Actions/Database/StartDragonfly.php +++ b/app/Actions/Database/StartDragonfly.php @@ -2,6 +2,8 @@ namespace App\Actions\Database; +use App\Helpers\SslHelper; +use App\Models\SslCertificate; use App\Models\StandaloneDragonfly; use Lorisleiva\Actions\Concerns\AsAction; use Symfony\Component\Yaml\Yaml; @@ -16,24 +18,81 @@ class StartDragonfly public string $configuration_dir; + private ?SslCertificate $ssl_certificate = null; + public function handle(StandaloneDragonfly $database) { $this->database = $database; - $startCommand = "dragonfly --requirepass {$this->database->dragonfly_password}"; - $container_name = $this->database->uuid; $this->configuration_dir = database_configuration_dir().'/'.$container_name; $this->commands = [ "echo 'Starting database.'", + "echo 'Creating directories.'", "mkdir -p $this->configuration_dir", + "echo 'Directories created successfully.'", ]; + if (! $this->database->enable_ssl) { + $this->commands[] = "rm -rf $this->configuration_dir/ssl"; + $this->database->sslCertificates()->delete(); + $this->database->fileStorages() + ->where('resource_type', $this->database->getMorphClass()) + ->where('resource_id', $this->database->id) + ->get() + ->filter(function ($storage) { + return in_array($storage->mount_path, [ + '/etc/dragonfly/certs/server.crt', + '/etc/dragonfly/certs/server.key', + ]); + }) + ->each(function ($storage) { + $storage->delete(); + }); + } else { + $this->commands[] = "echo 'Setting up SSL for this database.'"; + $this->commands[] = "mkdir -p $this->configuration_dir/ssl"; + + $server = $this->database->destination->server; + $caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first(); + + if (! $caCert) { + $server->generateCaCertificate(); + $caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first(); + } + + if (! $caCert) { + $this->dispatch('error', 'No CA certificate found for this database. Please generate a CA certificate for this server in the server/advanced page.'); + + return; + } + + $this->ssl_certificate = $this->database->sslCertificates()->first(); + + if (! $this->ssl_certificate) { + $this->commands[] = "echo 'No SSL certificate found, generating new SSL certificate for this database.'"; + $this->ssl_certificate = SslHelper::generateSslCertificate( + commonName: $this->database->uuid, + resourceType: $this->database->getMorphClass(), + resourceId: $this->database->id, + serverId: $server->id, + caCert: $caCert->ssl_certificate, + caKey: $caCert->ssl_private_key, + configurationDir: $this->configuration_dir, + mountPath: '/etc/dragonfly/certs', + ); + } + } + + $container_name = $this->database->uuid; + $this->configuration_dir = database_configuration_dir().'/'.$container_name; + $persistent_storages = $this->generate_local_persistent_volumes(); $persistent_file_volumes = $this->database->fileStorages()->get(); $volume_names = $this->generate_local_persistent_volumes_only_volume_names(); $environment_variables = $this->generate_environment_variables(); + $startCommand = $this->buildStartCommand(); $docker_compose = [ 'services' => [ @@ -70,27 +129,55 @@ class StartDragonfly ], ], ]; + 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'] = generate_fluentd_configuration(); } + if (count($this->database->ports_mappings_array) > 0) { $docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array; } + + $docker_compose['services'][$container_name]['volumes'] ??= []; + if (count($persistent_storages) > 0) { - $docker_compose['services'][$container_name]['volumes'] = $persistent_storages; + $docker_compose['services'][$container_name]['volumes'] = array_merge( + $docker_compose['services'][$container_name]['volumes'], + $persistent_storages + ); } + if (count($persistent_file_volumes) > 0) { - $docker_compose['services'][$container_name]['volumes'] = $persistent_file_volumes->map(function ($item) { - return "$item->fs_path:$item->mount_path"; - })->toArray(); + $docker_compose['services'][$container_name]['volumes'] = array_merge( + $docker_compose['services'][$container_name]['volumes'], + $persistent_file_volumes->map(function ($item) { + return "$item->fs_path:$item->mount_path"; + })->toArray() + ); } + if (count($volume_names) > 0) { $docker_compose['volumes'] = $volume_names; } + if ($this->database->enable_ssl) { + $docker_compose['services'][$container_name]['volumes'] = array_merge( + $docker_compose['services'][$container_name]['volumes'] ?? [], + [ + [ + 'type' => 'bind', + 'source' => '/data/coolify/ssl/coolify-ca.crt', + 'target' => '/etc/dragonfly/certs/coolify-ca.crt', + 'read_only' => true, + ], + ] + ); + } + // Add custom docker run options $docker_run_options = convertDockerRunToCompose($this->database->custom_docker_run_options); $docker_compose = generateCustomDockerRunOptionsForDatabases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network); @@ -102,12 +189,32 @@ class StartDragonfly $this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md"; $this->commands[] = "echo 'Pulling {$database->image} image.'"; $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull"; + if ($this->database->enable_ssl) { + $this->commands[] = "chown -R 999:999 $this->configuration_dir/ssl/server.key $this->configuration_dir/ssl/server.crt"; + } $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d"; $this->commands[] = "echo 'Database started.'"; return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged'); } + private function buildStartCommand(): string + { + $command = "dragonfly --requirepass {$this->database->dragonfly_password}"; + + if ($this->database->enable_ssl) { + $sslArgs = [ + '--tls', + '--tls_cert_file /etc/dragonfly/certs/server.crt', + '--tls_key_file /etc/dragonfly/certs/server.key', + '--tls_ca_cert_file /etc/dragonfly/certs/coolify-ca.crt', + ]; + $command .= ' '.implode(' ', $sslArgs); + } + + return $command; + } + private function generate_local_persistent_volumes() { $local_persistent_volumes = []; diff --git a/app/Actions/Database/StartKeydb.php b/app/Actions/Database/StartKeydb.php index 6c733d318..59bcd4123 100644 --- a/app/Actions/Database/StartKeydb.php +++ b/app/Actions/Database/StartKeydb.php @@ -2,6 +2,8 @@ namespace App\Actions\Database; +use App\Helpers\SslHelper; +use App\Models\SslCertificate; use App\Models\StandaloneKeydb; use Illuminate\Support\Facades\Storage; use Lorisleiva\Actions\Concerns\AsAction; @@ -17,26 +19,84 @@ class StartKeydb public string $configuration_dir; + private ?SslCertificate $ssl_certificate = null; + public function handle(StandaloneKeydb $database) { $this->database = $database; - $startCommand = "keydb-server --requirepass {$this->database->keydb_password} --appendonly yes"; - $container_name = $this->database->uuid; $this->configuration_dir = database_configuration_dir().'/'.$container_name; $this->commands = [ "echo 'Starting database.'", + "echo 'Creating directories.'", "mkdir -p $this->configuration_dir", + "echo 'Directories created successfully.'", ]; + if (! $this->database->enable_ssl) { + $this->commands[] = "rm -rf $this->configuration_dir/ssl"; + $this->database->sslCertificates()->delete(); + $this->database->fileStorages() + ->where('resource_type', $this->database->getMorphClass()) + ->where('resource_id', $this->database->id) + ->get() + ->filter(function ($storage) { + return in_array($storage->mount_path, [ + '/etc/keydb/certs/server.crt', + '/etc/keydb/certs/server.key', + ]); + }) + ->each(function ($storage) { + $storage->delete(); + }); + } else { + $this->commands[] = "echo 'Setting up SSL for this database.'"; + $this->commands[] = "mkdir -p $this->configuration_dir/ssl"; + + $server = $this->database->destination->server; + $caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first(); + + if (! $caCert) { + $server->generateCaCertificate(); + $caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first(); + } + + if (! $caCert) { + $this->dispatch('error', 'No CA certificate found for this database. Please generate a CA certificate for this server in the server/advanced page.'); + + return; + } + + $this->ssl_certificate = $this->database->sslCertificates()->first(); + + if (! $this->ssl_certificate) { + $this->commands[] = "echo 'No SSL certificate found, generating new SSL certificate for this database.'"; + $this->ssl_certificate = SslHelper::generateSslCertificate( + commonName: $this->database->uuid, + resourceType: $this->database->getMorphClass(), + resourceId: $this->database->id, + serverId: $server->id, + caCert: $caCert->ssl_certificate, + caKey: $caCert->ssl_private_key, + configurationDir: $this->configuration_dir, + mountPath: '/etc/keydb/certs', + ); + } + } + + $container_name = $this->database->uuid; + $this->configuration_dir = database_configuration_dir().'/'.$container_name; + $persistent_storages = $this->generate_local_persistent_volumes(); $persistent_file_volumes = $this->database->fileStorages()->get(); $volume_names = $this->generate_local_persistent_volumes_only_volume_names(); $environment_variables = $this->generate_environment_variables(); $this->add_custom_keydb(); + $startCommand = $this->buildStartCommand(); + $docker_compose = [ 'services' => [ $container_name => [ @@ -72,34 +132,67 @@ class StartKeydb ], ], ]; + 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'] = generate_fluentd_configuration(); } + if (count($this->database->ports_mappings_array) > 0) { $docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array; } + + $docker_compose['services'][$container_name]['volumes'] ??= []; + if (count($persistent_storages) > 0) { - $docker_compose['services'][$container_name]['volumes'] = $persistent_storages; + $docker_compose['services'][$container_name]['volumes'] = array_merge( + $docker_compose['services'][$container_name]['volumes'] ?? [], + $persistent_storages + ); } + if (count($persistent_file_volumes) > 0) { - $docker_compose['services'][$container_name]['volumes'] = $persistent_file_volumes->map(function ($item) { - return "$item->fs_path:$item->mount_path"; - })->toArray(); + $docker_compose['services'][$container_name]['volumes'] = array_merge( + $docker_compose['services'][$container_name]['volumes'] ?? [], + $persistent_file_volumes->map(function ($item) { + return "$item->fs_path:$item->mount_path"; + })->toArray() + ); } + if (count($volume_names) > 0) { $docker_compose['volumes'] = $volume_names; } + 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', - '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"; + $docker_compose['services'][$container_name]['volumes'] = array_merge( + $docker_compose['services'][$container_name]['volumes'] ?? [], + [ + [ + 'type' => 'bind', + 'source' => $this->configuration_dir.'/keydb.conf', + 'target' => '/etc/keydb/keydb.conf', + 'read_only' => true, + ], + ] + ); + } + + if ($this->database->enable_ssl) { + $docker_compose['services'][$container_name]['volumes'] = array_merge( + $docker_compose['services'][$container_name]['volumes'] ?? [], + [ + [ + 'type' => 'bind', + 'source' => '/data/coolify/ssl/coolify-ca.crt', + 'target' => '/etc/keydb/certs/coolify-ca.crt', + 'read_only' => true, + ], + ] + ); } // Add custom docker run options @@ -112,6 +205,9 @@ class StartKeydb $this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md"; $this->commands[] = "echo 'Pulling {$database->image} image.'"; $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull"; + if ($this->database->enable_ssl) { + $this->commands[] = "chown -R 999:999 $this->configuration_dir/ssl/server.key $this->configuration_dir/ssl/server.crt"; + } $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d"; $this->commands[] = "echo 'Database started.'"; @@ -177,4 +273,36 @@ class StartKeydb instant_scp($path, "{$this->configuration_dir}/{$filename}", $this->database->destination->server); Storage::disk('local')->delete("tmp/keydb.conf_{$this->database->uuid}"); } + + private function buildStartCommand(): string + { + $hasKeydbConf = ! is_null($this->database->keydb_conf) && ! empty($this->database->keydb_conf); + $keydbConfPath = '/etc/keydb/keydb.conf'; + + if ($hasKeydbConf) { + $confContent = $this->database->keydb_conf; + $hasRequirePass = str_contains($confContent, 'requirepass'); + + if ($hasRequirePass) { + $command = "keydb-server $keydbConfPath"; + } else { + $command = "keydb-server $keydbConfPath --requirepass {$this->database->keydb_password}"; + } + } else { + $command = "keydb-server --requirepass {$this->database->keydb_password} --appendonly yes"; + } + + if ($this->database->enable_ssl) { + $sslArgs = [ + '--tls-port 6380', + '--tls-cert-file /etc/keydb/certs/server.crt', + '--tls-key-file /etc/keydb/certs/server.key', + '--tls-ca-cert-file /etc/keydb/certs/coolify-ca.crt', + '--tls-auth-clients optional', + ]; + $command .= ' '.implode(' ', $sslArgs); + } + + return $command; + } } diff --git a/app/Actions/Database/StartMariadb.php b/app/Actions/Database/StartMariadb.php index 299b07385..13dba4b43 100644 --- a/app/Actions/Database/StartMariadb.php +++ b/app/Actions/Database/StartMariadb.php @@ -2,6 +2,8 @@ namespace App\Actions\Database; +use App\Helpers\SslHelper; +use App\Models\SslCertificate; use App\Models\StandaloneMariadb; use Lorisleiva\Actions\Concerns\AsAction; use Symfony\Component\Yaml\Yaml; @@ -16,6 +18,8 @@ class StartMariadb public string $configuration_dir; + private ?SslCertificate $ssl_certificate = null; + public function handle(StandaloneMariadb $database) { $this->database = $database; @@ -25,9 +29,64 @@ class StartMariadb $this->commands = [ "echo 'Starting database.'", + "echo 'Creating directories.'", "mkdir -p $this->configuration_dir", + "echo 'Directories created successfully.'", ]; + if (! $this->database->enable_ssl) { + $this->commands[] = "rm -rf $this->configuration_dir/ssl"; + + $this->database->sslCertificates()->delete(); + + $this->database->fileStorages() + ->where('resource_type', $this->database->getMorphClass()) + ->where('resource_id', $this->database->id) + ->get() + ->filter(function ($storage) { + return in_array($storage->mount_path, [ + '/etc/mysql/certs/server.crt', + '/etc/mysql/certs/server.key', + ]); + }) + ->each(function ($storage) { + $storage->delete(); + }); + } else { + $this->commands[] = "echo 'Setting up SSL for this database.'"; + $this->commands[] = "mkdir -p $this->configuration_dir/ssl"; + + $server = $this->database->destination->server; + $caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first(); + + if (! $caCert) { + $server->generateCaCertificate(); + $caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first(); + } + + if (! $caCert) { + $this->dispatch('error', 'No CA certificate found for this database. Please generate a CA certificate for this server in the server/advanced page.'); + + return; + } + + $this->ssl_certificate = $this->database->sslCertificates()->first(); + + if (! $this->ssl_certificate) { + $this->commands[] = "echo 'No SSL certificate found, generating new SSL certificate for this database.'"; + $this->ssl_certificate = SslHelper::generateSslCertificate( + commonName: $this->database->uuid, + resourceType: $this->database->getMorphClass(), + resourceId: $this->database->id, + serverId: $server->id, + caCert: $caCert->ssl_certificate, + caKey: $caCert->ssl_private_key, + configurationDir: $this->configuration_dir, + mountPath: '/etc/mysql/certs', + ); + } + } + $persistent_storages = $this->generate_local_persistent_volumes(); $persistent_file_volumes = $this->database->fileStorages()->get(); $volume_names = $this->generate_local_persistent_volumes_only_volume_names(); @@ -67,38 +126,81 @@ class StartMariadb ], ], ]; + 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'] = generate_fluentd_configuration(); } + if (count($this->database->ports_mappings_array) > 0) { $docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array; } - if (count($persistent_storages) > 0) { - $docker_compose['services'][$container_name]['volumes'] = $persistent_storages; - } - if (count($persistent_file_volumes) > 0) { - $docker_compose['services'][$container_name]['volumes'] = $persistent_file_volumes->map(function ($item) { - return "$item->fs_path:$item->mount_path"; - })->toArray(); - } + if (count($volume_names) > 0) { $docker_compose['volumes'] = $volume_names; } + + $docker_compose['services'][$container_name]['volumes'] ??= []; + + if (count($persistent_storages) > 0) { + $docker_compose['services'][$container_name]['volumes'] = array_merge( + $docker_compose['services'][$container_name]['volumes'], + $persistent_storages + ); + } + + if (count($persistent_file_volumes) > 0) { + $docker_compose['services'][$container_name]['volumes'] = array_merge( + $docker_compose['services'][$container_name]['volumes'], + $persistent_file_volumes->map(function ($item) { + return "$item->fs_path:$item->mount_path"; + })->toArray() + ); + } + + if ($this->database->enable_ssl) { + $docker_compose['services'][$container_name]['volumes'] = array_merge( + $docker_compose['services'][$container_name]['volumes'] ?? [], + [ + [ + 'type' => 'bind', + 'source' => '/data/coolify/ssl/coolify-ca.crt', + 'target' => '/etc/mysql/certs/coolify-ca.crt', + 'read_only' => true, + ], + ] + ); + } + 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', - 'target' => '/etc/mysql/conf.d/custom-config.cnf', - 'read_only' => true, - ]; + $docker_compose['services'][$container_name]['volumes'] = array_merge( + $docker_compose['services'][$container_name]['volumes'], + [ + [ + 'type' => 'bind', + '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 = convertDockerRunToCompose($this->database->custom_docker_run_options); $docker_compose = generateCustomDockerRunOptionsForDatabases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network); + if ($this->database->enable_ssl) { + $docker_compose['services'][$container_name]['command'] = [ + 'mariadbd', + '--ssl-cert=/etc/mysql/certs/server.crt', + '--ssl-key=/etc/mysql/certs/server.key', + '--ssl-ca=/etc/mysql/certs/coolify-ca.crt', + '--require-secure-transport=1', + ]; + } $docker_compose = Yaml::dump($docker_compose, 10); $docker_compose_base64 = base64_encode($docker_compose); @@ -109,6 +211,9 @@ class StartMariadb $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull"; $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d"; $this->commands[] = "echo 'Database started.'"; + if ($this->database->enable_ssl) { + $this->commands[] = executeInDocker($this->database->uuid, 'chown mysql:mysql /etc/mysql/certs/server.crt /etc/mysql/certs/server.key'); + } return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged'); } diff --git a/app/Actions/Database/StartMongodb.php b/app/Actions/Database/StartMongodb.php index 89d35ca7b..a42f03eb5 100644 --- a/app/Actions/Database/StartMongodb.php +++ b/app/Actions/Database/StartMongodb.php @@ -2,6 +2,8 @@ namespace App\Actions\Database; +use App\Helpers\SslHelper; +use App\Models\SslCertificate; use App\Models\StandaloneMongodb; use Lorisleiva\Actions\Concerns\AsAction; use Symfony\Component\Yaml\Yaml; @@ -16,6 +18,8 @@ class StartMongodb public string $configuration_dir; + private ?SslCertificate $ssl_certificate = null; + public function handle(StandaloneMongodb $database) { $this->database = $database; @@ -24,16 +28,69 @@ class StartMongodb $container_name = $this->database->uuid; $this->configuration_dir = database_configuration_dir().'/'.$container_name; - if (isDev()) { $this->configuration_dir = '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/databases/'.$container_name; } $this->commands = [ "echo 'Starting database.'", + "echo 'Creating directories.'", "mkdir -p $this->configuration_dir", + "echo 'Directories created successfully.'", ]; + if (! $this->database->enable_ssl) { + $this->commands[] = "rm -rf $this->configuration_dir/ssl"; + + $this->database->sslCertificates()->delete(); + + $this->database->fileStorages() + ->where('resource_type', $this->database->getMorphClass()) + ->where('resource_id', $this->database->id) + ->get() + ->filter(function ($storage) { + return in_array($storage->mount_path, [ + '/etc/mongo/certs/server.pem', + ]); + }) + ->each(function ($storage) { + $storage->delete(); + }); + } else { + $this->commands[] = "echo 'Setting up SSL for this database.'"; + $this->commands[] = "mkdir -p $this->configuration_dir/ssl"; + + $server = $this->database->destination->server; + $caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first(); + + if (! $caCert) { + $server->generateCaCertificate(); + $caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first(); + } + + if (! $caCert) { + $this->dispatch('error', 'No CA certificate found for this database. Please generate a CA certificate for this server in the server/advanced page.'); + + return; + } + $this->ssl_certificate = $this->database->sslCertificates()->first(); + + if (! $this->ssl_certificate) { + $this->commands[] = "echo 'No SSL certificate found, generating new SSL certificate for this database.'"; + $this->ssl_certificate = SslHelper::generateSslCertificate( + commonName: $this->database->uuid, + resourceType: $this->database->getMorphClass(), + resourceId: $this->database->id, + serverId: $server->id, + caCert: $caCert->ssl_certificate, + caKey: $caCert->ssl_private_key, + configurationDir: $this->configuration_dir, + mountPath: '/etc/mongo/certs', + isPemKeyFileRequired: true, + ); + } + } + $persistent_storages = $this->generate_local_persistent_volumes(); $persistent_file_volumes = $this->database->fileStorages()->get(); $volume_names = $this->generate_local_persistent_volumes_only_volume_names(); @@ -79,47 +136,119 @@ class StartMongodb ], ], ]; + 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'] = generate_fluentd_configuration(); } + if (count($this->database->ports_mappings_array) > 0) { $docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array; } + + $docker_compose['services'][$container_name]['volumes'] ??= []; + if (count($persistent_storages) > 0) { - $docker_compose['services'][$container_name]['volumes'] = $persistent_storages; + $docker_compose['services'][$container_name]['volumes'] = array_merge( + $docker_compose['services'][$container_name]['volumes'] ?? [], + $persistent_storages + ); } + if (count($persistent_file_volumes) > 0) { - $docker_compose['services'][$container_name]['volumes'] = $persistent_file_volumes->map(function ($item) { - return "$item->fs_path:$item->mount_path"; - })->toArray(); + $docker_compose['services'][$container_name]['volumes'] = array_merge( + $docker_compose['services'][$container_name]['volumes'] ?? [], + $persistent_file_volumes->map(function ($item) { + return "$item->fs_path:$item->mount_path"; + })->toArray() + ); } + if (count($volume_names) > 0) { $docker_compose['volumes'] = $volume_names; } - 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', - 'target' => '/etc/mongo/mongod.conf', - 'read_only' => true, - ]; - $docker_compose['services'][$container_name]['command'] = $startCommand.' --config /etc/mongo/mongod.conf'; + + if (! empty($this->database->mongo_conf)) { + $docker_compose['services'][$container_name]['volumes'] = array_merge( + $docker_compose['services'][$container_name]['volumes'] ?? [], + [[ + 'type' => 'bind', + 'source' => $this->configuration_dir.'/mongod.conf', + 'target' => '/etc/mongo/mongod.conf', + 'read_only' => true, + ]] + ); + $docker_compose['services'][$container_name]['command'] = ['mongod', '--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', - 'target' => '/docker-entrypoint-initdb.d', - 'read_only' => true, - ]; + + $docker_compose['services'][$container_name]['volumes'] = array_merge( + $docker_compose['services'][$container_name]['volumes'] ?? [], + [[ + 'type' => 'bind', + 'source' => $this->configuration_dir.'/docker-entrypoint-initdb.d', + 'target' => '/docker-entrypoint-initdb.d', + 'read_only' => true, + ]] + ); + + if ($this->database->enable_ssl) { + $docker_compose['services'][$container_name]['volumes'] = array_merge( + $docker_compose['services'][$container_name]['volumes'] ?? [], + [ + [ + 'type' => 'bind', + 'source' => '/data/coolify/ssl/coolify-ca.crt', + 'target' => '/etc/mongo/certs/ca.pem', + 'read_only' => true, + ], + ] + ); + } // Add custom docker run options $docker_run_options = convertDockerRunToCompose($this->database->custom_docker_run_options); $docker_compose = generateCustomDockerRunOptionsForDatabases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network); + if ($this->database->enable_ssl) { + $commandParts = ['mongod']; + + $sslConfig = match ($this->database->ssl_mode) { + 'allow' => [ + '--tlsMode=allowTLS', + '--tlsAllowConnectionsWithoutCertificates', + '--tlsAllowInvalidHostnames', + ], + 'prefer' => [ + '--tlsMode=preferTLS', + '--tlsAllowConnectionsWithoutCertificates', + '--tlsAllowInvalidHostnames', + ], + 'require' => [ + '--tlsMode=requireTLS', + '--tlsAllowConnectionsWithoutCertificates', + '--tlsAllowInvalidHostnames', + ], + 'verify-full' => [ + '--tlsMode=requireTLS', + '--tlsAllowInvalidHostnames', + ], + default => [], + }; + + $commandParts = [...$commandParts, ...$sslConfig]; + $commandParts[] = '--tlsCAFile'; + $commandParts[] = '/etc/mongo/certs/ca.pem'; + $commandParts[] = '--tlsCertificateKeyFile'; + $commandParts[] = '/etc/mongo/certs/server.pem'; + + $docker_compose['services'][$container_name]['command'] = $commandParts; + } + $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,6 +257,9 @@ class StartMongodb $this->commands[] = "echo 'Pulling {$database->image} image.'"; $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull"; $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d"; + if ($this->database->enable_ssl) { + $this->commands[] = executeInDocker($this->database->uuid, 'chown mongodb:mongodb /etc/mongo/certs/server.pem'); + } $this->commands[] = "echo 'Database started.'"; return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged'); diff --git a/app/Actions/Database/StartMysql.php b/app/Actions/Database/StartMysql.php index 73db1512a..5d5611e07 100644 --- a/app/Actions/Database/StartMysql.php +++ b/app/Actions/Database/StartMysql.php @@ -2,6 +2,8 @@ namespace App\Actions\Database; +use App\Helpers\SslHelper; +use App\Models\SslCertificate; use App\Models\StandaloneMysql; use Lorisleiva\Actions\Concerns\AsAction; use Symfony\Component\Yaml\Yaml; @@ -16,6 +18,8 @@ class StartMysql public string $configuration_dir; + private ?SslCertificate $ssl_certificate = null; + public function handle(StandaloneMysql $database) { $this->database = $database; @@ -25,9 +29,64 @@ class StartMysql $this->commands = [ "echo 'Starting database.'", + "echo 'Creating directories.'", "mkdir -p $this->configuration_dir", + "echo 'Directories created successfully.'", ]; + if (! $this->database->enable_ssl) { + $this->commands[] = "rm -rf $this->configuration_dir/ssl"; + + $this->database->sslCertificates()->delete(); + + $this->database->fileStorages() + ->where('resource_type', $this->database->getMorphClass()) + ->where('resource_id', $this->database->id) + ->get() + ->filter(function ($storage) { + return in_array($storage->mount_path, [ + '/etc/mysql/certs/server.crt', + '/etc/mysql/certs/server.key', + ]); + }) + ->each(function ($storage) { + $storage->delete(); + }); + } else { + $this->commands[] = "echo 'Setting up SSL for this database.'"; + $this->commands[] = "mkdir -p $this->configuration_dir/ssl"; + + $server = $this->database->destination->server; + $caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first(); + + if (! $caCert) { + $server->generateCaCertificate(); + $caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first(); + } + + if (! $caCert) { + $this->dispatch('error', 'No CA certificate found for this database. Please generate a CA certificate for this server in the server/advanced page.'); + + return; + } + + $this->ssl_certificate = $this->database->sslCertificates()->first(); + + if (! $this->ssl_certificate) { + $this->commands[] = "echo 'No SSL certificate found, generating new SSL certificate for this database.'"; + $this->ssl_certificate = SslHelper::generateSslCertificate( + commonName: $this->database->uuid, + resourceType: $this->database->getMorphClass(), + resourceId: $this->database->id, + serverId: $server->id, + caCert: $caCert->ssl_certificate, + caKey: $caCert->ssl_private_key, + configurationDir: $this->configuration_dir, + mountPath: '/etc/mysql/certs', + ); + } + } + $persistent_storages = $this->generate_local_persistent_volumes(); $persistent_file_volumes = $this->database->fileStorages()->get(); $volume_names = $this->generate_local_persistent_volumes_only_volume_names(); @@ -67,39 +126,83 @@ class StartMysql ], ], ]; + 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'] = generate_fluentd_configuration(); } + if (count($this->database->ports_mappings_array) > 0) { $docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array; } + + $docker_compose['services'][$container_name]['volumes'] ??= []; + if (count($persistent_storages) > 0) { - $docker_compose['services'][$container_name]['volumes'] = $persistent_storages; + $docker_compose['services'][$container_name]['volumes'] = array_merge( + $docker_compose['services'][$container_name]['volumes'] ?? [], + $persistent_storages + ); } + if (count($persistent_file_volumes) > 0) { - $docker_compose['services'][$container_name]['volumes'] = $persistent_file_volumes->map(function ($item) { - return "$item->fs_path:$item->mount_path"; - })->toArray(); + $docker_compose['services'][$container_name]['volumes'] = array_merge( + $docker_compose['services'][$container_name]['volumes'] ?? [], + $persistent_file_volumes->map(function ($item) { + return "$item->fs_path:$item->mount_path"; + })->toArray() + ); } + if (count($volume_names) > 0) { $docker_compose['volumes'] = $volume_names; } + + if ($this->database->enable_ssl) { + $docker_compose['services'][$container_name]['volumes'] = array_merge( + $docker_compose['services'][$container_name]['volumes'] ?? [], + [ + [ + 'type' => 'bind', + 'source' => '/data/coolify/ssl/coolify-ca.crt', + 'target' => '/etc/mysql/certs/coolify-ca.crt', + 'read_only' => true, + ], + ] + ); + } + 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', - 'target' => '/etc/mysql/conf.d/custom-config.cnf', - 'read_only' => true, - ]; + $docker_compose['services'][$container_name]['volumes'] = array_merge( + $docker_compose['services'][$container_name]['volumes'] ?? [], + [ + [ + 'type' => 'bind', + '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 = convertDockerRunToCompose($this->database->custom_docker_run_options); $docker_compose = generateCustomDockerRunOptionsForDatabases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network); + if ($this->database->enable_ssl) { + $docker_compose['services'][$container_name]['command'] = [ + 'mysqld', + '--ssl-cert=/etc/mysql/certs/server.crt', + '--ssl-key=/etc/mysql/certs/server.key', + '--ssl-ca=/etc/mysql/certs/coolify-ca.crt', + '--require-secure-transport=1', + ]; + } + $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"; @@ -108,6 +211,11 @@ class StartMysql $this->commands[] = "echo 'Pulling {$database->image} image.'"; $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull"; $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d"; + + if ($this->database->enable_ssl) { + $this->commands[] = executeInDocker($this->database->uuid, "chown {$this->database->mysql_user}:{$this->database->mysql_user} /etc/mysql/certs/server.crt /etc/mysql/certs/server.key"); + } + $this->commands[] = "echo 'Database started.'"; return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged'); diff --git a/app/Actions/Database/StartPostgresql.php b/app/Actions/Database/StartPostgresql.php index 035849340..a40eac17b 100644 --- a/app/Actions/Database/StartPostgresql.php +++ b/app/Actions/Database/StartPostgresql.php @@ -2,6 +2,8 @@ namespace App\Actions\Database; +use App\Helpers\SslHelper; +use App\Models\SslCertificate; use App\Models\StandalonePostgresql; use Lorisleiva\Actions\Concerns\AsAction; use Symfony\Component\Yaml\Yaml; @@ -18,6 +20,8 @@ class StartPostgresql public string $configuration_dir; + private ?SslCertificate $ssl_certificate = null; + public function handle(StandalonePostgresql $database) { $this->database = $database; @@ -29,10 +33,65 @@ class StartPostgresql $this->commands = [ "echo 'Starting database.'", + "echo 'Creating directories.'", "mkdir -p $this->configuration_dir", "mkdir -p $this->configuration_dir/docker-entrypoint-initdb.d/", + "echo 'Directories created successfully.'", ]; + if (! $this->database->enable_ssl) { + $this->commands[] = "rm -rf $this->configuration_dir/ssl"; + + $this->database->sslCertificates()->delete(); + + $this->database->fileStorages() + ->where('resource_type', $this->database->getMorphClass()) + ->where('resource_id', $this->database->id) + ->get() + ->filter(function ($storage) { + return in_array($storage->mount_path, [ + '/var/lib/postgresql/certs/server.crt', + '/var/lib/postgresql/certs/server.key', + ]); + }) + ->each(function ($storage) { + $storage->delete(); + }); + } else { + $this->commands[] = "echo 'Setting up SSL for this database.'"; + $this->commands[] = "mkdir -p $this->configuration_dir/ssl"; + + $server = $this->database->destination->server; + $caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first(); + + if (! $caCert) { + $server->generateCaCertificate(); + $caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first(); + } + + if (! $caCert) { + $this->dispatch('error', 'No CA certificate found for this database. Please generate a CA certificate for this server in the server/advanced page.'); + + return; + } + + $this->ssl_certificate = $this->database->sslCertificates()->first(); + + if (! $this->ssl_certificate) { + $this->commands[] = "echo 'No SSL certificate found, generating new SSL certificate for this database.'"; + $this->ssl_certificate = SslHelper::generateSslCertificate( + commonName: $this->database->uuid, + resourceType: $this->database->getMorphClass(), + resourceId: $this->database->id, + serverId: $server->id, + caCert: $caCert->ssl_certificate, + caKey: $caCert->ssl_private_key, + configurationDir: $this->configuration_dir, + mountPath: '/var/lib/postgresql/certs', + ); + } + } + $persistent_storages = $this->generate_local_persistent_volumes(); $persistent_file_volumes = $this->database->fileStorages()->get(); $volume_names = $this->generate_local_persistent_volumes_only_volume_names(); @@ -77,49 +136,84 @@ class StartPostgresql ], ], ]; + if (filled($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'] = generate_fluentd_configuration(); } + if (count($this->database->ports_mappings_array) > 0) { $docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array; } + + $docker_compose['services'][$container_name]['volumes'] ??= []; + if (count($persistent_storages) > 0) { - $docker_compose['services'][$container_name]['volumes'] = $persistent_storages; + $docker_compose['services'][$container_name]['volumes'] = array_merge( + $docker_compose['services'][$container_name]['volumes'], + $persistent_storages + ); } + if (count($persistent_file_volumes) > 0) { - $docker_compose['services'][$container_name]['volumes'] = $persistent_file_volumes->map(function ($item) { - return "$item->fs_path:$item->mount_path"; - })->toArray(); + $docker_compose['services'][$container_name]['volumes'] = array_merge( + $docker_compose['services'][$container_name]['volumes'], + $persistent_file_volumes->map(function ($item) { + return "$item->fs_path:$item->mount_path"; + })->toArray() + ); } + if (count($volume_names) > 0) { $docker_compose['volumes'] = $volume_names; } + if (count($this->init_scripts) > 0) { foreach ($this->init_scripts as $init_script) { - $docker_compose['services'][$container_name]['volumes'][] = [ - 'type' => 'bind', - 'source' => $init_script, - 'target' => '/docker-entrypoint-initdb.d/'.basename($init_script), - 'read_only' => true, - ]; + $docker_compose['services'][$container_name]['volumes'] = array_merge( + $docker_compose['services'][$container_name]['volumes'], + [[ + 'type' => 'bind', + 'source' => $init_script, + 'target' => '/docker-entrypoint-initdb.d/'.basename($init_script), + 'read_only' => true, + ]] + ); } } + if (filled($this->database->postgres_conf)) { - $docker_compose['services'][$container_name]['volumes'][] = [ - 'type' => 'bind', - 'source' => $this->configuration_dir.'/custom-postgres.conf', - 'target' => '/etc/postgresql/postgresql.conf', - 'read_only' => true, - ]; + $docker_compose['services'][$container_name]['volumes'] = array_merge( + $docker_compose['services'][$container_name]['volumes'], + [[ + 'type' => 'bind', + 'source' => $this->configuration_dir.'/custom-postgres.conf', + 'target' => '/etc/postgresql/postgresql.conf', + 'read_only' => true, + ]] + ); $docker_compose['services'][$container_name]['command'] = [ 'postgres', '-c', 'config_file=/etc/postgresql/postgresql.conf', ]; } + + if ($this->database->enable_ssl) { + $docker_compose['services'][$container_name]['command'] = [ + 'postgres', + '-c', + 'ssl=on', + '-c', + 'ssl_cert_file=/var/lib/postgresql/certs/server.crt', + '-c', + 'ssl_key_file=/var/lib/postgresql/certs/server.key', + ]; + } + // Add custom docker run options $docker_run_options = convertDockerRunToCompose($this->database->custom_docker_run_options); $docker_compose = generateCustomDockerRunOptionsForDatabases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network); @@ -132,6 +226,9 @@ class StartPostgresql $this->commands[] = "echo 'Pulling {$database->image} image.'"; $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull"; $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d"; + if ($this->database->enable_ssl) { + $this->commands[] = executeInDocker($this->database->uuid, "chown {$this->database->postgres_user}:{$this->database->postgres_user} /var/lib/postgresql/certs/server.key /var/lib/postgresql/certs/server.crt"); + } $this->commands[] = "echo 'Database started.'"; return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged'); diff --git a/app/Actions/Database/StartRedis.php b/app/Actions/Database/StartRedis.php index 1beebd134..68a1f3fe3 100644 --- a/app/Actions/Database/StartRedis.php +++ b/app/Actions/Database/StartRedis.php @@ -2,6 +2,8 @@ namespace App\Actions\Database; +use App\Helpers\SslHelper; +use App\Models\SslCertificate; use App\Models\StandaloneRedis; use Illuminate\Support\Facades\Storage; use Lorisleiva\Actions\Concerns\AsAction; @@ -17,6 +19,8 @@ class StartRedis public string $configuration_dir; + private ?SslCertificate $ssl_certificate = null; + public function handle(StandaloneRedis $database) { $this->database = $database; @@ -26,9 +30,62 @@ class StartRedis $this->commands = [ "echo 'Starting database.'", + "echo 'Creating directories.'", "mkdir -p $this->configuration_dir", + "echo 'Directories created successfully.'", ]; + if (! $this->database->enable_ssl) { + $this->commands[] = "rm -rf $this->configuration_dir/ssl"; + $this->database->sslCertificates()->delete(); + $this->database->fileStorages() + ->where('resource_type', $this->database->getMorphClass()) + ->where('resource_id', $this->database->id) + ->get() + ->filter(function ($storage) { + return in_array($storage->mount_path, [ + '/etc/redis/certs/server.crt', + '/etc/redis/certs/server.key', + ]); + }) + ->each(function ($storage) { + $storage->delete(); + }); + } else { + $this->commands[] = "echo 'Setting up SSL for this database.'"; + $this->commands[] = "mkdir -p $this->configuration_dir/ssl"; + + $server = $this->database->destination->server; + $caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first(); + + if (! $caCert) { + $server->generateCaCertificate(); + $caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first(); + } + + if (! $caCert) { + $this->dispatch('error', 'No CA certificate found for this database. Please generate a CA certificate for this server in the server/advanced page.'); + + return; + } + + $this->ssl_certificate = $this->database->sslCertificates()->first(); + + if (! $this->ssl_certificate) { + $this->commands[] = "echo 'No SSL certificate found, generating new SSL certificate for this database.'"; + $this->ssl_certificate = SslHelper::generateSslCertificate( + commonName: $this->database->uuid, + resourceType: $this->database->getMorphClass(), + resourceId: $this->database->id, + serverId: $server->id, + caCert: $caCert->ssl_certificate, + caKey: $caCert->ssl_private_key, + configurationDir: $this->configuration_dir, + mountPath: '/etc/redis/certs', + ); + } + } + $persistent_storages = $this->generate_local_persistent_volumes(); $persistent_file_volumes = $this->database->fileStorages()->get(); $volume_names = $this->generate_local_persistent_volumes_only_volume_names(); @@ -76,26 +133,55 @@ class StartRedis ], ], ]; + 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'] = generate_fluentd_configuration(); } + if (count($this->database->ports_mappings_array) > 0) { $docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array; } + + $docker_compose['services'][$container_name]['volumes'] ??= []; + if (count($persistent_storages) > 0) { - $docker_compose['services'][$container_name]['volumes'] = $persistent_storages; + $docker_compose['services'][$container_name]['volumes'] = array_merge( + $docker_compose['services'][$container_name]['volumes'], + $persistent_storages + ); } + if (count($persistent_file_volumes) > 0) { - $docker_compose['services'][$container_name]['volumes'] = $persistent_file_volumes->map(function ($item) { - return "$item->fs_path:$item->mount_path"; - })->toArray(); + $docker_compose['services'][$container_name]['volumes'] = array_merge( + $docker_compose['services'][$container_name]['volumes'], + $persistent_file_volumes->map(function ($item) { + return "$item->fs_path:$item->mount_path"; + })->toArray() + ); } + if (count($volume_names) > 0) { $docker_compose['volumes'] = $volume_names; } + + if ($this->database->enable_ssl) { + $docker_compose['services'][$container_name]['volumes'] = array_merge( + $docker_compose['services'][$container_name]['volumes'] ?? [], + [ + [ + 'type' => 'bind', + 'source' => '/data/coolify/ssl/coolify-ca.crt', + 'target' => '/etc/redis/certs/coolify-ca.crt', + 'read_only' => true, + ], + ] + ); + } + if (! is_null($this->database->redis_conf) || ! empty($this->database->redis_conf)) { $docker_compose['services'][$container_name]['volumes'][] = [ 'type' => 'bind', @@ -116,6 +202,9 @@ class StartRedis $this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md"; $this->commands[] = "echo 'Pulling {$database->image} image.'"; $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull"; + if ($this->database->enable_ssl) { + $this->commands[] = "chown -R 999:999 $this->configuration_dir/ssl/server.key $this->configuration_dir/ssl/server.crt"; + } $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d"; $this->commands[] = "echo 'Database started.'"; @@ -202,6 +291,20 @@ class StartRedis $command = "redis-server --requirepass {$this->database->redis_password} --appendonly yes"; } + if ($this->database->enable_ssl) { + $sslArgs = [ + '--tls-port 6380', + '--tls-cert-file /etc/redis/certs/server.crt', + '--tls-key-file /etc/redis/certs/server.key', + '--tls-ca-cert-file /etc/redis/certs/coolify-ca.crt', + '--tls-auth-clients optional', + ]; + } + + if (! empty($sslArgs)) { + $command .= ' '.implode(' ', $sslArgs); + } + return $command; } diff --git a/app/Actions/Database/StopDatabase.php b/app/Actions/Database/StopDatabase.php index e4cea7cee..de4eaa31f 100644 --- a/app/Actions/Database/StopDatabase.php +++ b/app/Actions/Database/StopDatabase.php @@ -26,7 +26,7 @@ class StopDatabase } $this->stopContainer($database, $database->uuid, 300); - if (! $isDeleteOperation) { + if ($isDeleteOperation) { if ($dockerCleanup) { CleanupDocker::dispatch($server, true); } diff --git a/app/Actions/Fortify/ResetUserPassword.php b/app/Actions/Fortify/ResetUserPassword.php index d3727a52c..158996c90 100644 --- a/app/Actions/Fortify/ResetUserPassword.php +++ b/app/Actions/Fortify/ResetUserPassword.php @@ -24,5 +24,6 @@ class ResetUserPassword implements ResetsUserPasswords $user->forceFill([ 'password' => Hash::make($input['password']), ])->save(); + $user->deleteAllSessions(); } } diff --git a/app/Actions/Proxy/CheckProxy.php b/app/Actions/Proxy/CheckProxy.php index 6c8dd5234..5a2562073 100644 --- a/app/Actions/Proxy/CheckProxy.php +++ b/app/Actions/Proxy/CheckProxy.php @@ -27,13 +27,9 @@ class CheckProxy return false; } $proxyType = $server->proxyType(); - if (is_null($proxyType) || $proxyType === 'NONE' || $server->proxy->force_stop) { + if ((is_null($proxyType) || $proxyType === 'NONE' || $server->proxy->force_stop) && ! $fromUI) { return false; } - ['uptime' => $uptime, 'error' => $error] = $server->validateConnection(); - if (! $uptime) { - throw new \Exception($error); - } if (! $server->isProxyShouldRun()) { if ($fromUI) { throw new \Exception('Proxy should not run. You selected the Custom Proxy.'); @@ -41,8 +37,12 @@ class CheckProxy return false; } } + + // Determine proxy container name based on environment + $proxyContainerName = $server->isSwarm() ? 'coolify-proxy_traefik' : 'coolify-proxy'; + if ($server->isSwarm()) { - $status = getContainerStatus($server, 'coolify-proxy_traefik'); + $status = getContainerStatus($server, $proxyContainerName); $server->proxy->set('status', $status); $server->save(); if ($status === 'running') { @@ -51,7 +51,7 @@ class CheckProxy return true; } else { - $status = getContainerStatus($server, 'coolify-proxy'); + $status = getContainerStatus($server, $proxyContainerName); if ($status === 'running') { $server->proxy->set('status', 'running'); $server->save(); @@ -65,9 +65,18 @@ class CheckProxy if ($server->id === 0) { $ip = 'host.docker.internal'; } - $portsToCheck = ['80', '443']; + foreach ($portsToCheck as $port) { + // Use the smart port checker that handles dual-stack properly + if ($this->isPortConflict($server, $port, $proxyContainerName)) { + if ($fromUI) { + throw new \Exception("Port $port is in use.
You must stop the process using this port.

Docs: https://coolify.io/docs
Discord: https://coolify.io/discord"); + } else { + return false; + } + } + } try { if ($server->proxyType() !== ProxyTypes::NONE->value) { $proxyCompose = CheckConfiguration::run($server); @@ -94,18 +103,148 @@ class CheckProxy if (count($portsToCheck) === 0) { return false; } - foreach ($portsToCheck as $port) { - $connection = @fsockopen($ip, $port); - if (is_resource($connection) && fclose($connection)) { - if ($fromUI) { - throw new \Exception("Port $port is in use.
You must stop the process using this port.
Docs: https://coolify.io/docs
Discord: https://coollabs.io/discord"); - } else { - return false; - } - } - } return true; } } + + /** + * Smart port checker that handles dual-stack configurations + * Returns true only if there's a real port conflict (not just dual-stack) + */ + private function isPortConflict(Server $server, string $port, string $proxyContainerName): bool + { + // First check if our own proxy is using this port (which is fine) + try { + $getProxyContainerId = "docker ps -a --filter name=$proxyContainerName --format '{{.ID}}'"; + $containerId = trim(instant_remote_process([$getProxyContainerId], $server)); + + if (! empty($containerId)) { + $checkProxyPort = "docker inspect $containerId --format '{{json .NetworkSettings.Ports}}' | grep '\"$port/tcp\"'"; + try { + instant_remote_process([$checkProxyPort], $server); + + // Our proxy is using the port, which is fine + return false; + } catch (\Throwable $e) { + // Our container exists but not using this port + } + } + } catch (\Throwable $e) { + // Container not found or error checking, continue with regular checks + } + + // Command sets for different ways to check ports, ordered by preference + $commandSets = [ + // Set 1: Use ss to check listener counts by protocol stack + [ + 'available' => 'command -v ss >/dev/null 2>&1', + 'check' => [ + // Get listening process details + "ss_output=\$(ss -Htuln state listening sport = :$port 2>/dev/null) && echo \"\$ss_output\"", + // Count IPv4 listeners + "echo \"\$ss_output\" | grep -c ':$port '", + ], + ], + // Set 2: Use netstat as alternative to ss + [ + 'available' => 'command -v netstat >/dev/null 2>&1', + 'check' => [ + // Get listening process details + "netstat_output=\$(netstat -tuln 2>/dev/null) && echo \"\$netstat_output\" | grep ':$port '", + // Count listeners + "echo \"\$netstat_output\" | grep ':$port ' | grep -c 'LISTEN'", + ], + ], + // Set 3: Use lsof as last resort + [ + 'available' => 'command -v lsof >/dev/null 2>&1', + 'check' => [ + // Get process using the port + "lsof -i :$port -P -n | grep 'LISTEN'", + // Count listeners + "lsof -i :$port -P -n | grep 'LISTEN' | wc -l", + ], + ], + ]; + + // Try each command set until we find one available + foreach ($commandSets as $set) { + try { + // Check if the command is available + instant_remote_process([$set['available']], $server); + + // Run the actual check commands + $output = instant_remote_process($set['check'], $server, true); + + // Parse the output lines + $lines = explode("\n", trim($output)); + + // Get the detailed output and listener count + $details = trim($lines[0] ?? ''); + $count = intval(trim($lines[1] ?? '0')); + + // If no listeners or empty result, port is free + if ($count == 0 || empty($details)) { + return false; + } + + // Try to detect if this is our coolify-proxy + if (strpos($details, 'docker') !== false || strpos($details, $proxyContainerName) !== false) { + // It's likely our docker or proxy, which is fine + return false; + } + + // Check for dual-stack scenario - typically 1-2 listeners (IPv4+IPv6) + // If exactly 2 listeners and both have same port, likely dual-stack + if ($count <= 2) { + // Check if it looks like a standard dual-stack setup + $isDualStack = false; + + // Look for IPv4 and IPv6 in the listing (ss output format) + if (preg_match('/LISTEN.*:'.$port.'\s/', $details) && + (preg_match('/\*:'.$port.'\s/', $details) || + preg_match('/:::'.$port.'\s/', $details))) { + $isDualStack = true; + } + + // For netstat format + if (strpos($details, '0.0.0.0:'.$port) !== false && + strpos($details, ':::'.$port) !== false) { + $isDualStack = true; + } + + // For lsof format (IPv4 and IPv6) + if (strpos($details, '*:'.$port) !== false && + preg_match('/\*:'.$port.'.*IPv4/', $details) && + preg_match('/\*:'.$port.'.*IPv6/', $details)) { + $isDualStack = true; + } + + if ($isDualStack) { + return false; // This is just a normal dual-stack setup + } + } + + // If we get here, it's likely a real port conflict + return true; + + } catch (\Throwable $e) { + // This command set failed, try the next one + continue; + } + } + + // Fallback to simpler check if all above methods fail + try { + // Just try to bind to the port directly to see if it's available + $checkCommand = "nc -z -w1 127.0.0.1 $port >/dev/null 2>&1 && echo 'in-use' || echo 'free'"; + $result = instant_remote_process([$checkCommand], $server, true); + + return trim($result) === 'in-use'; + } catch (\Throwable $e) { + // If everything fails, assume the port is free to avoid false positives + return false; + } + } } diff --git a/app/Actions/Proxy/StopProxy.php b/app/Actions/Proxy/StopProxy.php new file mode 100644 index 000000000..a5dcc6cf4 --- /dev/null +++ b/app/Actions/Proxy/StopProxy.php @@ -0,0 +1,56 @@ +isSwarm() ? 'coolify-proxy_traefik' : 'coolify-proxy'; + $timeout = 30; + + $process = $this->stopContainer($containerName, $timeout); + + $startTime = Carbon::now()->getTimestamp(); + while ($process->running()) { + if (Carbon::now()->getTimestamp() - $startTime >= $timeout) { + $this->forceStopContainer($containerName, $server); + break; + } + usleep(100000); + } + + $this->removeContainer($containerName, $server); + } catch (\Throwable $e) { + return handleError($e); + } finally { + $server->proxy->force_stop = $forceStop; + $server->proxy->status = 'exited'; + $server->save(); + } + } + + private function stopContainer(string $containerName, int $timeout): InvokedProcess + { + return Process::timeout($timeout)->start("docker stop --time=$timeout $containerName"); + } + + private function forceStopContainer(string $containerName, Server $server) + { + instant_remote_process(["docker kill $containerName"], $server, throwError: false); + } + + private function removeContainer(string $containerName, Server $server) + { + instant_remote_process(["docker rm -f $containerName"], $server, throwError: false); + } +} diff --git a/app/Actions/Server/InstallDocker.php b/app/Actions/Server/InstallDocker.php index cbcb20368..5410b1cbd 100644 --- a/app/Actions/Server/InstallDocker.php +++ b/app/Actions/Server/InstallDocker.php @@ -2,7 +2,9 @@ namespace App\Actions\Server; +use App\Helpers\SslHelper; use App\Models\Server; +use App\Models\SslCertificate; use App\Models\StandaloneDocker; use Lorisleiva\Actions\Concerns\AsAction; @@ -17,6 +19,27 @@ class InstallDocker if (! $supported_os_type) { throw new \Exception('Server OS type is not supported for automated installation. Please install Docker manually before continuing: documentation.'); } + + if (! SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->exists()) { + $serverCert = SslHelper::generateSslCertificate( + commonName: 'Coolify CA Certificate', + serverId: $server->id, + isCaCertificate: true, + validityDays: 10 * 365 + ); + $caCertPath = config('constants.coolify.base_config_path').'/ssl/'; + + $commands = collect([ + "mkdir -p $caCertPath", + "chown -R 9999:root $caCertPath", + "chmod -R 700 $caCertPath", + "rm -rf $caCertPath/coolify-ca.crt", + "echo '{$serverCert->ssl_certificate}' > $caCertPath/coolify-ca.crt", + "chmod 644 $caCertPath/coolify-ca.crt", + ]); + remote_process($commands, $server); + } + $config = base64_encode('{ "log-driver": "json-file", "log-opts": { diff --git a/app/Actions/Server/StartSentinel.php b/app/Actions/Server/StartSentinel.php index 587ac4a8d..2785505c0 100644 --- a/app/Actions/Server/StartSentinel.php +++ b/app/Actions/Server/StartSentinel.php @@ -25,7 +25,7 @@ class StartSentinel $endpoint = data_get($server, 'settings.sentinel_custom_url'); $debug = data_get($server, 'settings.is_sentinel_debug_enabled'); $mountDir = '/data/coolify/sentinel'; - $image = "ghcr.io/coollabsio/sentinel:$version"; + $image = config('constants.coolify.registry_url').'/coollabsio/sentinel:'.$version; if (! $endpoint) { throw new \Exception('You should set FQDN in Instance Settings.'); } diff --git a/app/Actions/Server/UpdateCoolify.php b/app/Actions/Server/UpdateCoolify.php index be9b4062c..9a6cc140b 100644 --- a/app/Actions/Server/UpdateCoolify.php +++ b/app/Actions/Server/UpdateCoolify.php @@ -52,7 +52,8 @@ class UpdateCoolify { PullHelperImageJob::dispatch($this->server); - instant_remote_process(["docker pull -q ghcr.io/coollabsio/coolify:{$this->latestVersion}"], $this->server, false); + $image = config('constants.coolify.registry_url').'/coollabsio/coolify:'.$this->latestVersion; + instant_remote_process(["docker pull -q $image"], $this->server, false); remote_process([ 'curl -fsSL https://cdn.coollabs.io/coolify/upgrade.sh -o /data/coolify/source/upgrade.sh', diff --git a/app/Actions/Service/StopService.php b/app/Actions/Service/StopService.php index 95b08b437..e16dd5616 100644 --- a/app/Actions/Service/StopService.php +++ b/app/Actions/Service/StopService.php @@ -23,7 +23,7 @@ class StopService $containersToStop = $service->getContainersToStop(); $service->stopContainers($containersToStop, $server); - if (! $isDeleteOperation) { + if ($isDeleteOperation) { $service->delete_connected_networks($service->uuid); if ($dockerCleanup) { CleanupDocker::dispatch($server, true); diff --git a/app/Console/Commands/Dev.php b/app/Console/Commands/Dev.php index 257de0a92..a4cfde6f8 100644 --- a/app/Console/Commands/Dev.php +++ b/app/Console/Commands/Dev.php @@ -5,12 +5,10 @@ namespace App\Console\Commands; use App\Models\InstanceSettings; use Illuminate\Console\Command; use Illuminate\Support\Facades\Artisan; -use Illuminate\Support\Facades\Process; -use Symfony\Component\Yaml\Yaml; class Dev extends Command { - protected $signature = 'dev {--init} {--generate-openapi}'; + protected $signature = 'dev {--init}'; protected $description = 'Helper commands for development.'; @@ -21,36 +19,6 @@ class Dev extends Command return; } - if ($this->option('generate-openapi')) { - $this->generateOpenApi(); - - return; - } - } - - public function generateOpenApi() - { - // Generate OpenAPI documentation - echo "Generating OpenAPI documentation.\n"; - // https://github.com/OAI/OpenAPI-Specification/releases - $process = Process::run([ - '/var/www/html/vendor/bin/openapi', - 'app', - '-o', - 'openapi.yaml', - '--version', - '3.1.0', - ]); - $error = $process->errorOutput(); - $error = preg_replace('/^.*an object literal,.*$/m', '', $error); - $error = preg_replace('/^\h*\v+/m', '', $error); - echo $error; - echo $process->output(); - // Convert YAML to JSON - $yaml = file_get_contents('openapi.yaml'); - $json = json_encode(Yaml::parse($yaml), JSON_PRETTY_PRINT); - file_put_contents('openapi.json', $json); - echo "Converted OpenAPI YAML to JSON.\n"; } public function init() diff --git a/app/Console/Commands/OpenApi.php b/app/Console/Commands/OpenApi.php index 6cbcb310c..3cef85477 100644 --- a/app/Console/Commands/OpenApi.php +++ b/app/Console/Commands/OpenApi.php @@ -4,6 +4,7 @@ namespace App\Console\Commands; use Illuminate\Console\Command; use Illuminate\Support\Facades\Process; +use Symfony\Component\Yaml\Yaml; class OpenApi extends Command { @@ -29,5 +30,10 @@ class OpenApi extends Command $error = preg_replace('/^\h*\v+/m', '', $error); echo $error; echo $process->output(); + + $yaml = file_get_contents('openapi.yaml'); + $json = json_encode(Yaml::parse($yaml), JSON_PRETTY_PRINT); + file_put_contents('openapi.json', $json); + echo "Converted OpenAPI YAML to JSON.\n"; } } diff --git a/app/Console/Commands/RootResetPassword.php b/app/Console/Commands/RootResetPassword.php index f36c11a4f..436363d06 100644 --- a/app/Console/Commands/RootResetPassword.php +++ b/app/Console/Commands/RootResetPassword.php @@ -39,7 +39,13 @@ class RootResetPassword extends Command } $this->info('Updating root password...'); try { - User::find(0)->update(['password' => Hash::make($password)]); + $user = User::find(0); + if (! $user) { + $this->error('Root user not found.'); + + return; + } + $user->update(['password' => Hash::make($password)]); $this->info('Root password updated successfully.'); } catch (\Exception $e) { $this->error('Failed to update root password.'); diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 122d72c39..a6f24aaad 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -9,6 +9,7 @@ use App\Jobs\CleanupInstanceStuffsJob; use App\Jobs\DatabaseBackupJob; use App\Jobs\DockerCleanupJob; use App\Jobs\PullTemplatesFromCDN; +use App\Jobs\RegenerateSslCertJob; use App\Jobs\ScheduledTaskJob; use App\Jobs\ServerCheckJob; use App\Jobs\ServerStorageCheckJob; @@ -83,6 +84,8 @@ class Kernel extends ConsoleKernel $this->checkScheduledBackups(); $this->checkScheduledTasks(); + $this->scheduleInstance->job(new RegenerateSslCertJob)->twiceDaily(); + $this->scheduleInstance->command('cleanup:database --yes')->daily(); $this->scheduleInstance->command('uploads:clear')->everyTwoMinutes(); } diff --git a/app/Helpers/SslHelper.php b/app/Helpers/SslHelper.php new file mode 100644 index 000000000..6397c330d --- /dev/null +++ b/app/Helpers/SslHelper.php @@ -0,0 +1,233 @@ + OPENSSL_KEYTYPE_EC, + 'curve_name' => 'secp521r1', + ]); + + if ($privateKey === false) { + throw new \RuntimeException('Failed to generate private key: '.openssl_error_string()); + } + + if (! openssl_pkey_export($privateKey, $privateKeyStr)) { + throw new \RuntimeException('Failed to export private key: '.openssl_error_string()); + } + + if (! is_null($serverId) && ! $isCaCertificate) { + $server = Server::find($serverId); + if ($server) { + $ip = $server->getIp; + if ($ip) { + $type = filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6) + ? 'IP' + : 'DNS'; + $subjectAlternativeNames = array_unique( + array_merge($subjectAlternativeNames, ["$type:$ip"]) + ); + } + } + } + + $basicConstraints = $isCaCertificate ? 'critical, CA:TRUE, pathlen:0' : 'critical, CA:FALSE'; + $keyUsage = $isCaCertificate ? 'critical, keyCertSign, cRLSign' : 'critical, digitalSignature, keyAgreement'; + + $subjectAltNameSection = ''; + $extendedKeyUsageSection = ''; + + if (! $isCaCertificate) { + $extendedKeyUsageSection = "\nextendedKeyUsage = serverAuth, clientAuth"; + + $subjectAlternativeNames = array_values( + array_unique( + array_merge(["DNS:$commonName"], $subjectAlternativeNames) + ) + ); + + $formattedSubjectAltNames = array_map( + function ($index, $san) { + [$type, $value] = explode(':', $san, 2); + + return "{$type}.".($index + 1)." = $value"; + }, + array_keys($subjectAlternativeNames), + $subjectAlternativeNames + ); + + $subjectAltNameSection = "subjectAltName = @subject_alt_names\n\n[ subject_alt_names ]\n" + .implode("\n", $formattedSubjectAltNames); + } + + $config = << $commonName, + 'organizationName' => $organizationName, + 'countryName' => $countryName, + 'stateOrProvinceName' => $stateName, + ], $privateKey, [ + 'digest_alg' => 'sha512', + 'config' => $tempConfigPath, + 'req_extensions' => 'req_ext', + ]); + + if ($csr === false) { + throw new \RuntimeException('Failed to generate CSR: '.openssl_error_string()); + } + + $certificate = openssl_csr_sign( + $csr, + $caCert ?? null, + $caKey ?? $privateKey, + $validityDays, + [ + 'digest_alg' => 'sha512', + 'config' => $tempConfigPath, + 'x509_extensions' => 'v3_req', + ], + random_int(1, PHP_INT_MAX) + ); + + if ($certificate === false) { + throw new \RuntimeException('Failed to sign certificate: '.openssl_error_string()); + } + + if (! openssl_x509_export($certificate, $certificateStr)) { + throw new \RuntimeException('Failed to export certificate: '.openssl_error_string()); + } + + SslCertificate::query() + ->where('resource_type', $resourceType) + ->where('resource_id', $resourceId) + ->where('server_id', $serverId) + ->delete(); + + $sslCertificate = SslCertificate::create([ + 'ssl_certificate' => $certificateStr, + 'ssl_private_key' => $privateKeyStr, + 'resource_type' => $resourceType, + 'resource_id' => $resourceId, + 'server_id' => $serverId, + 'configuration_dir' => $configurationDir, + 'mount_path' => $mountPath, + 'valid_until' => CarbonImmutable::now()->addDays($validityDays), + 'is_ca_certificate' => $isCaCertificate, + 'common_name' => $commonName, + 'subject_alternative_names' => $subjectAlternativeNames, + ]); + + if ($configurationDir && $mountPath && $resourceType && $resourceId) { + $model = app($resourceType)->find($resourceId); + + $model->fileStorages() + ->where('resource_type', $model->getMorphClass()) + ->where('resource_id', $model->id) + ->get() + ->filter(function ($storage) use ($mountPath) { + return in_array($storage->mount_path, [ + $mountPath.'/server.crt', + $mountPath.'/server.key', + $mountPath.'/server.pem', + ]); + }) + ->each(function ($storage) { + $storage->delete(); + }); + + if ($isPemKeyFileRequired) { + $model->fileStorages()->create([ + 'fs_path' => $configurationDir.'/ssl/server.pem', + 'mount_path' => $mountPath.'/server.pem', + 'content' => $certificateStr."\n".$privateKeyStr, + 'is_directory' => false, + 'chmod' => '600', + 'resource_type' => $resourceType, + 'resource_id' => $resourceId, + ]); + } else { + $model->fileStorages()->create([ + 'fs_path' => $configurationDir.'/ssl/server.crt', + 'mount_path' => $mountPath.'/server.crt', + 'content' => $certificateStr, + 'is_directory' => false, + 'chmod' => '644', + 'resource_type' => $resourceType, + 'resource_id' => $resourceId, + ]); + + $model->fileStorages()->create([ + 'fs_path' => $configurationDir.'/ssl/server.key', + 'mount_path' => $mountPath.'/server.key', + 'content' => $privateKeyStr, + 'is_directory' => false, + 'chmod' => '600', + 'resource_type' => $resourceType, + 'resource_id' => $resourceId, + ]); + } + } + + return $sslCertificate; + } catch (\Throwable $e) { + throw new \RuntimeException('SSL Certificate generation failed: '.$e->getMessage(), 0, $e); + } finally { + fclose($tempConfig); + } + } +} diff --git a/app/Http/Controllers/Api/ApplicationsController.php b/app/Http/Controllers/Api/ApplicationsController.php index aef19af23..45968b6c6 100644 --- a/app/Http/Controllers/Api/ApplicationsController.php +++ b/app/Http/Controllers/Api/ApplicationsController.php @@ -932,10 +932,31 @@ class ApplicationsController extends Controller if (! $githubApp) { return response()->json(['message' => 'Github App not found.'], 404); } + $token = generateGithubInstallationToken($githubApp); + if (! $token) { + return response()->json(['message' => 'Failed to generate Github App token.'], 400); + } + + $repositories = collect(); + $page = 1; + $repositories = loadRepositoryByPage($githubApp, $token, $page); + if ($repositories['total_count'] > 0) { + while (count($repositories['repositories']) < $repositories['total_count']) { + $page++; + $repositories = loadRepositoryByPage($githubApp, $token, $page); + } + } + $gitRepository = $request->git_repository; if (str($gitRepository)->startsWith('http') || str($gitRepository)->contains('github.com')) { $gitRepository = str($gitRepository)->replace('https://', '')->replace('http://', '')->replace('github.com/', ''); } + $gitRepositoryFound = collect($repositories['repositories'])->firstWhere('full_name', $gitRepository); + if (! $gitRepositoryFound) { + return response()->json(['message' => 'Repository not found.'], 404); + } + $repository_project_id = data_get($gitRepositoryFound, 'id'); + $application = new Application; removeUnnecessaryFieldsFromRequest($request); @@ -966,6 +987,8 @@ class ApplicationsController extends Controller $application->environment_id = $environment->id; $application->source_type = $githubApp->getMorphClass(); $application->source_id = $githubApp->id; + $application->repository_project_id = $repository_project_id; + $application->save(); $application->refresh(); if (isset($useBuildServer)) { @@ -1310,7 +1333,6 @@ class ApplicationsController extends Controller $service->destination_type = $destination->getMorphClass(); $service->save(); - $service->name = "service-$service->uuid"; $service->parse(isNew: true); if ($instantDeploy) { StartService::dispatch($service); @@ -2859,198 +2881,198 @@ class ApplicationsController extends Controller ); } - #[OA\Post( - summary: 'Execute Command', - description: "Execute a command on the application's current container.", - path: '/applications/{uuid}/execute', - operationId: 'execute-command-application', - security: [ - ['bearerAuth' => []], - ], - tags: ['Applications'], - parameters: [ - new OA\Parameter( - name: 'uuid', - in: 'path', - description: 'UUID of the application.', - required: true, - schema: new OA\Schema( - type: 'string', - format: 'uuid', - ) - ), - ], - requestBody: new OA\RequestBody( - required: true, - description: 'Command to execute.', - content: new OA\MediaType( - mediaType: 'application/json', - schema: new OA\Schema( - type: 'object', - properties: [ - 'command' => ['type' => 'string', 'description' => 'Command to execute.'], - ], - ), - ), - ), - responses: [ - new OA\Response( - response: 200, - description: "Execute a command on the application's current container.", - content: [ - new OA\MediaType( - mediaType: 'application/json', - schema: new OA\Schema( - type: 'object', - properties: [ - 'message' => ['type' => 'string', 'example' => 'Command executed.'], - 'response' => ['type' => 'string'], - ] - ) - ), - ] - ), - 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 execute_command_by_uuid(Request $request) - { - // TODO: Need to review this from security perspective, to not allow arbitrary command execution - $allowedFields = ['command']; - $teamId = getTeamIdFromToken(); - if (is_null($teamId)) { - return invalidTokenResponse(); - } - $uuid = $request->route('uuid'); - if (! $uuid) { - return response()->json(['message' => 'UUID is required.'], 400); - } - $application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first(); - if (! $application) { - return response()->json(['message' => 'Application not found.'], 404); - } - $return = validateIncomingRequest($request); - if ($return instanceof \Illuminate\Http\JsonResponse) { - return $return; - } - $validator = customApiValidator($request->all(), [ - 'command' => 'string|required', - ]); + // #[OA\Post( + // summary: 'Execute Command', + // description: "Execute a command on the application's current container.", + // path: '/applications/{uuid}/execute', + // operationId: 'execute-command-application', + // security: [ + // ['bearerAuth' => []], + // ], + // tags: ['Applications'], + // parameters: [ + // new OA\Parameter( + // name: 'uuid', + // in: 'path', + // description: 'UUID of the application.', + // required: true, + // schema: new OA\Schema( + // type: 'string', + // format: 'uuid', + // ) + // ), + // ], + // requestBody: new OA\RequestBody( + // required: true, + // description: 'Command to execute.', + // content: new OA\MediaType( + // mediaType: 'application/json', + // schema: new OA\Schema( + // type: 'object', + // properties: [ + // 'command' => ['type' => 'string', 'description' => 'Command to execute.'], + // ], + // ), + // ), + // ), + // responses: [ + // new OA\Response( + // response: 200, + // description: "Execute a command on the application's current container.", + // content: [ + // new OA\MediaType( + // mediaType: 'application/json', + // schema: new OA\Schema( + // type: 'object', + // properties: [ + // 'message' => ['type' => 'string', 'example' => 'Command executed.'], + // 'response' => ['type' => 'string'], + // ] + // ) + // ), + // ] + // ), + // 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 execute_command_by_uuid(Request $request) + // { + // // TODO: Need to review this from security perspective, to not allow arbitrary command execution + // $allowedFields = ['command']; + // $teamId = getTeamIdFromToken(); + // if (is_null($teamId)) { + // return invalidTokenResponse(); + // } + // $uuid = $request->route('uuid'); + // if (! $uuid) { + // return response()->json(['message' => 'UUID is required.'], 400); + // } + // $application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first(); + // if (! $application) { + // return response()->json(['message' => 'Application not found.'], 404); + // } + // $return = validateIncomingRequest($request); + // if ($return instanceof \Illuminate\Http\JsonResponse) { + // return $return; + // } + // $validator = customApiValidator($request->all(), [ + // 'command' => 'string|required', + // ]); - $extraFields = array_diff(array_keys($request->all()), $allowedFields); - if ($validator->fails() || ! empty($extraFields)) { - $errors = $validator->errors(); - if (! empty($extraFields)) { - foreach ($extraFields as $field) { - $errors->add($field, 'This field is not allowed.'); - } - } + // $extraFields = array_diff(array_keys($request->all()), $allowedFields); + // if ($validator->fails() || ! empty($extraFields)) { + // $errors = $validator->errors(); + // if (! empty($extraFields)) { + // foreach ($extraFields as $field) { + // $errors->add($field, 'This field is not allowed.'); + // } + // } - return response()->json([ - 'message' => 'Validation failed.', - 'errors' => $errors, - ], 422); - } + // return response()->json([ + // 'message' => 'Validation failed.', + // 'errors' => $errors, + // ], 422); + // } - $container = getCurrentApplicationContainerStatus($application->destination->server, $application->id)->firstOrFail(); - $status = getContainerStatus($application->destination->server, $container['Names']); + // $container = getCurrentApplicationContainerStatus($application->destination->server, $application->id)->firstOrFail(); + // $status = getContainerStatus($application->destination->server, $container['Names']); - if ($status !== 'running') { - return response()->json([ - 'message' => 'Application is not running.', - ], 400); - } + // if ($status !== 'running') { + // return response()->json([ + // 'message' => 'Application is not running.', + // ], 400); + // } - $commands = collect([ - executeInDocker($container['Names'], $request->command), - ]); + // $commands = collect([ + // executeInDocker($container['Names'], $request->command), + // ]); - $res = instant_remote_process(command: $commands, server: $application->destination->server); + // $res = instant_remote_process(command: $commands, server: $application->destination->server); - return response()->json([ - 'message' => 'Command executed.', - 'response' => $res, - ]); - } + // return response()->json([ + // 'message' => 'Command executed.', + // 'response' => $res, + // ]); + // } - private function validateDataApplications(Request $request, Server $server) - { - $teamId = getTeamIdFromToken(); + private function validateDataApplications(Request $request, Server $server) + { + $teamId = getTeamIdFromToken(); - // Validate ports_mappings - if ($request->has('ports_mappings')) { - $ports = []; - foreach (explode(',', $request->ports_mappings) as $portMapping) { - $port = explode(':', $portMapping); - if (in_array($port[0], $ports)) { - return response()->json([ - 'message' => 'Validation failed.', - 'errors' => [ - 'ports_mappings' => 'The first number before : should be unique between mappings.', - ], - ], 422); - } - $ports[] = $port[0]; - } - } - // Validate custom_labels - if ($request->has('custom_labels')) { - if (! isBase64Encoded($request->custom_labels)) { - return response()->json([ - 'message' => 'Validation failed.', - 'errors' => [ - 'custom_labels' => 'The custom_labels should be base64 encoded.', - ], - ], 422); - } - $customLabels = base64_decode($request->custom_labels); - if (mb_detect_encoding($customLabels, 'ASCII', true) === false) { - return response()->json([ - 'message' => 'Validation failed.', - 'errors' => [ - 'custom_labels' => 'The custom_labels should be base64 encoded.', - ], - ], 422); - } - } - if ($request->has('domains') && $server->isProxyShouldRun()) { - $uuid = $request->uuid; - $fqdn = $request->domains; - $fqdn = str($fqdn)->replaceEnd(',', '')->trim(); - $fqdn = str($fqdn)->replaceStart(',', '')->trim(); - $errors = []; - $fqdn = str($fqdn)->trim()->explode(',')->map(function ($domain) use (&$errors) { - if (filter_var($domain, FILTER_VALIDATE_URL) === false) { - $errors[] = 'Invalid domain: '.$domain; - } + // Validate ports_mappings + if ($request->has('ports_mappings')) { + $ports = []; + foreach (explode(',', $request->ports_mappings) as $portMapping) { + $port = explode(':', $portMapping); + if (in_array($port[0], $ports)) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => [ + 'ports_mappings' => 'The first number before : should be unique between mappings.', + ], + ], 422); + } + $ports[] = $port[0]; + } + } + // Validate custom_labels + if ($request->has('custom_labels')) { + if (! isBase64Encoded($request->custom_labels)) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => [ + 'custom_labels' => 'The custom_labels should be base64 encoded.', + ], + ], 422); + } + $customLabels = base64_decode($request->custom_labels); + if (mb_detect_encoding($customLabels, 'ASCII', true) === false) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => [ + 'custom_labels' => 'The custom_labels should be base64 encoded.', + ], + ], 422); + } + } + if ($request->has('domains') && $server->isProxyShouldRun()) { + $uuid = $request->uuid; + $fqdn = $request->domains; + $fqdn = str($fqdn)->replaceEnd(',', '')->trim(); + $fqdn = str($fqdn)->replaceStart(',', '')->trim(); + $errors = []; + $fqdn = str($fqdn)->trim()->explode(',')->map(function ($domain) use (&$errors) { + if (filter_var($domain, FILTER_VALIDATE_URL) === false) { + $errors[] = 'Invalid domain: '.$domain; + } - return str($domain)->trim()->lower(); - }); - if (count($errors) > 0) { - return response()->json([ - 'message' => 'Validation failed.', - 'errors' => $errors, - ], 422); - } - if (checkIfDomainIsAlreadyUsed($fqdn, $teamId, $uuid)) { - return response()->json([ - 'message' => 'Validation failed.', - 'errors' => [ - 'domains' => 'One of the domain is already used.', - ], - ], 422); - } - } - } + return str($domain)->trim()->lower(); + }); + if (count($errors) > 0) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $errors, + ], 422); + } + if (checkIfDomainIsAlreadyUsed($fqdn, $teamId, $uuid)) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => [ + 'domains' => 'One of the domain is already used.', + ], + ], 422); + } + } + } } diff --git a/app/Http/Controllers/Api/DeployController.php b/app/Http/Controllers/Api/DeployController.php index 73b452f86..424c2cc76 100644 --- a/app/Http/Controllers/Api/DeployController.php +++ b/app/Http/Controllers/Api/DeployController.php @@ -5,6 +5,7 @@ namespace App\Http\Controllers\Api; use App\Actions\Database\StartDatabase; use App\Actions\Service\StartService; use App\Http\Controllers\Controller; +use App\Models\Application; use App\Models\ApplicationDeploymentQueue; use App\Models\Server; use App\Models\Tag; @@ -142,6 +143,7 @@ class DeployController extends Controller new OA\Parameter(name: 'tag', in: 'query', description: 'Tag name(s). Comma separated list is also accepted.', schema: new OA\Schema(type: 'string')), new OA\Parameter(name: 'uuid', in: 'query', description: 'Resource UUID(s). Comma separated list is also accepted.', schema: new OA\Schema(type: 'string')), new OA\Parameter(name: 'force', in: 'query', description: 'Force rebuild (without cache)', schema: new OA\Schema(type: 'boolean')), + new OA\Parameter(name: 'pr', in: 'query', description: 'Pull Request Id for deploying specific PR builds. Cannot be used with tag parameter.', schema: new OA\Schema(type: 'integer')), ], responses: [ @@ -184,26 +186,32 @@ class DeployController extends Controller public function deploy(Request $request) { $teamId = getTeamIdFromToken(); + + if (is_null($teamId)) { + return invalidTokenResponse(); + } + $uuids = $request->query->get('uuid'); $tags = $request->query->get('tag'); $force = $request->query->get('force') ?? false; + $pr = $request->query->get('pr') ? max((int) $request->query->get('pr'), 0) : 0; if ($uuids && $tags) { return response()->json(['message' => 'You can only use uuid or tag, not both.'], 400); } - if (is_null($teamId)) { - return invalidTokenResponse(); + if ($tags && $pr) { + return response()->json(['message' => 'You can only use tag or pr, not both.'], 400); } if ($tags) { return $this->by_tags($tags, $teamId, $force); } elseif ($uuids) { - return $this->by_uuids($uuids, $teamId, $force); + return $this->by_uuids($uuids, $teamId, $force, $pr); } return response()->json(['message' => 'You must provide uuid or tag.'], 400); } - private function by_uuids(string $uuid, int $teamId, bool $force = false) + private function by_uuids(string $uuid, int $teamId, bool $force = false, int $pr = 0) { $uuids = explode(',', $uuid); $uuids = collect(array_filter($uuids)); @@ -216,7 +224,7 @@ class DeployController extends Controller foreach ($uuids as $uuid) { $resource = getResourceByUuid($uuid, $teamId); if ($resource) { - ['message' => $return_message, 'deployment_uuid' => $deployment_uuid] = $this->deploy_resource($resource, $force); + ['message' => $return_message, 'deployment_uuid' => $deployment_uuid] = $this->deploy_resource($resource, $force, $pr); if ($deployment_uuid) { $deployments->push(['message' => $return_message, 'resource_uuid' => $uuid, 'deployment_uuid' => $deployment_uuid->toString()]); } else { @@ -281,7 +289,7 @@ class DeployController extends Controller return response()->json(['message' => 'No resources found with this tag.'], 404); } - public function deploy_resource($resource, bool $force = false): array + public function deploy_resource($resource, bool $force = false, int $pr = 0): array { $message = null; $deployment_uuid = null; @@ -295,6 +303,7 @@ class DeployController extends Controller application: $resource, deployment_uuid: $deployment_uuid, force_rebuild: $force, + pull_request_id: $pr, ); $message = "Application {$resource->name} deployment queued."; break; @@ -314,4 +323,68 @@ class DeployController extends Controller return ['message' => $message, 'deployment_uuid' => $deployment_uuid]; } + + #[OA\Get( + summary: 'List application deployments', + description: 'List application deployments by using the app uuid', + path: '/deployments/applications/{uuid}', + operationId: 'list-deployments-by-app-uuid', + security: [ + ['bearerAuth' => []], + ], + tags: ['Deployments'], + responses: [ + new OA\Response( + response: 200, + description: 'List application deployments by using the app uuid.', + content: [ + + new OA\MediaType( + mediaType: 'application/json', + schema: new OA\Schema( + type: 'array', + items: new OA\Items(ref: '#/components/schemas/Application'), + ) + ), + ]), + new OA\Response( + response: 401, + ref: '#/components/responses/401', + ), + new OA\Response( + response: 400, + ref: '#/components/responses/400', + ), + ] + )] + public function get_application_deployments(Request $request) + { + $request->validate([ + 'skip' => ['nullable', 'integer', 'min:0'], + 'take' => ['nullable', 'integer', 'min:1'], + ]); + + $app_uuid = $request->route('uuid', null); + $skip = $request->get('skip', 0); + $take = $request->get('take', 10); + + $teamId = getTeamIdFromToken(); + if (is_null($teamId)) { + return invalidTokenResponse(); + } + $servers = Server::whereTeamId($teamId)->get(); + + if (is_null($app_uuid)) { + return response()->json(['message' => 'Application uuid is required'], 400); + } + + $application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $app_uuid)->first(); + + if (is_null($application)) { + return response()->json(['message' => 'Application not found'], 404); + } + $deployments = $application->deployments($skip, $take); + + return response()->json($deployments); + } } diff --git a/app/Http/Controllers/Api/SecurityController.php b/app/Http/Controllers/Api/SecurityController.php index fdd46b100..55a6cd9f4 100644 --- a/app/Http/Controllers/Api/SecurityController.php +++ b/app/Http/Controllers/Api/SecurityController.php @@ -368,6 +368,20 @@ class SecurityController extends Controller response: 404, description: 'Private Key not found.', ), + new OA\Response( + response: 422, + description: 'Private Key is in use and cannot be deleted.', + content: [ + new OA\MediaType( + mediaType: 'application/json', + schema: new OA\Schema( + type: 'object', + properties: [ + 'message' => ['type' => 'string', 'example' => 'Private Key is in use and cannot be deleted.'], + ] + ) + ), + ]), ] )] public function delete_key(Request $request) @@ -384,6 +398,14 @@ class SecurityController extends Controller if (is_null($key)) { return response()->json(['message' => 'Private Key not found.'], 404); } + + if ($key->isInUse()) { + return response()->json([ + 'message' => 'Private Key is in use and cannot be deleted.', + 'details' => 'This private key is currently being used by servers, applications, or Git integrations.', + ], 422); + } + $key->forceDelete(); return response()->json([ diff --git a/app/Http/Controllers/Api/ServicesController.php b/app/Http/Controllers/Api/ServicesController.php index 03d9d209c..027bd5c1c 100644 --- a/app/Http/Controllers/Api/ServicesController.php +++ b/app/Http/Controllers/Api/ServicesController.php @@ -13,6 +13,7 @@ use App\Models\Server; use App\Models\Service; use Illuminate\Http\Request; use OpenApi\Attributes as OA; +use Symfony\Component\Yaml\Yaml; class ServicesController extends Controller { @@ -88,8 +89,8 @@ class ServicesController extends Controller } #[OA\Post( - summary: 'Create', - description: 'Create a one-click service', + summary: 'Create service', + description: 'Create a one-click / custom service', path: '/services', operationId: 'create-service', security: [ @@ -102,7 +103,7 @@ class ServicesController extends Controller mediaType: 'application/json', schema: new OA\Schema( type: 'object', - required: ['server_uuid', 'project_uuid', 'environment_name', 'environment_uuid', 'type'], + required: ['server_uuid', 'project_uuid', 'environment_name', 'environment_uuid'], properties: [ 'type' => [ 'description' => 'The one-click service type', @@ -204,6 +205,7 @@ class ServicesController extends Controller 'server_uuid' => ['type' => 'string', 'description' => 'Server UUID.'], 'destination_uuid' => ['type' => 'string', 'description' => 'Destination UUID. Required if server has multiple destinations.'], 'instant_deploy' => ['type' => 'boolean', 'default' => false, 'description' => 'Start the service immediately after creation.'], + 'docker_compose_raw' => ['type' => 'string', 'description' => 'The Docker Compose raw content.'], ], ), ), @@ -211,7 +213,7 @@ class ServicesController extends Controller responses: [ new OA\Response( response: 201, - description: 'Create a service.', + description: 'Service created successfully.', content: [ new OA\MediaType( mediaType: 'application/json', @@ -237,7 +239,7 @@ class ServicesController extends Controller )] public function create_service(Request $request) { - $allowedFields = ['type', 'name', 'description', 'project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'instant_deploy']; + $allowedFields = ['type', 'name', 'description', 'project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'instant_deploy', 'docker_compose_raw']; $teamId = getTeamIdFromToken(); if (is_null($teamId)) { @@ -249,12 +251,13 @@ class ServicesController extends Controller return $return; } $validator = customApiValidator($request->all(), [ - 'type' => 'string|required', + 'type' => 'string|required_without:docker_compose_raw', + 'docker_compose_raw' => 'string|required_without:type', 'project_uuid' => 'string|required', 'environment_name' => 'string|nullable', 'environment_uuid' => 'string|nullable', 'server_uuid' => 'string|required', - 'destination_uuid' => 'string', + 'destination_uuid' => 'string|nullable', 'name' => 'string|max:255', 'description' => 'string|nullable', 'instant_deploy' => 'boolean', @@ -372,12 +375,16 @@ class ServicesController extends Controller ]); } - return response()->json(['message' => 'Service not found.'], 404); - } else { - return response()->json(['message' => 'Invalid service type.', 'valid_service_types' => $serviceKeys], 400); - } + return response()->json(['message' => 'Service not found.', 'valid_service_types' => $serviceKeys], 404); + } elseif (filled($request->docker_compose_raw)) { - return response()->json(['message' => 'Invalid service type.'], 400); + $service = new Service; + $result = $this->upsert_service($request, $service, $teamId); + + return response()->json(serializeApiResponse($result))->setStatusCode(201); + } else { + return response()->json(['message' => 'No service type or docker_compose_raw provided.'], 400); + } } #[OA\Get( @@ -511,6 +518,206 @@ class ServicesController extends Controller ]); } + #[OA\Patch( + summary: 'Update', + description: 'Update service by UUID.', + path: '/services/{uuid}', + operationId: 'update-service-by-uuid', + security: [ + ['bearerAuth' => []], + ], + tags: ['Services'], + requestBody: new OA\RequestBody( + description: 'Service updated.', + required: true, + content: [ + new OA\MediaType( + mediaType: 'application/json', + schema: new OA\Schema( + type: 'object', + required: ['server_uuid', 'project_uuid', 'environment_name', 'environment_uuid', 'docker_compose_raw'], + properties: [ + 'name' => ['type' => 'string', 'description' => 'The service name.'], + 'description' => ['type' => 'string', 'description' => 'The service description.'], + 'project_uuid' => ['type' => 'string', 'description' => 'The project UUID.'], + 'environment_name' => ['type' => 'string', 'description' => 'The environment name.'], + 'environment_uuid' => ['type' => 'string', 'description' => 'The environment UUID.'], + 'server_uuid' => ['type' => 'string', 'description' => 'The server UUID.'], + 'destination_uuid' => ['type' => 'string', 'description' => 'The destination UUID.'], + 'instant_deploy' => ['type' => 'boolean', 'description' => 'The flag to indicate if the service should be deployed instantly.'], + 'connect_to_docker_network' => ['type' => 'boolean', 'default' => false, 'description' => 'Connect the service to the predefined docker network.'], + 'docker_compose_raw' => ['type' => 'string', 'description' => 'The Docker Compose raw content.'], + ], + ) + ), + ] + ), + responses: [ + new OA\Response( + response: 200, + description: 'Service updated.', + content: [ + new OA\MediaType( + mediaType: 'application/json', + schema: new OA\Schema( + type: 'object', + properties: [ + 'uuid' => ['type' => 'string', 'description' => 'Service UUID.'], + 'domains' => ['type' => 'array', 'items' => ['type' => 'string'], 'description' => 'Service domains.'], + ] + ) + ), + ] + ), + 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_by_uuid(Request $request) + { + $teamId = getTeamIdFromToken(); + if (is_null($teamId)) { + return invalidTokenResponse(); + } + + $return = validateIncomingRequest($request); + if ($return instanceof \Illuminate\Http\JsonResponse) { + return $return; + } + + $service = Service::whereRelation('environment.project.team', 'id', $teamId)->whereUuid($request->uuid)->first(); + + if (! $service) { + return response()->json(['message' => 'Service not found.'], 404); + } + + $result = $this->upsert_service($request, $service, $teamId); + + return response()->json(serializeApiResponse($result))->setStatusCode(200); + } + + private function upsert_service(Request $request, Service $service, string $teamId) + { + $allowedFields = ['name', 'description', 'project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'instant_deploy', 'docker_compose_raw', 'connect_to_docker_network']; + $validator = customApiValidator($request->all(), [ + 'project_uuid' => 'string|required', + 'environment_name' => 'string|nullable', + 'environment_uuid' => 'string|nullable', + 'server_uuid' => 'string|required', + 'destination_uuid' => 'string', + 'name' => 'string|max:255', + 'description' => 'string|nullable', + 'instant_deploy' => 'boolean', + 'connect_to_docker_network' => 'boolean', + 'docker_compose_raw' => 'string|required', + ]); + + $extraFields = array_diff(array_keys($request->all()), $allowedFields); + if ($validator->fails() || ! empty($extraFields)) { + $errors = $validator->errors(); + if (! empty($extraFields)) { + foreach ($extraFields as $field) { + $errors->add($field, 'This field is not allowed.'); + } + } + + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => $errors, + ], 422); + } + + $environmentUuid = $request->environment_uuid; + $environmentName = $request->environment_name; + if (blank($environmentUuid) && blank($environmentName)) { + return response()->json(['message' => 'You need to provide at least one of environment_name or environment_uuid.'], 422); + } + $serverUuid = $request->server_uuid; + $instantDeploy = $request->instant_deploy ?? false; + $project = Project::whereTeamId($teamId)->whereUuid($request->project_uuid)->first(); + if (! $project) { + return response()->json(['message' => 'Project not found.'], 404); + } + $environment = $project->environments()->where('name', $environmentName)->first(); + if (! $environment) { + $environment = $project->environments()->where('uuid', $environmentUuid)->first(); + } + if (! $environment) { + return response()->json(['message' => 'Environment not found.'], 404); + } + $server = Server::whereTeamId($teamId)->whereUuid($serverUuid)->first(); + if (! $server) { + return response()->json(['message' => 'Server not found.'], 404); + } + $destinations = $server->destinations(); + if ($destinations->count() == 0) { + return response()->json(['message' => 'Server has no destinations.'], 400); + } + if ($destinations->count() > 1 && ! $request->has('destination_uuid')) { + return response()->json(['message' => 'Server has multiple destinations and you do not set destination_uuid.'], 400); + } + $destination = $destinations->first(); + if (! isBase64Encoded($request->docker_compose_raw)) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => [ + 'docker_compose_raw' => 'The docker_compose_raw should be base64 encoded.', + ], + ], 422); + } + $dockerComposeRaw = base64_decode($request->docker_compose_raw); + if (mb_detect_encoding($dockerComposeRaw, 'ASCII', true) === false) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => [ + 'docker_compose_raw' => 'The docker_compose_raw should be base64 encoded.', + ], + ], 422); + } + $dockerCompose = base64_decode($request->docker_compose_raw); + $dockerComposeRaw = Yaml::dump(Yaml::parse($dockerCompose), 10, 2, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK); + $connectToDockerNetwork = $request->connect_to_docker_network ?? false; + + $service->name = $request->name ?? null; + $service->description = $request->description ?? null; + $service->docker_compose_raw = $dockerComposeRaw; + $service->environment_id = $environment->id; + $service->server_id = $server->id; + $service->destination_id = $destination->id; + $service->destination_type = $destination->getMorphClass(); + $service->connect_to_docker_network = $connectToDockerNetwork; + $service->save(); + + $service->parse(); + if ($instantDeploy) { + StartService::dispatch($service); + } + + $domains = $service->applications()->get()->pluck('fqdn')->sort(); + $domains = $domains->map(function ($domain) { + if (count(explode(':', $domain)) > 2) { + return str($domain)->beforeLast(':')->value(); + } + + return $domain; + })->values(); + + return [ + 'uuid' => $service->uuid, + 'domains' => $domains, + ]; + } + #[OA\Get( summary: 'List Envs', description: 'List all envs by service UUID.', diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index 93b43ea07..9afcbe371 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -329,13 +329,8 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue } else { $this->write_deployment_configurations(); } - $this->execute_remote_command( - [ - "docker rm -f {$this->deployment_uuid} >/dev/null 2>&1", - 'hidden' => true, - 'ignore_errors' => true, - ] - ); + $this->application_deployment_queue->addLogEntry("Starting graceful shutdown container: {$this->deployment_uuid}"); + $this->graceful_shutdown_container($this->deployment_uuid); ApplicationStatusChanged::dispatch(data_get($this->application, 'environment.project.team.id')); } @@ -1211,7 +1206,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue if ($this->container_name) { $counter = 1; $this->application_deployment_queue->addLogEntry('Waiting for healthcheck to pass on the new container.'); - if ($this->full_healthcheck_url) { + if ($this->full_healthcheck_url && ! $this->application->custom_healthcheck_found) { $this->application_deployment_queue->addLogEntry("Healthcheck URL (inside the container): {$this->full_healthcheck_url}"); } $this->application_deployment_queue->addLogEntry("Waiting for the start period ({$this->application->health_check_start_period} seconds) before starting healthcheck."); @@ -1366,13 +1361,8 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue } } $this->application_deployment_queue->addLogEntry("Preparing container with helper image: $helperImage."); - $this->execute_remote_command( - [ - 'command' => "docker rm -f {$this->deployment_uuid}", - 'ignore_errors' => true, - 'hidden' => true, - ] - ); + $this->application_deployment_queue->addLogEntry("Starting graceful shutdown container: {$this->deployment_uuid}"); + $this->graceful_shutdown_container($this->deployment_uuid); $this->execute_remote_command( [ $runCommand, @@ -1718,8 +1708,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue 'save' => 'dockerfile_from_repo', 'ignore_errors' => true, ]); - $dockerfile = collect(str($this->saved_outputs->get('dockerfile_from_repo'))->trim()->explode("\n")); - $this->application->parseHealthcheckFromDockerfile($dockerfile); + $this->application->parseHealthcheckFromDockerfile($this->saved_outputs->get('dockerfile_from_repo')); } $docker_compose = [ 'services' => [ @@ -2029,7 +2018,11 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf"); if (str($this->application->custom_nginx_configuration)->isNotEmpty()) { $nginx_config = base64_encode($this->application->custom_nginx_configuration); } else { - $nginx_config = base64_encode(defaultNginxConfiguration()); + if ($this->application->settings->is_spa) { + $nginx_config = base64_encode(defaultNginxConfiguration('spa')); + } else { + $nginx_config = base64_encode(defaultNginxConfiguration()); + } } } else { if ($this->application->build_pack === 'nixpacks') { @@ -2096,7 +2089,11 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf"); if (str($this->application->custom_nginx_configuration)->isNotEmpty()) { $nginx_config = base64_encode($this->application->custom_nginx_configuration); } else { - $nginx_config = base64_encode(defaultNginxConfiguration()); + if ($this->application->settings->is_spa) { + $nginx_config = base64_encode(defaultNginxConfiguration('spa')); + } else { + $nginx_config = base64_encode(defaultNginxConfiguration()); + } } } $build_command = "docker build {$this->addHosts} --network host -f {$this->workdir}/Dockerfile {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}"; diff --git a/app/Jobs/CleanupHelperContainersJob.php b/app/Jobs/CleanupHelperContainersJob.php index 0e1fcb4d7..c82a27ce9 100644 --- a/app/Jobs/CleanupHelperContainersJob.php +++ b/app/Jobs/CleanupHelperContainersJob.php @@ -20,7 +20,7 @@ class CleanupHelperContainersJob implements ShouldBeEncrypted, ShouldBeUnique, S public function handle(): void { try { - $containers = instant_remote_process_with_timeout(['docker container ps --format \'{{json .}}\' | jq -s \'map(select(.Image | contains("ghcr.io/coollabsio/coolify-helper")))\''], $this->server, false); + $containers = instant_remote_process_with_timeout(['docker container ps --format \'{{json .}}\' | jq -s \'map(select(.Image | contains("'.config('constants.coolify.registry_url').'/coollabsio/coolify-helper")))\''], $this->server, false); $containerIds = collect(json_decode($containers))->pluck('ID'); if ($containerIds->count() > 0) { foreach ($containerIds as $containerId) { diff --git a/app/Jobs/DatabaseBackupJob.php b/app/Jobs/DatabaseBackupJob.php index 6070ad16a..3276711c5 100644 --- a/app/Jobs/DatabaseBackupJob.php +++ b/app/Jobs/DatabaseBackupJob.php @@ -484,6 +484,11 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue $fullImageName = $this->getFullImageName(); + $containerExists = instant_remote_process(["docker ps -a -q -f name=backup-of-{$this->backup->uuid}"], $this->server, false); + if (filled($containerExists)) { + instant_remote_process(["docker rm -f backup-of-{$this->backup->uuid}"], $this->server, false); + } + if (isDev()) { if ($this->database->name === 'coolify-db') { $backup_location_from = '/var/lib/docker/volumes/coolify_dev_backups_data/_data/coolify/coolify-db-'.$this->server->ip.$this->backup_file; diff --git a/app/Jobs/DeleteResourceJob.php b/app/Jobs/DeleteResourceJob.php index 8b9228e5f..9fd46db77 100644 --- a/app/Jobs/DeleteResourceJob.php +++ b/app/Jobs/DeleteResourceJob.php @@ -66,12 +66,9 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue } if ($this->deleteVolumes && $this->resource->type() !== 'service') { - $this->resource?->delete_volumes($persistentStorages); + $this->resource->delete_volumes($persistentStorages); + $this->resource->persistentStorages()->delete(); } - if ($this->deleteConfigurations) { - $this->resource?->delete_configurations(); - } - $isDatabase = $this->resource instanceof StandalonePostgresql || $this->resource instanceof StandaloneRedis || $this->resource instanceof StandaloneMongodb @@ -80,6 +77,18 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue || $this->resource instanceof StandaloneKeydb || $this->resource instanceof StandaloneDragonfly || $this->resource instanceof StandaloneClickhouse; + + if ($this->deleteConfigurations) { + $this->resource->delete_configurations(); // rename to FileStorages + $this->resource->fileStorages()->delete(); + } + if ($isDatabase) { + $this->resource->sslCertificates()->delete(); + $this->resource->scheduledBackups()->delete(); + $this->resource->environment_variables()->delete(); + $this->resource->tags()->detach(); + } + $server = data_get($this->resource, 'server') ?? data_get($this->resource, 'destination.server'); if (($this->dockerCleanup || $isDatabase) && $server) { CleanupDocker::dispatch($server, true); diff --git a/app/Jobs/RegenerateSslCertJob.php b/app/Jobs/RegenerateSslCertJob.php new file mode 100644 index 000000000..cf598c75c --- /dev/null +++ b/app/Jobs/RegenerateSslCertJob.php @@ -0,0 +1,78 @@ +server_id) { + $query->where('server_id', $this->server_id); + } + + if (! $this->force_regeneration) { + $query->where('valid_until', '<=', now()->addDays(14)); + } + + $query->where('is_ca_certificate', false); + + $regenerated = collect(); + + $query->cursor()->each(function ($certificate) use ($regenerated) { + try { + $caCert = SslCertificate::where('server_id', $certificate->server_id) + ->where('is_ca_certificate', true) + ->first(); + + if (! $caCert) { + Log::error("No CA certificate found for server_id: {$certificate->server_id}"); + + return; + } + SSLHelper::generateSslCertificate( + commonName: $certificate->common_name, + subjectAlternativeNames: $certificate->subject_alternative_names, + resourceType: $certificate->resource_type, + resourceId: $certificate->resource_id, + serverId: $certificate->server_id, + configurationDir: $certificate->configuration_dir, + mountPath: $certificate->mount_path, + caCert: $caCert->ssl_certificate, + caKey: $caCert->ssl_private_key, + ); + $regenerated->push($certificate); + } catch (\Exception $e) { + Log::error('Failed to regenerate SSL certificate: '.$e->getMessage()); + } + }); + + if ($regenerated->isNotEmpty()) { + $this->team?->notify(new SslExpirationNotification($regenerated)); + } + } +} diff --git a/app/Jobs/RestartProxyJob.php b/app/Jobs/RestartProxyJob.php new file mode 100644 index 000000000..7fc716f70 --- /dev/null +++ b/app/Jobs/RestartProxyJob.php @@ -0,0 +1,46 @@ +server->uuid))->dontRelease()]; + } + + public function __construct(public Server $server) {} + + public function handle() + { + try { + StopProxy::run($this->server); + + $this->server->proxy->force_stop = false; + $this->server->save(); + StartProxy::run($this->server, force: true); + + CheckProxy::run($this->server, true); + } catch (\Throwable $e) { + return handleError($e); + } + } +} diff --git a/app/Livewire/Boarding/Index.php b/app/Livewire/Boarding/Index.php index 15eabfec5..430470fa0 100644 --- a/app/Livewire/Boarding/Index.php +++ b/app/Livewire/Boarding/Index.php @@ -7,6 +7,7 @@ use App\Models\PrivateKey; use App\Models\Project; use App\Models\Server; use App\Models\Team; +use App\Services\ConfigurationRepository; use Illuminate\Support\Collection; use Livewire\Component; use Visus\Cuid2\Cuid2; @@ -266,7 +267,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA== public function validateServer() { try { - config()->set('constants.ssh.mux_enabled', false); + $this->disableSshMux(); // EC2 does not have `uptime` command, lol instant_remote_process(['ls /'], $this->createdServer, true); @@ -376,6 +377,12 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA== ['private' => $this->privateKey, 'public' => $this->publicKey] = generateSSHKey(); } + private function disableSshMux(): void + { + $configRepository = app(ConfigurationRepository::class); + $configRepository->disableSshMux(); + } + public function render() { return view('livewire.boarding.index')->layout('layouts.boarding'); diff --git a/app/Livewire/Notifications/Discord.php b/app/Livewire/Notifications/Discord.php index 57007813e..9489eb128 100644 --- a/app/Livewire/Notifications/Discord.php +++ b/app/Livewire/Notifications/Discord.php @@ -56,6 +56,9 @@ class Discord extends Component #[Validate(['boolean'])] public bool $serverUnreachableDiscordNotifications = true; + #[Validate(['boolean'])] + public bool $discordPingEnabled = true; + public function mount() { try { @@ -87,6 +90,8 @@ class Discord extends Component $this->settings->server_reachable_discord_notifications = $this->serverReachableDiscordNotifications; $this->settings->server_unreachable_discord_notifications = $this->serverUnreachableDiscordNotifications; + $this->settings->discord_ping_enabled = $this->discordPingEnabled; + $this->settings->save(); refreshSession(); } else { @@ -105,12 +110,30 @@ class Discord extends Component $this->serverDiskUsageDiscordNotifications = $this->settings->server_disk_usage_discord_notifications; $this->serverReachableDiscordNotifications = $this->settings->server_reachable_discord_notifications; $this->serverUnreachableDiscordNotifications = $this->settings->server_unreachable_discord_notifications; + + $this->discordPingEnabled = $this->settings->discord_ping_enabled; + } + } + + public function instantSaveDiscordPingEnabled() + { + try { + $original = $this->discordPingEnabled; + $this->validate([ + 'discordPingEnabled' => 'required', + ]); + $this->saveModel(); + } catch (\Throwable $e) { + $this->discordPingEnabled = $original; + + return handleError($e, $this); } } public function instantSaveDiscordEnabled() { try { + $original = $this->discordEnabled; $this->validate([ 'discordWebhookUrl' => 'required', ], [ @@ -118,7 +141,7 @@ class Discord extends Component ]); $this->saveModel(); } catch (\Throwable $e) { - $this->discordEnabled = false; + $this->discordEnabled = $original; return handleError($e, $this); } diff --git a/app/Livewire/Profile/Index.php b/app/Livewire/Profile/Index.php index 53314cd5c..788802353 100644 --- a/app/Livewire/Profile/Index.php +++ b/app/Livewire/Profile/Index.php @@ -70,6 +70,7 @@ class Index extends Component $this->current_password = ''; $this->new_password = ''; $this->new_password_confirmation = ''; + $this->dispatch('reloadWindow'); } catch (\Throwable $e) { return handleError($e, $this); } diff --git a/app/Livewire/Project/Application/Configuration.php b/app/Livewire/Project/Application/Configuration.php index 56e0caf75..267ca72ad 100644 --- a/app/Livewire/Project/Application/Configuration.php +++ b/app/Livewire/Project/Application/Configuration.php @@ -22,6 +22,7 @@ class Configuration extends Component public function mount() { $this->currentRoute = request()->route()->getName(); + $project = currentTeam() ->projects() ->select('id', 'uuid', 'team_id') @@ -39,6 +40,9 @@ class Configuration extends Component $this->project = $project; $this->environment = $environment; $this->application = $application; + if ($this->application->build_pack === 'dockercompose' && $this->currentRoute === 'project.application.healthcheck') { + return redirect()->route('project.application.configuration', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]); + } } public function render() diff --git a/app/Livewire/Project/Application/General.php b/app/Livewire/Project/Application/General.php index ec7ea6381..bfe0f8387 100644 --- a/app/Livewire/Project/Application/General.php +++ b/app/Livewire/Project/Application/General.php @@ -87,6 +87,7 @@ class General extends Component 'application.post_deployment_command_container' => 'nullable', 'application.custom_nginx_configuration' => 'nullable', 'application.settings.is_static' => 'boolean|required', + 'application.settings.is_spa' => 'boolean|required', 'application.settings.is_build_server_enabled' => 'boolean|required', 'application.settings.is_container_label_escape_enabled' => 'boolean|required', 'application.settings.is_container_label_readonly_enabled' => 'boolean|required', @@ -126,6 +127,7 @@ class General extends Component 'application.docker_compose_custom_build_command' => 'Docker compose custom build command', 'application.custom_nginx_configuration' => 'Custom Nginx configuration', 'application.settings.is_static' => 'Is static', + 'application.settings.is_spa' => 'Is SPA', 'application.settings.is_build_server_enabled' => 'Is build server enabled', 'application.settings.is_container_label_escape_enabled' => 'Is container label escape enabled', 'application.settings.is_container_label_readonly_enabled' => 'Is container label readonly', @@ -173,6 +175,9 @@ class General extends Component public function instantSave() { + if ($this->application->settings->isDirty('is_spa')) { + $this->generateNginxConfiguration($this->application->settings->is_spa ? 'spa' : 'static'); + } $this->application->settings->save(); $this->dispatch('success', 'Settings saved.'); $this->application->refresh(); @@ -192,6 +197,7 @@ class General extends Component if ($this->application->settings->is_container_label_readonly_enabled) { $this->resetDefaultLabels(false); } + } public function loadComposeFile($isInit = false) @@ -289,9 +295,9 @@ class General extends Component } } - public function generateNginxConfiguration() + public function generateNginxConfiguration($type = 'static') { - $this->application->custom_nginx_configuration = defaultNginxConfiguration(); + $this->application->custom_nginx_configuration = defaultNginxConfiguration($type); $this->application->save(); $this->dispatch('success', 'Nginx configuration generated.'); } @@ -371,6 +377,9 @@ class General extends Component if ($this->application->isDirty('redirect')) { $this->setRedirect(); } + if ($this->application->isDirty('dockerfile')) { + $this->application->parseHealthcheckFromDockerfile($this->application->dockerfile); + } $this->checkFqdns(); @@ -448,7 +457,6 @@ class General extends Component { $config = GenerateConfig::run($this->application, true); $fileName = str($this->application->name)->slug()->append('_config.json'); - dd($config); return response()->streamDownload(function () use ($config) { echo $config; diff --git a/app/Livewire/Project/Database/Dragonfly/General.php b/app/Livewire/Project/Database/Dragonfly/General.php index ea6cd46b0..0fffbef31 100644 --- a/app/Livewire/Project/Database/Dragonfly/General.php +++ b/app/Livewire/Project/Database/Dragonfly/General.php @@ -4,8 +4,11 @@ namespace App\Livewire\Project\Database\Dragonfly; use App\Actions\Database\StartDatabaseProxy; use App\Actions\Database\StopDatabaseProxy; +use App\Helpers\SslHelper; use App\Models\Server; +use App\Models\SslCertificate; use App\Models\StandaloneDragonfly; +use Carbon\Carbon; use Exception; use Illuminate\Support\Facades\Auth; use Livewire\Attributes\Validate; @@ -50,12 +53,19 @@ class General extends Component #[Validate(['nullable', 'boolean'])] public bool $isLogDrainEnabled = false; + public ?Carbon $certificateValidUntil = null; + + #[Validate(['nullable', 'boolean'])] + public bool $enable_ssl = false; + public function getListeners() { + $userId = Auth::id(); $teamId = Auth::user()->currentTeam()->id; return [ "echo-private:team.{$teamId},DatabaseProxyStopped" => 'databaseProxyStopped', + "echo-private:user.{$userId},DatabaseStatusChanged" => '$refresh', ]; } @@ -64,6 +74,12 @@ class General extends Component try { $this->syncData(); $this->server = data_get($this->database, 'destination.server'); + + $existingCert = $this->database->sslCertificates()->first(); + + if ($existingCert) { + $this->certificateValidUntil = $existingCert->valid_until; + } } catch (\Throwable $e) { return handleError($e, $this); } @@ -82,6 +98,7 @@ class General extends Component $this->database->public_port = $this->publicPort; $this->database->custom_docker_run_options = $this->customDockerRunOptions; $this->database->is_log_drain_enabled = $this->isLogDrainEnabled; + $this->database->enable_ssl = $this->enable_ssl; $this->database->save(); $this->dbUrl = $this->database->internal_db_url; @@ -96,6 +113,7 @@ class General extends Component $this->publicPort = $this->database->public_port; $this->customDockerRunOptions = $this->database->custom_docker_run_options; $this->isLogDrainEnabled = $this->database->is_log_drain_enabled; + $this->enable_ssl = $this->database->enable_ssl; $this->dbUrl = $this->database->internal_db_url; $this->dbUrlPublic = $this->database->external_db_url; } @@ -174,4 +192,61 @@ class General extends Component } } } + + public function instantSaveSSL() + { + try { + $this->syncData(true); + $this->dispatch('success', 'SSL configuration updated.'); + } catch (Exception $e) { + return handleError($e, $this); + } + } + + public function regenerateSslCertificate() + { + try { + $existingCert = $this->database->sslCertificates()->first(); + + if (! $existingCert) { + $this->dispatch('error', 'No existing SSL certificate found for this database.'); + + return; + } + + $server = $this->database->destination->server; + + $caCert = SslCertificate::where('server_id', $server->id) + ->where('is_ca_certificate', true) + ->first(); + + if (! $caCert) { + $server->generateCaCertificate(); + $caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first(); + } + + if (! $caCert) { + $this->dispatch('error', 'No CA certificate found for this database. Please generate a CA certificate for this server in the server/advanced page.'); + + return; + } + + SslHelper::generateSslCertificate( + commonName: $existingCert->commonName, + subjectAlternativeNames: $existingCert->subjectAlternativeNames ?? [], + resourceType: $existingCert->resource_type, + resourceId: $existingCert->resource_id, + serverId: $existingCert->server_id, + caCert: $caCert->ssl_certificate, + caKey: $caCert->ssl_private_key, + configurationDir: $existingCert->configuration_dir, + mountPath: $existingCert->mount_path, + isPemKeyFileRequired: true, + ); + + $this->dispatch('success', 'SSL certificates regenerated. Restart database to apply changes.'); + } catch (Exception $e) { + handleError($e, $this); + } + } } diff --git a/app/Livewire/Project/Database/Heading.php b/app/Livewire/Project/Database/Heading.php index c3b57b9f4..9ddb1909c 100644 --- a/app/Livewire/Project/Database/Heading.php +++ b/app/Livewire/Project/Database/Heading.php @@ -31,8 +31,8 @@ class Heading extends Component $this->database->update([ 'started_at' => now(), ]); - $this->dispatch('refresh'); $this->check_status(); + if (is_null($this->database->config_hash) || $this->database->isConfigurationChanged()) { $this->database->isConfigurationChanged(true); $this->dispatch('configurationChanged'); @@ -44,7 +44,7 @@ class Heading extends Component public function check_status($showNotification = false) { if ($this->database->destination->server->isFunctional()) { - GetContainersStatus::dispatch($this->database->destination->server); + GetContainersStatus::run($this->database->destination->server); } if ($showNotification) { @@ -63,6 +63,7 @@ class Heading extends Component $this->database->status = 'exited'; $this->database->save(); $this->check_status(); + $this->dispatch('refresh'); } public function restart() diff --git a/app/Livewire/Project/Database/Keydb/General.php b/app/Livewire/Project/Database/Keydb/General.php index e768495eb..cfc22aedc 100644 --- a/app/Livewire/Project/Database/Keydb/General.php +++ b/app/Livewire/Project/Database/Keydb/General.php @@ -4,8 +4,11 @@ namespace App\Livewire\Project\Database\Keydb; use App\Actions\Database\StartDatabaseProxy; use App\Actions\Database\StopDatabaseProxy; +use App\Helpers\SslHelper; use App\Models\Server; +use App\Models\SslCertificate; use App\Models\StandaloneKeydb; +use Carbon\Carbon; use Exception; use Illuminate\Support\Facades\Auth; use Livewire\Attributes\Validate; @@ -53,12 +56,20 @@ class General extends Component #[Validate(['nullable', 'boolean'])] public bool $isLogDrainEnabled = false; + public ?Carbon $certificateValidUntil = null; + + #[Validate(['boolean'])] + public bool $enable_ssl = false; + public function getListeners() { + $userId = Auth::id(); $teamId = Auth::user()->currentTeam()->id; return [ "echo-private:team.{$teamId},DatabaseProxyStopped" => 'databaseProxyStopped', + "echo-private:user.{$userId},DatabaseStatusChanged" => '$refresh', + 'refresh' => '$refresh', ]; } @@ -67,6 +78,12 @@ class General extends Component try { $this->syncData(); $this->server = data_get($this->database, 'destination.server'); + + $existingCert = $this->database->sslCertificates()->first(); + + if ($existingCert) { + $this->certificateValidUntil = $existingCert->valid_until; + } } catch (\Throwable $e) { return handleError($e, $this); } @@ -86,6 +103,7 @@ class General extends Component $this->database->public_port = $this->publicPort; $this->database->custom_docker_run_options = $this->customDockerRunOptions; $this->database->is_log_drain_enabled = $this->isLogDrainEnabled; + $this->database->enable_ssl = $this->enable_ssl; $this->database->save(); $this->dbUrl = $this->database->internal_db_url; @@ -101,6 +119,7 @@ class General extends Component $this->publicPort = $this->database->public_port; $this->customDockerRunOptions = $this->database->custom_docker_run_options; $this->isLogDrainEnabled = $this->database->is_log_drain_enabled; + $this->enable_ssl = $this->database->enable_ssl; $this->dbUrl = $this->database->internal_db_url; $this->dbUrlPublic = $this->database->external_db_url; } @@ -179,4 +198,48 @@ class General extends Component } } } + + public function instantSaveSSL() + { + try { + $this->syncData(true); + $this->dispatch('success', 'SSL configuration updated.'); + } catch (Exception $e) { + return handleError($e, $this); + } + } + + public function regenerateSslCertificate() + { + try { + $existingCert = $this->database->sslCertificates()->first(); + + if (! $existingCert) { + $this->dispatch('error', 'No existing SSL certificate found for this database.'); + + return; + } + + $caCert = SslCertificate::where('server_id', $existingCert->server_id) + ->where('is_ca_certificate', true) + ->first(); + + SslHelper::generateSslCertificate( + commonName: $existingCert->commonName, + subjectAlternativeNames: $existingCert->subjectAlternativeNames ?? [], + resourceType: $existingCert->resource_type, + resourceId: $existingCert->resource_id, + serverId: $existingCert->server_id, + caCert: $caCert->ssl_certificate, + caKey: $caCert->ssl_private_key, + configurationDir: $existingCert->configuration_dir, + mountPath: $existingCert->mount_path, + isPemKeyFileRequired: true, + ); + + $this->dispatch('success', 'SSL certificates regenerated. Restart database to apply changes.'); + } catch (Exception $e) { + handleError($e, $this); + } + } } diff --git a/app/Livewire/Project/Database/Mariadb/General.php b/app/Livewire/Project/Database/Mariadb/General.php index c9d473223..174f907c8 100644 --- a/app/Livewire/Project/Database/Mariadb/General.php +++ b/app/Livewire/Project/Database/Mariadb/General.php @@ -4,9 +4,13 @@ namespace App\Livewire\Project\Database\Mariadb; use App\Actions\Database\StartDatabaseProxy; use App\Actions\Database\StopDatabaseProxy; +use App\Helpers\SslHelper; use App\Models\Server; +use App\Models\SslCertificate; use App\Models\StandaloneMariadb; +use Carbon\Carbon; use Exception; +use Illuminate\Support\Facades\Auth; use Livewire\Component; class General extends Component @@ -21,6 +25,18 @@ class General extends Component public ?string $db_url_public = null; + public ?Carbon $certificateValidUntil = null; + + public function getListeners() + { + $userId = Auth::id(); + + return [ + "echo-private:user.{$userId},DatabaseStatusChanged" => '$refresh', + 'refresh' => '$refresh', + ]; + } + protected $rules = [ 'database.name' => 'required', 'database.description' => 'nullable', @@ -35,6 +51,7 @@ class General extends Component 'database.public_port' => 'nullable|integer', 'database.is_log_drain_enabled' => 'nullable|boolean', 'database.custom_docker_run_options' => 'nullable', + 'database.enable_ssl' => 'boolean', ]; protected $validationAttributes = [ @@ -50,6 +67,7 @@ class General extends Component 'database.is_public' => 'Is Public', 'database.public_port' => 'Public Port', 'database.custom_docker_run_options' => 'Custom Docker Options', + 'database.enable_ssl' => 'Enable SSL', ]; public function mount() @@ -57,6 +75,12 @@ class General extends Component $this->db_url = $this->database->internal_db_url; $this->db_url_public = $this->database->external_db_url; $this->server = data_get($this->database, 'destination.server'); + + $existingCert = $this->database->sslCertificates()->first(); + + if ($existingCert) { + $this->certificateValidUntil = $existingCert->valid_until; + } } public function instantSaveAdvanced() @@ -127,6 +151,48 @@ class General extends Component } } + public function instantSaveSSL() + { + try { + $this->database->save(); + $this->dispatch('success', 'SSL configuration updated.'); + } catch (Exception $e) { + return handleError($e, $this); + } + } + + public function regenerateSslCertificate() + { + try { + $existingCert = $this->database->sslCertificates()->first(); + + if (! $existingCert) { + $this->dispatch('error', 'No existing SSL certificate found for this database.'); + + return; + } + + $caCert = SslCertificate::where('server_id', $existingCert->server_id)->where('is_ca_certificate', true)->first(); + + SslHelper::generateSslCertificate( + commonName: $existingCert->common_name, + subjectAlternativeNames: $existingCert->subject_alternative_names ?? [], + resourceType: $existingCert->resource_type, + resourceId: $existingCert->resource_id, + serverId: $existingCert->server_id, + caCert: $caCert->ssl_certificate, + caKey: $caCert->ssl_private_key, + configurationDir: $existingCert->configuration_dir, + mountPath: $existingCert->mount_path, + isPemKeyFileRequired: true, + ); + + $this->dispatch('success', 'SSL certificates have been regenerated. Please restart the database for changes to take effect.'); + } catch (Exception $e) { + return handleError($e, $this); + } + } + public function refresh(): void { $this->database->refresh(); diff --git a/app/Livewire/Project/Database/Mongodb/General.php b/app/Livewire/Project/Database/Mongodb/General.php index e19895dae..2ac6e43b7 100644 --- a/app/Livewire/Project/Database/Mongodb/General.php +++ b/app/Livewire/Project/Database/Mongodb/General.php @@ -4,9 +4,13 @@ namespace App\Livewire\Project\Database\Mongodb; use App\Actions\Database\StartDatabaseProxy; use App\Actions\Database\StopDatabaseProxy; +use App\Helpers\SslHelper; use App\Models\Server; +use App\Models\SslCertificate; use App\Models\StandaloneMongodb; +use Carbon\Carbon; use Exception; +use Illuminate\Support\Facades\Auth; use Livewire\Component; class General extends Component @@ -21,6 +25,18 @@ class General extends Component public ?string $db_url_public = null; + public ?Carbon $certificateValidUntil = null; + + public function getListeners() + { + $userId = Auth::id(); + + return [ + "echo-private:user.{$userId},DatabaseStatusChanged" => '$refresh', + 'refresh' => '$refresh', + ]; + } + protected $rules = [ 'database.name' => 'required', 'database.description' => 'nullable', @@ -34,6 +50,8 @@ class General extends Component 'database.public_port' => 'nullable|integer', 'database.is_log_drain_enabled' => 'nullable|boolean', 'database.custom_docker_run_options' => 'nullable', + 'database.enable_ssl' => 'boolean', + 'database.ssl_mode' => 'nullable|string|in:allow,prefer,require,verify-full', ]; protected $validationAttributes = [ @@ -48,6 +66,8 @@ class General extends Component 'database.is_public' => 'Is Public', 'database.public_port' => 'Public Port', 'database.custom_docker_run_options' => 'Custom Docker Run Options', + 'database.enable_ssl' => 'Enable SSL', + 'database.ssl_mode' => 'SSL Mode', ]; public function mount() @@ -55,6 +75,12 @@ class General extends Component $this->db_url = $this->database->internal_db_url; $this->db_url_public = $this->database->external_db_url; $this->server = data_get($this->database, 'destination.server'); + + $existingCert = $this->database->sslCertificates()->first(); + + if ($existingCert) { + $this->certificateValidUntil = $existingCert->valid_until; + } } public function instantSaveAdvanced() @@ -128,6 +154,53 @@ class General extends Component } } + public function updatedDatabaseSslMode() + { + $this->instantSaveSSL(); + } + + public function instantSaveSSL() + { + try { + $this->database->save(); + $this->dispatch('success', 'SSL configuration updated.'); + } catch (Exception $e) { + return handleError($e, $this); + } + } + + public function regenerateSslCertificate() + { + try { + $existingCert = $this->database->sslCertificates()->first(); + + if (! $existingCert) { + $this->dispatch('error', 'No existing SSL certificate found for this database.'); + + return; + } + + $caCert = SslCertificate::where('server_id', $existingCert->server_id)->where('is_ca_certificate', true)->first(); + + SslHelper::generateSslCertificate( + commonName: $existingCert->common_name, + subjectAlternativeNames: $existingCert->subject_alternative_names ?? [], + resourceType: $existingCert->resource_type, + resourceId: $existingCert->resource_id, + serverId: $existingCert->server_id, + caCert: $caCert->ssl_certificate, + caKey: $caCert->ssl_private_key, + configurationDir: $existingCert->configuration_dir, + mountPath: $existingCert->mount_path, + isPemKeyFileRequired: true, + ); + + $this->dispatch('success', 'SSL certificates have been regenerated. Please restart the database for changes to take effect.'); + } catch (Exception $e) { + return handleError($e, $this); + } + } + public function refresh(): void { $this->database->refresh(); diff --git a/app/Livewire/Project/Database/Mysql/General.php b/app/Livewire/Project/Database/Mysql/General.php index 7d5270ddf..ea0ea4691 100644 --- a/app/Livewire/Project/Database/Mysql/General.php +++ b/app/Livewire/Project/Database/Mysql/General.php @@ -4,9 +4,13 @@ namespace App\Livewire\Project\Database\Mysql; use App\Actions\Database\StartDatabaseProxy; use App\Actions\Database\StopDatabaseProxy; +use App\Helpers\SslHelper; use App\Models\Server; +use App\Models\SslCertificate; use App\Models\StandaloneMysql; +use Carbon\Carbon; use Exception; +use Illuminate\Support\Facades\Auth; use Livewire\Component; class General extends Component @@ -21,6 +25,18 @@ class General extends Component public ?string $db_url_public = null; + public ?Carbon $certificateValidUntil = null; + + public function getListeners() + { + $userId = Auth::id(); + + return [ + "echo-private:user.{$userId},DatabaseStatusChanged" => '$refresh', + 'refresh' => '$refresh', + ]; + } + protected $rules = [ 'database.name' => 'required', 'database.description' => 'nullable', @@ -35,6 +51,8 @@ class General extends Component 'database.public_port' => 'nullable|integer', 'database.is_log_drain_enabled' => 'nullable|boolean', 'database.custom_docker_run_options' => 'nullable', + 'database.enable_ssl' => 'boolean', + 'database.ssl_mode' => 'nullable|string|in:PREFERRED,REQUIRED,VERIFY_CA,VERIFY_IDENTITY', ]; protected $validationAttributes = [ @@ -50,6 +68,8 @@ class General extends Component 'database.is_public' => 'Is Public', 'database.public_port' => 'Public Port', 'database.custom_docker_run_options' => 'Custom Docker Run Options', + 'database.enable_ssl' => 'Enable SSL', + 'database.ssl_mode' => 'SSL Mode', ]; public function mount() @@ -57,6 +77,12 @@ class General extends Component $this->db_url = $this->database->internal_db_url; $this->db_url_public = $this->database->external_db_url; $this->server = data_get($this->database, 'destination.server'); + + $existingCert = $this->database->sslCertificates()->first(); + + if ($existingCert) { + $this->certificateValidUntil = $existingCert->valid_until; + } } public function instantSaveAdvanced() @@ -127,6 +153,53 @@ class General extends Component } } + public function updatedDatabaseSslMode() + { + $this->instantSaveSSL(); + } + + public function instantSaveSSL() + { + try { + $this->database->save(); + $this->dispatch('success', 'SSL configuration updated.'); + } catch (Exception $e) { + return handleError($e, $this); + } + } + + public function regenerateSslCertificate() + { + try { + $existingCert = $this->database->sslCertificates()->first(); + + if (! $existingCert) { + $this->dispatch('error', 'No existing SSL certificate found for this database.'); + + return; + } + + $caCert = SslCertificate::where('server_id', $existingCert->server_id)->where('is_ca_certificate', true)->first(); + + SslHelper::generateSslCertificate( + commonName: $existingCert->common_name, + subjectAlternativeNames: $existingCert->subject_alternative_names ?? [], + resourceType: $existingCert->resource_type, + resourceId: $existingCert->resource_id, + serverId: $existingCert->server_id, + caCert: $caCert->ssl_certificate, + caKey: $caCert->ssl_private_key, + configurationDir: $existingCert->configuration_dir, + mountPath: $existingCert->mount_path, + isPemKeyFileRequired: true, + ); + + $this->dispatch('success', 'SSL certificates have been regenerated. Please restart the database for changes to take effect.'); + } catch (Exception $e) { + return handleError($e, $this); + } + } + public function refresh(): void { $this->database->refresh(); diff --git a/app/Livewire/Project/Database/Postgresql/General.php b/app/Livewire/Project/Database/Postgresql/General.php index 88dd5c1a8..4162f47b5 100644 --- a/app/Livewire/Project/Database/Postgresql/General.php +++ b/app/Livewire/Project/Database/Postgresql/General.php @@ -4,9 +4,13 @@ namespace App\Livewire\Project\Database\Postgresql; use App\Actions\Database\StartDatabaseProxy; use App\Actions\Database\StopDatabaseProxy; +use App\Helpers\SslHelper; use App\Models\Server; +use App\Models\SslCertificate; use App\Models\StandalonePostgresql; +use Carbon\Carbon; use Exception; +use Illuminate\Support\Facades\Auth; use Livewire\Component; class General extends Component @@ -23,10 +27,15 @@ class General extends Component public ?string $db_url_public = null; + public ?Carbon $certificateValidUntil = null; + public function getListeners() { + $userId = Auth::id(); + return [ - 'refresh', + "echo-private:user.{$userId},DatabaseStatusChanged" => '$refresh', + 'refresh' => '$refresh', 'save_init_script', 'delete_init_script', ]; @@ -48,6 +57,8 @@ class General extends Component 'database.public_port' => 'nullable|integer', 'database.is_log_drain_enabled' => 'nullable|boolean', 'database.custom_docker_run_options' => 'nullable', + 'database.enable_ssl' => 'boolean', + 'database.ssl_mode' => 'nullable|string|in:allow,prefer,require,verify-ca,verify-full', ]; protected $validationAttributes = [ @@ -65,6 +76,8 @@ class General extends Component 'database.is_public' => 'Is Public', 'database.public_port' => 'Public Port', 'database.custom_docker_run_options' => 'Custom Docker Run Options', + 'database.enable_ssl' => 'Enable SSL', + 'database.ssl_mode' => 'SSL Mode', ]; public function mount() @@ -72,6 +85,12 @@ class General extends Component $this->db_url = $this->database->internal_db_url; $this->db_url_public = $this->database->external_db_url; $this->server = data_get($this->database, 'destination.server'); + + $existingCert = $this->database->sslCertificates()->first(); + + if ($existingCert) { + $this->certificateValidUntil = $existingCert->valid_until; + } } public function instantSaveAdvanced() @@ -91,6 +110,53 @@ class General extends Component } } + public function updatedDatabaseSslMode() + { + $this->instantSaveSSL(); + } + + public function instantSaveSSL() + { + try { + $this->database->save(); + $this->dispatch('success', 'SSL configuration updated.'); + } catch (Exception $e) { + return handleError($e, $this); + } + } + + public function regenerateSslCertificate() + { + try { + $existingCert = $this->database->sslCertificates()->first(); + + if (! $existingCert) { + $this->dispatch('error', 'No existing SSL certificate found for this database.'); + + return; + } + + $caCert = SslCertificate::where('server_id', $existingCert->server_id)->where('is_ca_certificate', true)->first(); + + SslHelper::generateSslCertificate( + commonName: $existingCert->common_name, + subjectAlternativeNames: $existingCert->subject_alternative_names ?? [], + resourceType: $existingCert->resource_type, + resourceId: $existingCert->resource_id, + serverId: $existingCert->server_id, + caCert: $caCert->ssl_certificate, + caKey: $caCert->ssl_private_key, + configurationDir: $existingCert->configuration_dir, + mountPath: $existingCert->mount_path, + isPemKeyFileRequired: true, + ); + + $this->dispatch('success', 'SSL certificates have been regenerated. Please restart the database for changes to take effect.'); + } catch (Exception $e) { + return handleError($e, $this); + } + } + public function instantSave() { try { @@ -143,7 +209,7 @@ class General extends Component $delete_command = "rm -f $old_file_path"; try { instant_remote_process([$delete_command], $this->server); - } catch (\Exception $e) { + } catch (Exception $e) { $this->dispatch('error', 'Failed to remove old init script from server: '.$e->getMessage()); return; @@ -184,7 +250,7 @@ class General extends Component $command = "rm -f $file_path"; try { instant_remote_process([$command], $this->server); - } catch (\Exception $e) { + } catch (Exception $e) { $this->dispatch('error', 'Failed to remove init script from server: '.$e->getMessage()); return; @@ -201,16 +267,11 @@ class General extends Component $this->database->init_scripts = $updatedScripts; $this->database->save(); - $this->refresh(); + $this->dispatch('refresh')->self(); $this->dispatch('success', 'Init script deleted from the database and the server.'); } } - public function refresh(): void - { - $this->database->refresh(); - } - public function save_new_init_script() { $this->validate([ diff --git a/app/Livewire/Project/Database/Redis/General.php b/app/Livewire/Project/Database/Redis/General.php index 05babeaec..f03f1256d 100644 --- a/app/Livewire/Project/Database/Redis/General.php +++ b/app/Livewire/Project/Database/Redis/General.php @@ -4,25 +4,24 @@ namespace App\Livewire\Project\Database\Redis; use App\Actions\Database\StartDatabaseProxy; use App\Actions\Database\StopDatabaseProxy; +use App\Helpers\SslHelper; use App\Models\Server; +use App\Models\SslCertificate; use App\Models\StandaloneRedis; +use Carbon\Carbon; use Exception; +use Illuminate\Support\Facades\Auth; use Livewire\Component; class General extends Component { - protected $listeners = [ - 'envsUpdated' => 'refresh', - 'refresh', - ]; - public Server $server; public StandaloneRedis $database; public string $redis_username; - public string $redis_password; + public ?string $redis_password; public string $redis_version; @@ -30,6 +29,19 @@ class General extends Component public ?string $db_url_public = null; + public ?Carbon $certificateValidUntil = null; + + public function getListeners() + { + $userId = Auth::id(); + + return [ + "echo-private:user.{$userId},DatabaseStatusChanged" => '$refresh', + 'envsUpdated' => 'refresh', + 'refresh', + ]; + } + protected $rules = [ 'database.name' => 'required', 'database.description' => 'nullable', @@ -42,6 +54,7 @@ class General extends Component 'database.custom_docker_run_options' => 'nullable', 'redis_username' => 'required', 'redis_password' => 'required', + 'database.enable_ssl' => 'boolean', ]; protected $validationAttributes = [ @@ -55,12 +68,18 @@ class General extends Component 'database.custom_docker_run_options' => 'Custom Docker Options', 'redis_username' => 'Redis Username', 'redis_password' => 'Redis Password', + 'database.enable_ssl' => 'Enable SSL', ]; public function mount() { $this->server = data_get($this->database, 'destination.server'); $this->refreshView(); + $existingCert = $this->database->sslCertificates()->first(); + + if ($existingCert) { + $this->certificateValidUntil = $existingCert->valid_until; + } } public function instantSaveAdvanced() @@ -136,6 +155,48 @@ class General extends Component } } + public function instantSaveSSL() + { + try { + $this->database->save(); + $this->dispatch('success', 'SSL configuration updated.'); + } catch (Exception $e) { + return handleError($e, $this); + } + } + + public function regenerateSslCertificate() + { + try { + $existingCert = $this->database->sslCertificates()->first(); + + if (! $existingCert) { + $this->dispatch('error', 'No existing SSL certificate found for this database.'); + + return; + } + + $caCert = SslCertificate::where('server_id', $existingCert->server_id)->where('is_ca_certificate', true)->first(); + + SslHelper::generateSslCertificate( + commonName: $existingCert->commonName, + subjectAlternativeNames: $existingCert->subjectAlternativeNames ?? [], + resourceType: $existingCert->resource_type, + resourceId: $existingCert->resource_id, + serverId: $existingCert->server_id, + caCert: $caCert->ssl_certificate, + caKey: $caCert->ssl_private_key, + configurationDir: $existingCert->configuration_dir, + mountPath: $existingCert->mount_path, + isPemKeyFileRequired: true, + ); + + $this->dispatch('success', 'SSL certificates regenerated. Restart database to apply changes.'); + } catch (Exception $e) { + handleError($e, $this); + } + } + public function refresh(): void { $this->database->refresh(); diff --git a/app/Livewire/Project/New/DockerCompose.php b/app/Livewire/Project/New/DockerCompose.php index 2f51094d1..3d47ffae5 100644 --- a/app/Livewire/Project/New/DockerCompose.php +++ b/app/Livewire/Project/New/DockerCompose.php @@ -7,7 +7,6 @@ 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; @@ -66,7 +65,6 @@ class DockerCompose extends Component $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, @@ -85,8 +83,6 @@ class DockerCompose extends Component 'resourceable_type' => $service->getMorphClass(), ]); } - $service->name = "service-$service->uuid"; - $service->parse(isNew: true); return redirect()->route('project.service.configuration', [ diff --git a/app/Livewire/Project/New/GithubPrivateRepository.php b/app/Livewire/Project/New/GithubPrivateRepository.php index 4a81d841f..b1b0aef15 100644 --- a/app/Livewire/Project/New/GithubPrivateRepository.php +++ b/app/Livewire/Project/New/GithubPrivateRepository.php @@ -106,11 +106,15 @@ class GithubPrivateRepository extends Component $this->selected_github_app_id = $github_app_id; $this->github_app = GithubApp::where('id', $github_app_id)->first(); $this->token = generateGithubInstallationToken($this->github_app); - $this->loadRepositoryByPage(); + $repositories = loadRepositoryByPage($this->github_app, $this->token, $this->page); + $this->total_repositories_count = $repositories['total_count']; + $this->repositories = $this->repositories->concat(collect($repositories['repositories'])); if ($this->repositories->count() < $this->total_repositories_count) { while ($this->repositories->count() < $this->total_repositories_count) { $this->page++; - $this->loadRepositoryByPage(); + $repositories = loadRepositoryByPage($this->github_app, $this->token, $this->page); + $this->total_repositories_count = $repositories['total_count']; + $this->repositories = $this->repositories->concat(collect($repositories['repositories'])); } } $this->repositories = $this->repositories->sortBy('name'); @@ -120,21 +124,6 @@ class GithubPrivateRepository extends Component $this->current_step = 'repository'; } - protected function loadRepositoryByPage() - { - $response = Http::withToken($this->token)->get("{$this->github_app->api_url}/installation/repositories?per_page=100&page={$this->page}"); - $json = $response->json(); - if ($response->status() !== 200) { - return $this->dispatch('error', $json['message']); - } - - if ($json['total_count'] === 0) { - return; - } - $this->total_repositories_count = $json['total_count']; - $this->repositories = $this->repositories->concat(collect($json['repositories'])); - } - public function loadBranches() { $this->selected_repository_owner = $this->repositories->where('id', $this->selected_repository_id)->first()['owner']['login']; diff --git a/app/Livewire/Project/New/SimpleDockerfile.php b/app/Livewire/Project/New/SimpleDockerfile.php index c3ed6039a..ebc9878dc 100644 --- a/app/Livewire/Project/New/SimpleDockerfile.php +++ b/app/Livewire/Project/New/SimpleDockerfile.php @@ -74,7 +74,7 @@ CMD ["nginx", "-g", "daemon off;"] 'fqdn' => $fqdn, ]); - $application->parseHealthcheckFromDockerfile(dockerfile: collect(str($this->dockerfile)->trim()->explode("\n")), isInit: true); + $application->parseHealthcheckFromDockerfile(dockerfile: $this->dockerfile, isInit: true); return redirect()->route('project.application.configuration', [ 'application_uuid' => $application->uuid, diff --git a/app/Livewire/Project/Resource/Create.php b/app/Livewire/Project/Resource/Create.php index 0faf0b8da..e7cff4f29 100644 --- a/app/Livewire/Project/Resource/Create.php +++ b/app/Livewire/Project/Resource/Create.php @@ -73,7 +73,6 @@ class Create extends Component if ($oneClickService) { $destination = StandaloneDocker::whereUuid($destination_uuid)->first(); $service_payload = [ - 'name' => "$oneClickServiceName-".str()->random(10), 'docker_compose_raw' => base64_decode($oneClickService), 'environment_id' => $environment->id, 'service_type' => $oneClickServiceName, diff --git a/app/Livewire/Project/Service/FileStorage.php b/app/Livewire/Project/Service/FileStorage.php index 4d070bc0c..5b88c15eb 100644 --- a/app/Livewire/Project/Service/FileStorage.php +++ b/app/Livewire/Project/Service/FileStorage.php @@ -49,7 +49,6 @@ class FileStorage extends Component $this->workdir = null; $this->fs_path = $this->fileStorage->fs_path; } - $this->fileStorage->loadStorageOnServer(); } public function convertToDirectory() @@ -68,6 +67,18 @@ class FileStorage extends Component } } + public function loadStorageOnServer() + { + try { + $this->fileStorage->loadStorageOnServer(); + $this->dispatch('success', 'File storage loaded from server.'); + } catch (\Throwable $e) { + return handleError($e, $this); + } finally { + $this->dispatch('refreshStorages'); + } + } + public function convertToFile() { try { diff --git a/app/Livewire/Project/Shared/EnvironmentVariable/All.php b/app/Livewire/Project/Shared/EnvironmentVariable/All.php index 35e585c82..57952ddb3 100644 --- a/app/Livewire/Project/Shared/EnvironmentVariable/All.php +++ b/app/Livewire/Project/Shared/EnvironmentVariable/All.php @@ -3,10 +3,13 @@ namespace App\Livewire\Project\Shared\EnvironmentVariable; use App\Models\EnvironmentVariable; +use App\Traits\EnvironmentVariableProtection; use Livewire\Component; class All extends Component { + use EnvironmentVariableProtection; + public $resource; public string $resourceClass; @@ -138,17 +141,57 @@ class All extends Component private function handleBulkSubmit() { $variables = parseEnvFormatToArray($this->variables); + $changesMade = false; + $errorOccurred = false; - $this->deleteRemovedVariables(false, $variables); - $this->updateOrCreateVariables(false, $variables); + // Try to delete removed variables + $deletedCount = $this->deleteRemovedVariables(false, $variables); + if ($deletedCount > 0) { + $changesMade = true; + } elseif ($deletedCount === 0 && $this->resource->environment_variables()->whereNotIn('key', array_keys($variables))->exists()) { + // If we tried to delete but couldn't (due to Docker Compose), mark as error + $errorOccurred = true; + } + + // Update or create variables + $updatedCount = $this->updateOrCreateVariables(false, $variables); + if ($updatedCount > 0) { + $changesMade = true; + } if ($this->showPreview) { $previewVariables = parseEnvFormatToArray($this->variablesPreview); - $this->deleteRemovedVariables(true, $previewVariables); - $this->updateOrCreateVariables(true, $previewVariables); + + // Try to delete removed preview variables + $deletedPreviewCount = $this->deleteRemovedVariables(true, $previewVariables); + if ($deletedPreviewCount > 0) { + $changesMade = true; + } elseif ($deletedPreviewCount === 0 && $this->resource->environment_variables_preview()->whereNotIn('key', array_keys($previewVariables))->exists()) { + // If we tried to delete but couldn't (due to Docker Compose), mark as error + $errorOccurred = true; + } + + // Update or create preview variables + $updatedPreviewCount = $this->updateOrCreateVariables(true, $previewVariables); + if ($updatedPreviewCount > 0) { + $changesMade = true; + } } - $this->dispatch('success', 'Environment variables updated.'); + // Debug information + \Log::info('Environment variables update status', [ + 'deletedCount' => $deletedCount, + 'updatedCount' => $updatedCount, + 'deletedPreviewCount' => $deletedPreviewCount ?? 0, + 'updatedPreviewCount' => $updatedPreviewCount ?? 0, + 'changesMade' => $changesMade, + 'errorOccurred' => $errorOccurred, + ]); + + // Only show success message if changes were actually made and no errors occurred + if ($changesMade && ! $errorOccurred) { + $this->dispatch('success', 'Environment variables updated.'); + } } private function handleSingleSubmit($data) @@ -184,11 +227,46 @@ class All extends Component private function deleteRemovedVariables($isPreview, $variables) { $method = $isPreview ? 'environment_variables_preview' : 'environment_variables'; + + // Get all environment variables that will be deleted + $variablesToDelete = $this->resource->$method()->whereNotIn('key', array_keys($variables))->get(); + + // If there are no variables to delete, return 0 + if ($variablesToDelete->isEmpty()) { + return 0; + } + + // Check for system variables that shouldn't be deleted + foreach ($variablesToDelete as $envVar) { + if ($this->isProtectedEnvironmentVariable($envVar->key)) { + $this->dispatch('error', "Cannot delete system environment variable '{$envVar->key}'."); + + return 0; + } + } + + // Check if any of these variables are used in Docker Compose + if ($this->resource->type() === 'service' || $this->resource->build_pack === 'dockercompose') { + foreach ($variablesToDelete as $envVar) { + [$isUsed, $reason] = $this->isEnvironmentVariableUsedInDockerCompose($envVar->key, $this->resource->docker_compose); + + if ($isUsed) { + $this->dispatch('error', "Cannot delete environment variable '{$envVar->key}'

Please remove it from the Docker Compose file first."); + + return 0; + } + } + } + + // If we get here, no variables are used in Docker Compose, so we can delete them $this->resource->$method()->whereNotIn('key', array_keys($variables))->delete(); + + return $variablesToDelete->count(); } private function updateOrCreateVariables($isPreview, $variables) { + $count = 0; foreach ($variables as $key => $value) { if (str($key)->startsWith('SERVICE_FQDN') || str($key)->startsWith('SERVICE_URL')) { continue; @@ -198,8 +276,12 @@ class All extends Component if ($found) { if (! $found->is_shown_once && ! $found->is_multiline) { - $found->value = $value; - $found->save(); + // Only count as a change if the value actually changed + if ($found->value !== $value) { + $found->value = $value; + $found->save(); + $count++; + } } } else { $environment = new EnvironmentVariable; @@ -212,8 +294,11 @@ class All extends Component $environment->resourceable_type = $this->resource->getMorphClass(); $environment->save(); + $count++; } } + + return $count; } public function refreshEnvs() diff --git a/app/Livewire/Project/Shared/EnvironmentVariable/Show.php b/app/Livewire/Project/Shared/EnvironmentVariable/Show.php index 3a7d0faa5..d58151abf 100644 --- a/app/Livewire/Project/Shared/EnvironmentVariable/Show.php +++ b/app/Livewire/Project/Shared/EnvironmentVariable/Show.php @@ -4,10 +4,13 @@ namespace App\Livewire\Project\Shared\EnvironmentVariable; use App\Models\EnvironmentVariable as ModelsEnvironmentVariable; use App\Models\SharedEnvironmentVariable; +use App\Traits\EnvironmentVariableProtection; use Livewire\Component; class Show extends Component { + use EnvironmentVariableProtection; + public $parameters; public ModelsEnvironmentVariable|SharedEnvironmentVariable $env; @@ -40,6 +43,8 @@ class Show extends Component public bool $is_really_required = false; + public bool $is_redis_credential = false; + protected $listeners = [ 'refreshEnvs' => 'refresh', 'refresh', @@ -65,7 +70,9 @@ class Show extends Component } $this->parameters = get_route_parameters(); $this->checkEnvs(); - + if ($this->type === 'standalone-redis' && ($this->env->key === 'REDIS_PASSWORD' || $this->env->key === 'REDIS_USERNAME')) { + $this->is_redis_credential = true; + } } public function refresh() @@ -171,6 +178,24 @@ class Show extends Component public function delete() { try { + // Check if the variable is protected + if ($this->isProtectedEnvironmentVariable($this->env->key)) { + $this->dispatch('error', "Cannot delete system environment variable '{$this->env->key}'."); + + return; + } + + // Check if the variable is used in Docker Compose + if ($this->type === 'service' || $this->type === 'application' && $this->env->resource()?->docker_compose) { + [$isUsed, $reason] = $this->isEnvironmentVariableUsedInDockerCompose($this->env->key, $this->env->resource()?->docker_compose); + + if ($isUsed) { + $this->dispatch('error', "Cannot delete environment variable '{$this->env->key}'

Please remove it from the Docker Compose file first."); + + return; + } + } + $this->env->delete(); $this->dispatch('environmentVariableDeleted'); $this->dispatch('success', 'Environment variable deleted successfully.'); diff --git a/app/Livewire/Server/Advanced.php b/app/Livewire/Server/Advanced.php index b269c916f..b2b8b1518 100644 --- a/app/Livewire/Server/Advanced.php +++ b/app/Livewire/Server/Advanced.php @@ -2,7 +2,11 @@ namespace App\Livewire\Server; +use App\Helpers\SslHelper; +use App\Jobs\RegenerateSslCertJob; use App\Models\Server; +use App\Models\SslCertificate; +use Carbon\Carbon; use Livewire\Attributes\Validate; use Livewire\Component; @@ -10,6 +14,14 @@ class Advanced extends Component { public Server $server; + public ?SslCertificate $caCertificate = null; + + public $showCertificate = false; + + public $certificateContent = ''; + + public ?Carbon $certificateValidUntil = null; + public array $parameters = []; #[Validate(['string'])] @@ -30,11 +42,99 @@ class Advanced extends Component $this->server = Server::ownedByCurrentTeam()->whereUuid($server_uuid)->firstOrFail(); $this->parameters = get_route_parameters(); $this->syncData(); + $this->loadCaCertificate(); } catch (\Throwable) { return redirect()->route('server.index'); } } + public function loadCaCertificate() + { + $this->caCertificate = SslCertificate::where('server_id', $this->server->id)->where('is_ca_certificate', true)->first(); + + if ($this->caCertificate) { + $this->certificateContent = $this->caCertificate->ssl_certificate; + $this->certificateValidUntil = $this->caCertificate->valid_until; + } + } + + public function toggleCertificate() + { + $this->showCertificate = ! $this->showCertificate; + } + + public function saveCaCertificate() + { + try { + if (! $this->certificateContent) { + throw new \Exception('Certificate content cannot be empty.'); + } + + if (! openssl_x509_read($this->certificateContent)) { + throw new \Exception('Invalid certificate format.'); + } + + if ($this->caCertificate) { + $this->caCertificate->ssl_certificate = $this->certificateContent; + $this->caCertificate->save(); + + $this->loadCaCertificate(); + + $this->writeCertificateToServer(); + + dispatch(new RegenerateSslCertJob( + server_id: $this->server->id, + force_regeneration: true + )); + } + $this->dispatch('success', 'CA Certificate saved successfully.'); + } catch (\Throwable $e) { + return handleError($e, $this); + } + } + + public function regenerateCaCertificate() + { + try { + SslHelper::generateSslCertificate( + commonName: 'Coolify CA Certificate', + serverId: $this->server->id, + isCaCertificate: true, + validityDays: 10 * 365 + ); + + $this->loadCaCertificate(); + + $this->writeCertificateToServer(); + + dispatch(new RegenerateSslCertJob( + server_id: $this->server->id, + force_regeneration: true + )); + + $this->loadCaCertificate(); + $this->dispatch('success', 'CA Certificate regenerated successfully.'); + } catch (\Throwable $e) { + return handleError($e, $this); + } + } + + private function writeCertificateToServer() + { + $caCertPath = config('constants.coolify.base_config_path').'/ssl/'; + + $commands = collect([ + "mkdir -p $caCertPath", + "chown -R 9999:root $caCertPath", + "chmod -R 700 $caCertPath", + "rm -rf $caCertPath/coolify-ca.crt", + "echo '{$this->certificateContent}' > $caCertPath/coolify-ca.crt", + "chmod 644 $caCertPath/coolify-ca.crt", + ]); + + remote_process($commands, $this->server); + } + public function syncData(bool $toModel = false) { if ($toModel) { diff --git a/app/Livewire/Server/Proxy/Deploy.php b/app/Livewire/Server/Proxy/Deploy.php index f823ff3d4..48eede4e5 100644 --- a/app/Livewire/Server/Proxy/Deploy.php +++ b/app/Livewire/Server/Proxy/Deploy.php @@ -4,11 +4,10 @@ namespace App\Livewire\Server\Proxy; use App\Actions\Proxy\CheckProxy; use App\Actions\Proxy\StartProxy; +use App\Actions\Proxy\StopProxy; use App\Events\ProxyStatusChanged; +use App\Jobs\RestartProxyJob; use App\Models\Server; -use Carbon\Carbon; -use Illuminate\Process\InvokedProcess; -use Illuminate\Support\Facades\Process; use Livewire\Component; class Deploy extends Component @@ -65,7 +64,7 @@ class Deploy extends Component public function restart() { try { - $this->stop(); + RestartProxyJob::dispatch($this->server); $this->dispatch('checkProxy'); } catch (\Throwable $e) { return handleError($e, $this); @@ -98,43 +97,10 @@ class Deploy extends Component public function stop(bool $forceStop = true) { try { - $containerName = $this->server->isSwarm() ? 'coolify-proxy_traefik' : 'coolify-proxy'; - $timeout = 30; - - $process = $this->stopContainer($containerName, $timeout); - - $startTime = Carbon::now()->getTimestamp(); - while ($process->running()) { - if (Carbon::now()->getTimestamp() - $startTime >= $timeout) { - $this->forceStopContainer($containerName); - break; - } - usleep(100000); - } - - $this->removeContainer($containerName); + StopProxy::run($this->server, $forceStop); + $this->dispatch('proxyStatusUpdated'); } catch (\Throwable $e) { return handleError($e, $this); - } finally { - $this->server->proxy->force_stop = $forceStop; - $this->server->proxy->status = 'exited'; - $this->server->save(); - $this->dispatch('proxyStatusUpdated'); } } - - private function stopContainer(string $containerName, int $timeout): InvokedProcess - { - return Process::timeout($timeout)->start("docker stop --time=$timeout $containerName"); - } - - private function forceStopContainer(string $containerName) - { - instant_remote_process(["docker kill $containerName"], $this->server, throwError: false); - } - - private function removeContainer(string $containerName) - { - instant_remote_process(["docker rm -f $containerName"], $this->server, throwError: false); - } } diff --git a/app/Livewire/SettingsBackup.php b/app/Livewire/SettingsBackup.php index 1b0599ffe..bb5ed0aa8 100644 --- a/app/Livewire/SettingsBackup.php +++ b/app/Livewire/SettingsBackup.php @@ -15,6 +15,8 @@ class SettingsBackup extends Component { public InstanceSettings $settings; + public Server $server; + public ?StandalonePostgresql $database = null; public ScheduledDatabaseBackup|null|array $backup = []; @@ -46,6 +48,7 @@ class SettingsBackup extends Component return redirect()->route('dashboard'); } else { $settings = instanceSettings(); + $this->server = Server::findOrFail(0); $this->database = StandalonePostgresql::whereName('coolify-db')->first(); $s3s = S3Storage::whereTeamId(0)->get() ?? []; if ($this->database) { @@ -60,6 +63,10 @@ class SettingsBackup extends Component $this->database->save(); } $this->backup = $this->database->scheduledBackups->first(); + if ($this->backup && ! $this->server->isFunctional()) { + $this->backup->enabled = false; + $this->backup->save(); + } $this->executions = $this->backup->executions; } $this->settings = $settings; diff --git a/app/Livewire/SettingsEmail.php b/app/Livewire/SettingsEmail.php index 4205594a5..b2394d7b0 100644 --- a/app/Livewire/SettingsEmail.php +++ b/app/Livewire/SettingsEmail.php @@ -4,7 +4,7 @@ namespace App\Livewire; use App\Models\InstanceSettings; use App\Models\Team; -use App\Notifications\Test; +use App\Notifications\TransactionalEmails\Test; use Illuminate\Support\Facades\RateLimiter; use Livewire\Attributes\Locked; use Livewire\Attributes\Validate; @@ -225,7 +225,7 @@ class SettingsEmail extends Component 'test-email:'.$this->team->id, $perMinute = 0, function () { - $this->team?->notify(new Test($this->testEmailAddress, 'email')); + $this->team?->notify(new Test($this->testEmailAddress)); $this->dispatch('success', 'Test Email sent.'); }, $decaySeconds = 10, @@ -235,7 +235,7 @@ class SettingsEmail extends Component throw new \Exception('Too many messages sent!'); } } catch (\Throwable $e) { - return handleError($e); + return handleError($e, $this); } } } diff --git a/app/Models/Application.php b/app/Models/Application.php index e8ad4c1b4..d2c2d97a7 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -1068,7 +1068,6 @@ class Application extends BaseModel if ($this->deploymentType() === 'other') { $fullRepoUrl = $customRepository; $base_command = "{$base_command} {$customRepository}"; - $base_command = $this->setGitImportSettings($deployment_uuid, $base_command, public: true); if ($exec_in_docker) { $commands->push(executeInDocker($deployment_uuid, $base_command)); @@ -1511,6 +1510,7 @@ class Application extends BaseModel public function parseHealthcheckFromDockerfile($dockerfile, bool $isInit = false) { + $dockerfile = str($dockerfile)->trim()->explode("\n"); if (str($dockerfile)->contains('HEALTHCHECK') && ($this->isHealthcheckDisabled() || $isInit)) { $healthcheckCommand = null; $lines = $dockerfile->toArray(); @@ -1530,27 +1530,24 @@ class Application extends BaseModel } } if (str($healthcheckCommand)->isNotEmpty()) { - $interval = str($healthcheckCommand)->match('/--interval=(\d+)/'); - $timeout = str($healthcheckCommand)->match('/--timeout=(\d+)/'); - $start_period = str($healthcheckCommand)->match('/--start-period=(\d+)/'); - $start_interval = str($healthcheckCommand)->match('/--start-interval=(\d+)/'); + $interval = str($healthcheckCommand)->match('/--interval=([0-9]+[a-zµ]*)/'); + $timeout = str($healthcheckCommand)->match('/--timeout=([0-9]+[a-zµ]*)/'); + $start_period = str($healthcheckCommand)->match('/--start-period=([0-9]+[a-zµ]*)/'); $retries = str($healthcheckCommand)->match('/--retries=(\d+)/'); + if ($interval->isNotEmpty()) { - $this->health_check_interval = $interval->toInteger(); + $this->health_check_interval = parseDockerfileInterval($interval); } if ($timeout->isNotEmpty()) { - $this->health_check_timeout = $timeout->toInteger(); + $this->health_check_timeout = parseDockerfileInterval($timeout); } if ($start_period->isNotEmpty()) { - $this->health_check_start_period = $start_period->toInteger(); + $this->health_check_start_period = parseDockerfileInterval($start_period); } - // if ($start_interval) { - // $this->health_check_start_interval = $start_interval->value(); - // } if ($retries->isNotEmpty()) { $this->health_check_retries = $retries->toInteger(); } - if ($interval || $timeout || $start_period || $start_interval || $retries) { + if ($interval || $timeout || $start_period || $retries) { $this->custom_healthcheck_found = true; $this->save(); } diff --git a/app/Models/DiscordNotificationSettings.php b/app/Models/DiscordNotificationSettings.php index 619393ddc..1ba16ccd8 100644 --- a/app/Models/DiscordNotificationSettings.php +++ b/app/Models/DiscordNotificationSettings.php @@ -28,6 +28,7 @@ class DiscordNotificationSettings extends Model 'server_disk_usage_discord_notifications', 'server_reachable_discord_notifications', 'server_unreachable_discord_notifications', + 'discord_ping_enabled', ]; protected $casts = [ @@ -45,6 +46,7 @@ class DiscordNotificationSettings extends Model 'server_disk_usage_discord_notifications' => 'boolean', 'server_reachable_discord_notifications' => 'boolean', 'server_unreachable_discord_notifications' => 'boolean', + 'discord_ping_enabled' => 'boolean', ]; public function team() @@ -56,4 +58,9 @@ class DiscordNotificationSettings extends Model { return $this->discord_enabled; } + + public function isPingEnabled() + { + return $this->discord_ping_enabled; + } } diff --git a/app/Models/EmailNotificationSettings.php b/app/Models/EmailNotificationSettings.php index ae118986f..445987619 100644 --- a/app/Models/EmailNotificationSettings.php +++ b/app/Models/EmailNotificationSettings.php @@ -70,10 +70,6 @@ class EmailNotificationSettings extends Model public function isEnabled() { - if (isCloud()) { - return true; - } - return $this->smtp_enabled || $this->resend_enabled || $this->use_instance_email_settings; } } diff --git a/app/Models/InstanceSettings.php b/app/Models/InstanceSettings.php index 5b89bb401..ac95bb8a9 100644 --- a/app/Models/InstanceSettings.php +++ b/app/Models/InstanceSettings.php @@ -3,16 +3,12 @@ namespace App\Models; use App\Jobs\PullHelperImageJob; -use App\Notifications\Channels\SendsEmail; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Model; -use Illuminate\Notifications\Notifiable; use Spatie\Url\Url; -class InstanceSettings extends Model implements SendsEmail +class InstanceSettings extends Model { - use Notifiable; - protected $guarded = []; protected $casts = [ @@ -92,15 +88,15 @@ class InstanceSettings extends Model implements SendsEmail return InstanceSettings::findOrFail(0); } - public function getRecipients($notification) - { - $recipients = data_get($notification, 'emails', null); - if (is_null($recipients) || $recipients === '') { - return []; - } + // public function getRecipients($notification) + // { + // $recipients = data_get($notification, 'emails', null); + // if (is_null($recipients) || $recipients === '') { + // return []; + // } - return explode(',', $recipients); - } + // return explode(',', $recipients); + // } public function getTitleDisplayName(): string { diff --git a/app/Models/LocalFileVolume.php b/app/Models/LocalFileVolume.php index d96f7125e..c56cd7694 100644 --- a/app/Models/LocalFileVolume.php +++ b/app/Models/LocalFileVolume.php @@ -8,6 +8,13 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; class LocalFileVolume extends BaseModel { + protected $casts = [ + // 'fs_path' => 'encrypted', + // 'mount_path' => 'encrypted', + 'content' => 'encrypted', + 'is_directory' => 'boolean', + ]; + use HasFactory; protected $guarded = []; @@ -169,4 +176,19 @@ class LocalFileVolume extends BaseModel return instant_remote_process($commands, $server); } + + // Accessor for convenient access + protected function plainMountPath(): Attribute + { + return Attribute::make( + get: fn () => $this->mount_path, + set: fn ($value) => $this->mount_path = $value + ); + } + + // Scope for searching + public function scopeWherePlainMountPath($query, $path) + { + return $query->get()->where('plain_mount_path', $path); + } } diff --git a/app/Models/LocalPersistentVolume.php b/app/Models/LocalPersistentVolume.php index 68e476365..b5dfd9663 100644 --- a/app/Models/LocalPersistentVolume.php +++ b/app/Models/LocalPersistentVolume.php @@ -24,11 +24,6 @@ class LocalPersistentVolume extends Model return $this->morphTo('resource'); } - public function standalone_postgresql() - { - return $this->morphTo('resource'); - } - protected function name(): Attribute { return Attribute::make( diff --git a/app/Models/Server.php b/app/Models/Server.php index 828500c40..60d0da3ed 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -7,9 +7,12 @@ use App\Actions\Server\InstallDocker; use App\Actions\Server\StartSentinel; use App\Enums\ProxyTypes; use App\Events\ServerReachabilityChanged; +use App\Helpers\SslHelper; use App\Jobs\CheckAndStartSentinelJob; +use App\Jobs\RegenerateSslCertJob; use App\Notifications\Server\Reachable; use App\Notifications\Server\Unreachable; +use App\Services\ConfigurationRepository; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; @@ -484,7 +487,7 @@ $schema://$host { $base_path = config('constants.coolify.base_config_path'); $proxyType = $this->proxyType(); $proxy_path = "$base_path/proxy"; - // TODO: should use /traefik for already exisiting configurations? + // TODO: should use /traefik for already existing configurations? // Should move everything except /caddy and /nginx to /traefik // The code needs to be modified as well, so maybe it does not worth it if ($proxyType === ProxyTypes::TRAEFIK->value) { @@ -543,7 +546,7 @@ $schema://$host { $this->settings->save(); $sshKeyFileLocation = "id.root@{$this->uuid}"; Storage::disk('ssh-keys')->delete($sshKeyFileLocation); - Storage::disk('ssh-mux')->delete($this->muxFilename()); + $this->disableSshMux(); } public function sentinelHeartbeat(bool $isReset = false) @@ -922,7 +925,7 @@ $schema://$host { public function isFunctional() { - $isFunctional = $this->settings->is_reachable && $this->settings->is_usable && $this->settings->force_disabled === false && $this->ip !== '1.2.3.4'; + $isFunctional = data_get($this->settings, 'is_reachable') && data_get($this->settings, 'is_usable') && data_get($this->settings, 'force_disabled') === false && $this->ip !== '1.2.3.4'; if ($isFunctional === false) { Storage::disk('ssh-mux')->delete($this->muxFilename()); @@ -1103,7 +1106,7 @@ $schema://$host { public function validateConnection(bool $justCheckingNewKey = false) { - config()->set('constants.ssh.mux_enabled', false); + $this->disableSshMux(); if ($this->skipServer()) { return ['uptime' => false, 'error' => 'Server skipped.']; @@ -1330,4 +1333,47 @@ $schema://$host { $this->databases()->count() == 0 && $this->services()->count() == 0; } + + private function disableSshMux(): void + { + $configRepository = app(ConfigurationRepository::class); + $configRepository->disableSshMux(); + } + + public function generateCaCertificate() + { + try { + ray('Generating CA certificate for server', $this->id); + SslHelper::generateSslCertificate( + commonName: 'Coolify CA Certificate', + serverId: $this->id, + isCaCertificate: true, + validityDays: 10 * 365 + ); + $caCertificate = SslCertificate::where('server_id', $this->id)->where('is_ca_certificate', true)->first(); + ray('CA certificate generated', $caCertificate); + if ($caCertificate) { + $certificateContent = $caCertificate->ssl_certificate; + $caCertPath = config('constants.coolify.base_config_path').'/ssl/'; + + $commands = collect([ + "mkdir -p $caCertPath", + "chown -R 9999:root $caCertPath", + "chmod -R 700 $caCertPath", + "rm -rf $caCertPath/coolify-ca.crt", + "echo '{$certificateContent}' > $caCertPath/coolify-ca.crt", + "chmod 644 $caCertPath/coolify-ca.crt", + ]); + + instant_remote_process($commands, $this, false); + + dispatch(new RegenerateSslCertJob( + server_id: $this->id, + force_regeneration: true + )); + } + } catch (\Throwable $e) { + return handleError($e); + } + } } diff --git a/app/Models/Service.php b/app/Models/Service.php index 25e6b92ea..23ddb5923 100644 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -50,6 +50,11 @@ class Service extends BaseModel protected static function booted() { + static::creating(function ($service) { + if (blank($service->name)) { + $service->name = 'service-'.(new Cuid2); + } + }); static::created(function ($service) { $service->compose_parsing_version = self::$parserVersion; $service->save(); diff --git a/app/Models/SslCertificate.php b/app/Models/SslCertificate.php new file mode 100644 index 000000000..eb2175d44 --- /dev/null +++ b/app/Models/SslCertificate.php @@ -0,0 +1,49 @@ + 'encrypted', + 'ssl_private_key' => 'encrypted', + 'subject_alternative_names' => 'array', + 'valid_until' => 'datetime', + ]; + + public function application() + { + return $this->morphTo('resource'); + } + + public function service() + { + return $this->morphTo('resource'); + } + + public function database() + { + return $this->morphTo('resource'); + } + + public function server() + { + return $this->belongsTo(Server::class); + } +} diff --git a/app/Models/StandaloneClickhouse.php b/app/Models/StandaloneClickhouse.php index 60198115d..bc1f9b4b3 100644 --- a/app/Models/StandaloneClickhouse.php +++ b/app/Models/StandaloneClickhouse.php @@ -163,6 +163,11 @@ class StandaloneClickhouse extends BaseModel return data_get($this, 'environment.project'); } + public function sslCertificates() + { + return $this->morphMany(SslCertificate::class, 'resource'); + } + public function link() { if (data_get($this, 'environment.project.uuid')) { @@ -218,7 +223,12 @@ class StandaloneClickhouse extends BaseModel protected function internalDbUrl(): Attribute { return new Attribute( - get: fn () => "clickhouse://{$this->clickhouse_admin_user}:{$this->clickhouse_admin_password}@{$this->uuid}:9000/{$this->clickhouse_db}", + get: function () { + $encodedUser = rawurlencode($this->clickhouse_admin_user); + $encodedPass = rawurlencode($this->clickhouse_admin_password); + + return "clickhouse://{$encodedUser}:{$encodedPass}@{$this->uuid}:9000/{$this->clickhouse_db}"; + }, ); } @@ -227,7 +237,10 @@ class StandaloneClickhouse extends BaseModel return new Attribute( get: function () { if ($this->is_public && $this->public_port) { - return "clickhouse://{$this->clickhouse_admin_user}:{$this->clickhouse_admin_password}@{$this->destination->server->getIp}:{$this->public_port}/{$this->clickhouse_db}"; + $encodedUser = rawurlencode($this->clickhouse_admin_user); + $encodedPass = rawurlencode($this->clickhouse_admin_password); + + return "clickhouse://{$encodedUser}:{$encodedPass}@{$this->destination->server->getIp}:{$this->public_port}/{$this->clickhouse_db}"; } return null; diff --git a/app/Models/StandaloneDragonfly.php b/app/Models/StandaloneDragonfly.php index 3c1127d8d..a14c5e378 100644 --- a/app/Models/StandaloneDragonfly.php +++ b/app/Models/StandaloneDragonfly.php @@ -168,6 +168,11 @@ class StandaloneDragonfly extends BaseModel return data_get($this, 'environment.project.team'); } + public function sslCertificates() + { + return $this->morphMany(SslCertificate::class, 'resource'); + } + public function link() { if (data_get($this, 'environment.project.uuid')) { @@ -218,7 +223,18 @@ class StandaloneDragonfly extends BaseModel protected function internalDbUrl(): Attribute { return new Attribute( - get: fn () => "redis://:{$this->dragonfly_password}@{$this->uuid}:6379/0", + get: function () { + $scheme = $this->enable_ssl ? 'rediss' : 'redis'; + $port = $this->enable_ssl ? 6380 : 6379; + $encodedPass = rawurlencode($this->dragonfly_password); + $url = "{$scheme}://:{$encodedPass}@{$this->uuid}:{$port}/0"; + + if ($this->enable_ssl && $this->ssl_mode === 'verify-ca') { + $url .= '?cacert=/etc/ssl/certs/coolify-ca.crt'; + } + + return $url; + } ); } @@ -227,7 +243,15 @@ class StandaloneDragonfly extends BaseModel return new Attribute( get: function () { if ($this->is_public && $this->public_port) { - return "redis://:{$this->dragonfly_password}@{$this->destination->server->getIp}:{$this->public_port}/0"; + $scheme = $this->enable_ssl ? 'rediss' : 'redis'; + $encodedPass = rawurlencode($this->dragonfly_password); + $url = "{$scheme}://:{$encodedPass}@{$this->destination->server->getIp}:{$this->public_port}/0"; + + if ($this->enable_ssl && $this->ssl_mode === 'verify-ca') { + $url .= '?cacert=/etc/ssl/certs/coolify-ca.crt'; + } + + return $url; } return null; diff --git a/app/Models/StandaloneKeydb.php b/app/Models/StandaloneKeydb.php index ebf1c22e9..2d3aea755 100644 --- a/app/Models/StandaloneKeydb.php +++ b/app/Models/StandaloneKeydb.php @@ -168,6 +168,11 @@ class StandaloneKeydb extends BaseModel return data_get($this, 'environment.project.team'); } + public function sslCertificates() + { + return $this->morphMany(SslCertificate::class, 'resource'); + } + public function link() { if (data_get($this, 'environment.project.uuid')) { @@ -218,7 +223,18 @@ class StandaloneKeydb extends BaseModel protected function internalDbUrl(): Attribute { return new Attribute( - get: fn () => "redis://:{$this->keydb_password}@{$this->uuid}:6379/0", + get: function () { + $scheme = $this->enable_ssl ? 'rediss' : 'redis'; + $port = $this->enable_ssl ? 6380 : 6379; + $encodedPass = rawurlencode($this->keydb_password); + $url = "{$scheme}://:{$encodedPass}@{$this->uuid}:{$port}/0"; + + if ($this->enable_ssl && $this->ssl_mode === 'verify-ca') { + $url .= '?cacert=/etc/ssl/certs/coolify-ca.crt'; + } + + return $url; + } ); } @@ -227,7 +243,15 @@ 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"; + $scheme = $this->enable_ssl ? 'rediss' : 'redis'; + $encodedPass = rawurlencode($this->keydb_password); + $url = "{$scheme}://:{$encodedPass}@{$this->destination->server->getIp}:{$this->public_port}/0"; + + if ($this->enable_ssl && $this->ssl_mode === 'verify-ca') { + $url .= '?cacert=/etc/ssl/certs/coolify-ca.crt'; + } + + return $url; } return null; diff --git a/app/Models/StandaloneMariadb.php b/app/Models/StandaloneMariadb.php index 004ead4d9..7549ace3e 100644 --- a/app/Models/StandaloneMariadb.php +++ b/app/Models/StandaloneMariadb.php @@ -218,7 +218,12 @@ class StandaloneMariadb extends BaseModel protected function internalDbUrl(): Attribute { return new Attribute( - get: fn () => "mysql://{$this->mariadb_user}:{$this->mariadb_password}@{$this->uuid}:3306/{$this->mariadb_database}", + get: function () { + $encodedUser = rawurlencode($this->mariadb_user); + $encodedPass = rawurlencode($this->mariadb_password); + + return "mysql://{$encodedUser}:{$encodedPass}@{$this->uuid}:3306/{$this->mariadb_database}"; + }, ); } @@ -227,7 +232,10 @@ class StandaloneMariadb extends BaseModel return new Attribute( get: function () { if ($this->is_public && $this->public_port) { - return "mysql://{$this->mariadb_user}:{$this->mariadb_password}@{$this->destination->server->getIp}:{$this->public_port}/{$this->mariadb_database}"; + $encodedUser = rawurlencode($this->mariadb_user); + $encodedPass = rawurlencode($this->mariadb_password); + + return "mysql://{$encodedUser}:{$encodedPass}@{$this->destination->server->getIp}:{$this->public_port}/{$this->mariadb_database}"; } return null; @@ -271,6 +279,11 @@ class StandaloneMariadb extends BaseModel return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); } + public function sslCertificates() + { + return $this->morphMany(SslCertificate::class, 'resource'); + } + public function getCpuMetrics(int $mins = 5) { $server = $this->destination->server; diff --git a/app/Models/StandaloneMongodb.php b/app/Models/StandaloneMongodb.php index aba0f6123..3092216bd 100644 --- a/app/Models/StandaloneMongodb.php +++ b/app/Models/StandaloneMongodb.php @@ -177,6 +177,11 @@ class StandaloneMongodb extends BaseModel return data_get($this, 'is_log_drain_enabled', false); } + public function sslCertificates() + { + return $this->morphMany(SslCertificate::class, 'resource'); + } + public function link() { if (data_get($this, 'environment.project.uuid')) { @@ -238,7 +243,19 @@ class StandaloneMongodb extends BaseModel protected function internalDbUrl(): Attribute { return new Attribute( - get: fn () => "mongodb://{$this->mongo_initdb_root_username}:{$this->mongo_initdb_root_password}@{$this->uuid}:27017/?directConnection=true", + get: function () { + $encodedUser = rawurlencode($this->mongo_initdb_root_username); + $encodedPass = rawurlencode($this->mongo_initdb_root_password); + $url = "mongodb://{$encodedUser}:{$encodedPass}@{$this->uuid}:27017/?directConnection=true"; + if ($this->enable_ssl) { + $url .= '&tls=true&tlsCAFile=/etc/mongo/certs/ca.pem'; + if (in_array($this->ssl_mode, ['verify-full'])) { + $url .= '&tlsCertificateKeyFile=/etc/mongo/certs/server.pem'; + } + } + + return $url; + }, ); } @@ -247,7 +264,17 @@ class StandaloneMongodb extends BaseModel return new Attribute( get: function () { if ($this->is_public && $this->public_port) { - return "mongodb://{$this->mongo_initdb_root_username}:{$this->mongo_initdb_root_password}@{$this->destination->server->getIp}:{$this->public_port}/?directConnection=true"; + $encodedUser = rawurlencode($this->mongo_initdb_root_username); + $encodedPass = rawurlencode($this->mongo_initdb_root_password); + $url = "mongodb://{$encodedUser}:{$encodedPass}@{$this->destination->server->getIp}:{$this->public_port}/?directConnection=true"; + if ($this->enable_ssl) { + $url .= '&tls=true&tlsCAFile=/etc/mongo/certs/ca.pem'; + if (in_array($this->ssl_mode, ['verify-full'])) { + $url .= '&tlsCertificateKeyFile=/etc/mongo/certs/server.pem'; + } + } + + return $url; } return null; diff --git a/app/Models/StandaloneMysql.php b/app/Models/StandaloneMysql.php index 9ae0fdcae..dbb5b1ae6 100644 --- a/app/Models/StandaloneMysql.php +++ b/app/Models/StandaloneMysql.php @@ -169,6 +169,11 @@ class StandaloneMysql extends BaseModel return data_get($this, 'environment.project.team'); } + public function sslCertificates() + { + return $this->morphMany(SslCertificate::class, 'resource'); + } + public function link() { if (data_get($this, 'environment.project.uuid')) { @@ -219,7 +224,19 @@ class StandaloneMysql extends BaseModel protected function internalDbUrl(): Attribute { return new Attribute( - get: fn () => "mysql://{$this->mysql_user}:{$this->mysql_password}@{$this->uuid}:3306/{$this->mysql_database}", + get: function () { + $encodedUser = rawurlencode($this->mysql_user); + $encodedPass = rawurlencode($this->mysql_password); + $url = "mysql://{$encodedUser}:{$encodedPass}@{$this->uuid}:3306/{$this->mysql_database}"; + if ($this->enable_ssl) { + $url .= "?ssl-mode={$this->ssl_mode}"; + if (in_array($this->ssl_mode, ['VERIFY_CA', 'VERIFY_IDENTITY'])) { + $url .= '&ssl-ca=/etc/ssl/certs/coolify-ca.crt'; + } + } + + return $url; + }, ); } @@ -228,7 +245,17 @@ class StandaloneMysql extends BaseModel return new Attribute( get: function () { if ($this->is_public && $this->public_port) { - return "mysql://{$this->mysql_user}:{$this->mysql_password}@{$this->destination->server->getIp}:{$this->public_port}/{$this->mysql_database}"; + $encodedUser = rawurlencode($this->mysql_user); + $encodedPass = rawurlencode($this->mysql_password); + $url = "mysql://{$encodedUser}:{$encodedPass}@{$this->destination->server->getIp}:{$this->public_port}/{$this->mysql_database}"; + if ($this->enable_ssl) { + $url .= "?ssl-mode={$this->ssl_mode}"; + if (in_array($this->ssl_mode, ['VERIFY_CA', 'VERIFY_IDENTITY'])) { + $url .= '&ssl-ca=/etc/ssl/certs/coolify-ca.crt'; + } + } + + return $url; } return null; diff --git a/app/Models/StandalonePostgresql.php b/app/Models/StandalonePostgresql.php index dd92ae7c9..a74d567a0 100644 --- a/app/Models/StandalonePostgresql.php +++ b/app/Models/StandalonePostgresql.php @@ -219,7 +219,19 @@ class StandalonePostgresql extends BaseModel protected function internalDbUrl(): Attribute { return new Attribute( - get: fn () => "postgres://{$this->postgres_user}:{$this->postgres_password}@{$this->uuid}:5432/{$this->postgres_db}", + get: function () { + $encodedUser = rawurlencode($this->postgres_user); + $encodedPass = rawurlencode($this->postgres_password); + $url = "postgres://{$encodedUser}:{$encodedPass}@{$this->uuid}:5432/{$this->postgres_db}"; + if ($this->enable_ssl) { + $url .= "?sslmode={$this->ssl_mode}"; + if (in_array($this->ssl_mode, ['verify-ca', 'verify-full'])) { + $url .= '&sslrootcert=/etc/ssl/certs/coolify-ca.crt'; + } + } + + return $url; + }, ); } @@ -228,7 +240,17 @@ class StandalonePostgresql extends BaseModel return new Attribute( get: function () { if ($this->is_public && $this->public_port) { - return "postgres://{$this->postgres_user}:{$this->postgres_password}@{$this->destination->server->getIp}:{$this->public_port}/{$this->postgres_db}"; + $encodedUser = rawurlencode($this->postgres_user); + $encodedPass = rawurlencode($this->postgres_password); + $url = "postgres://{$encodedUser}:{$encodedPass}@{$this->destination->server->getIp}:{$this->public_port}/{$this->postgres_db}"; + if ($this->enable_ssl) { + $url .= "?sslmode={$this->ssl_mode}"; + if (in_array($this->ssl_mode, ['verify-ca', 'verify-full'])) { + $url .= '&sslrootcert=/etc/ssl/certs/coolify-ca.crt'; + } + } + + return $url; } return null; @@ -241,11 +263,21 @@ class StandalonePostgresql extends BaseModel return $this->belongsTo(Environment::class); } + public function persistentStorages() + { + return $this->morphMany(LocalPersistentVolume::class, 'resource'); + } + public function fileStorages() { return $this->morphMany(LocalFileVolume::class, 'resource'); } + public function sslCertificates() + { + return $this->morphMany(SslCertificate::class, 'resource'); + } + public function destination() { return $this->morphTo(); @@ -256,16 +288,17 @@ class StandalonePostgresql extends BaseModel return $this->morphMany(EnvironmentVariable::class, 'resourceable'); } - public function persistentStorages() - { - return $this->morphMany(LocalPersistentVolume::class, 'resource'); - } - public function scheduledBackups() { return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); } + public function environment_variables() + { + return $this->morphMany(EnvironmentVariable::class, 'resourceable') + ->orderBy('key', 'asc'); + } + public function isBackupSolutionAvailable() { return true; @@ -314,10 +347,4 @@ class StandalonePostgresql extends BaseModel return $parsedCollection->toArray(); } - - public function environment_variables() - { - return $this->morphMany(EnvironmentVariable::class, 'resourceable') - ->orderBy('key', 'asc'); - } } diff --git a/app/Models/StandaloneRedis.php b/app/Models/StandaloneRedis.php index 6037364fe..fccbb24a5 100644 --- a/app/Models/StandaloneRedis.php +++ b/app/Models/StandaloneRedis.php @@ -170,6 +170,11 @@ class StandaloneRedis extends BaseModel return data_get($this, 'environment.project.team'); } + public function sslCertificates() + { + return $this->morphMany(SslCertificate::class, 'resource'); + } + public function link() { if (data_get($this, 'environment.project.uuid')) { @@ -222,9 +227,17 @@ class StandaloneRedis extends BaseModel return new Attribute( get: function () { $redis_version = $this->getRedisVersion(); - $username_part = version_compare($redis_version, '6.0', '>=') ? "{$this->redis_username}:" : ''; + $username_part = version_compare($redis_version, '6.0', '>=') ? rawurlencode($this->redis_username).':' : ''; + $encodedPass = rawurlencode($this->redis_password); + $scheme = $this->enable_ssl ? 'rediss' : 'redis'; + $port = $this->enable_ssl ? 6380 : 6379; + $url = "{$scheme}://{$username_part}{$encodedPass}@{$this->uuid}:{$port}/0"; - return "redis://{$username_part}{$this->redis_password}@{$this->uuid}:6379/0"; + if ($this->enable_ssl && $this->ssl_mode === 'verify-ca') { + $url .= '?cacert=/etc/ssl/certs/coolify-ca.crt'; + } + + return $url; } ); } @@ -235,9 +248,16 @@ class StandaloneRedis extends BaseModel get: function () { if ($this->is_public && $this->public_port) { $redis_version = $this->getRedisVersion(); - $username_part = version_compare($redis_version, '6.0', '>=') ? "{$this->redis_username}:" : ''; + $username_part = version_compare($redis_version, '6.0', '>=') ? rawurlencode($this->redis_username).':' : ''; + $encodedPass = rawurlencode($this->redis_password); + $scheme = $this->enable_ssl ? 'rediss' : 'redis'; + $url = "{$scheme}://{$username_part}{$encodedPass}@{$this->destination->server->getIp}:{$this->public_port}/0"; - return "redis://{$username_part}{$this->redis_password}@{$this->destination->server->getIp}:{$this->public_port}/0"; + if ($this->enable_ssl && $this->ssl_mode === 'verify-ca') { + $url .= '?cacert=/etc/ssl/certs/coolify-ca.crt'; + } + + return $url; } return null; diff --git a/app/Models/Team.php b/app/Models/Team.php index 6796b22ad..d36f8c1ab 100644 --- a/app/Models/Team.php +++ b/app/Models/Team.php @@ -163,14 +163,17 @@ class Team extends Model implements SendsDiscord, SendsEmail, SendsPushover, Sen ]; } - public function getRecipients($notification) + public function getRecipients(): array { - $recipients = data_get($notification, 'emails', null); - if (is_null($recipients)) { - return $this->members()->pluck('email')->toArray(); + $recipients = $this->members()->pluck('email')->toArray(); + $validatedEmails = array_filter($recipients, function ($email) { + return filter_var($email, FILTER_VALIDATE_EMAIL); + }); + if (is_null($validatedEmails)) { + return []; } - return explode(',', $recipients); + return array_values($validatedEmails); } public function isAnyNotificationEnabled() diff --git a/app/Models/User.php b/app/Models/User.php index 7c23631c3..f9515ad09 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -4,6 +4,7 @@ namespace App\Models; use App\Notifications\Channels\SendsEmail; use App\Notifications\TransactionalEmails\ResetPassword as TransactionalEmailsResetPassword; +use App\Traits\DeletesUserSessions; use DateTimeInterface; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Foundation\Auth\User as Authenticatable; @@ -37,7 +38,7 @@ use OpenApi\Attributes as OA; )] class User extends Authenticatable implements SendsEmail { - use HasApiTokens, HasFactory, Notifiable, TwoFactorAuthenticatable; + use DeletesUserSessions, HasApiTokens, HasFactory, Notifiable, TwoFactorAuthenticatable; protected $guarded = []; @@ -57,6 +58,7 @@ class User extends Authenticatable implements SendsEmail protected static function boot() { parent::boot(); + static::created(function (User $user) { $team = [ 'name' => $user->name."'s Team", @@ -114,9 +116,9 @@ class User extends Authenticatable implements SendsEmail return $this->belongsToMany(Team::class)->withPivot('role'); } - public function getRecipients($notification) + public function getRecipients(): array { - return $this->email; + return [$this->email]; } public function sendVerificationEmail() diff --git a/app/Notifications/Channels/DiscordChannel.php b/app/Notifications/Channels/DiscordChannel.php index 362006d8e..b4ba9bf8c 100644 --- a/app/Notifications/Channels/DiscordChannel.php +++ b/app/Notifications/Channels/DiscordChannel.php @@ -20,6 +20,10 @@ class DiscordChannel return; } + if (! $discordSettings->discord_ping_enabled) { + $message->isCritical = false; + } + SendMessageToDiscordJob::dispatch($message, $discordSettings->discord_webhook_url); } } diff --git a/app/Notifications/Channels/EmailChannel.php b/app/Notifications/Channels/EmailChannel.php index 215fae4ea..8a9a95107 100644 --- a/app/Notifications/Channels/EmailChannel.php +++ b/app/Notifications/Channels/EmailChannel.php @@ -2,89 +2,69 @@ namespace App\Notifications\Channels; -use Exception; -use Illuminate\Mail\Message; use Illuminate\Notifications\Notification; -use Illuminate\Support\Facades\Mail; +use Resend; class EmailChannel { + public function __construct() {} + public function send(SendsEmail $notifiable, Notification $notification): void { - try { - $this->bootConfigs($notifiable); - $recipients = $notifiable->getRecipients($notification); - if (count($recipients) === 0) { - throw new Exception('No email recipients found'); - } - - $mailMessage = $notification->toMail($notifiable); - Mail::send( - [], - [], - fn (Message $message) => $message - ->to($recipients) - ->subject($mailMessage->subject) - ->html((string) $mailMessage->render()) - ); - } catch (Exception $e) { - $error = $e->getMessage(); - if ($error === 'No email settings found.') { - throw $e; - } - $message = "EmailChannel error: {$e->getMessage()}. Failed to send email to:"; - if (isset($recipients)) { - $message .= implode(', ', $recipients); - } - if (isset($mailMessage)) { - $message .= " with subject: {$mailMessage->subject}"; - } - send_internal_notification($message); - throw $e; + $useInstanceEmailSettings = $notifiable->emailNotificationSettings->use_instance_email_settings; + $isTransactionalEmail = data_get($notification, 'isTransactionalEmail', false); + $customEmails = data_get($notification, 'emails', null); + if ($useInstanceEmailSettings || $isTransactionalEmail) { + $settings = instanceSettings(); + } else { + $settings = $notifiable->emailNotificationSettings; } - } - - private function bootConfigs($notifiable): void - { - $emailSettings = $notifiable->emailNotificationSettings; - - if ($emailSettings->use_instance_email_settings) { - $type = set_transanctional_email_settings(); - if (blank($type)) { - throw new Exception('No email settings found.'); - } - - return; + $isResendEnabled = $settings->resend_enabled; + $isSmtpEnabled = $settings->smtp_enabled; + if ($customEmails) { + $recipients = [$customEmails]; + } else { + $recipients = $notifiable->getRecipients(); } + $mailMessage = $notification->toMail($notifiable); - config()->set('mail.from.address', $emailSettings->smtp_from_address ?? 'test@example.com'); - config()->set('mail.from.name', $emailSettings->smtp_from_name ?? 'Test'); - - if ($emailSettings->resend_enabled) { - config()->set('mail.default', 'resend'); - config()->set('resend.api_key', $emailSettings->resend_api_key); - } - - if ($emailSettings->smtp_enabled) { - $encryption = match (strtolower($emailSettings->smtp_encryption)) { + if ($isResendEnabled) { + $resend = Resend::client($settings->resend_api_key); + $from = "{$settings->smtp_from_name} <{$settings->smtp_from_address}>"; + $resend->emails->send([ + 'from' => $from, + 'to' => $recipients, + 'subject' => $mailMessage->subject, + 'html' => (string) $mailMessage->render(), + ]); + } elseif ($isSmtpEnabled) { + $encryption = match (strtolower($settings->smtp_encryption)) { 'starttls' => null, 'tls' => 'tls', 'none' => null, default => null, }; - config()->set('mail.default', 'smtp'); - config()->set('mail.mailers.smtp', [ - 'transport' => 'smtp', - 'host' => $emailSettings->smtp_host, - 'port' => $emailSettings->smtp_port, - 'encryption' => $encryption, - 'username' => $emailSettings->smtp_username, - 'password' => $emailSettings->smtp_password, - 'timeout' => $emailSettings->smtp_timeout, - 'local_domain' => null, - 'auto_tls' => $emailSettings->smtp_encryption === 'none' ? '0' : '', // If encryption is "none", it will not try to upgrade to TLS via StartTLS to make sure it is unencrypted. - ]); + $transport = new \Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport( + $settings->smtp_host, + $settings->smtp_port, + $encryption + ); + $transport->setUsername($settings->smtp_username ?? ''); + $transport->setPassword($settings->smtp_password ?? ''); + + $mailer = new \Symfony\Component\Mailer\Mailer($transport); + + $fromEmail = $settings->smtp_from_address ?? 'noreply@localhost'; + $fromName = $settings->smtp_from_name ?? 'System'; + $from = new \Symfony\Component\Mime\Address($fromEmail, $fromName); + $email = (new \Symfony\Component\Mime\Email) + ->from($from) + ->to(...$recipients) + ->subject($mailMessage->subject) + ->html((string) $mailMessage->render()); + + $mailer->send($email); } } } diff --git a/app/Notifications/Channels/SendsEmail.php b/app/Notifications/Channels/SendsEmail.php index 3adc6d0a2..7039a3066 100644 --- a/app/Notifications/Channels/SendsEmail.php +++ b/app/Notifications/Channels/SendsEmail.php @@ -4,5 +4,5 @@ namespace App\Notifications\Channels; interface SendsEmail { - public function getRecipients($notification); + public function getRecipients(): array; } diff --git a/app/Notifications/Notification.php b/app/Notifications/Notification.php new file mode 100644 index 000000000..d37716a8b --- /dev/null +++ b/app/Notifications/Notification.php @@ -0,0 +1,22 @@ +onQueue('high'); + $this->resources = collect($resources); + + // Collect URLs for each resource + $this->resources->each(function ($resource) { + if (data_get($resource, 'environment.project.uuid')) { + $routeName = match ($resource->type()) { + 'application' => 'project.application.configuration', + 'database' => 'project.database.configuration', + 'service' => 'project.service.configuration', + default => null + }; + + if ($routeName) { + $route = route($routeName, [ + 'project_uuid' => data_get($resource, 'environment.project.uuid'), + 'environment_uuid' => data_get($resource, 'environment.uuid'), + $resource->type().'_uuid' => data_get($resource, 'uuid'), + ]); + + $settings = instanceSettings(); + if (data_get($settings, 'fqdn')) { + $url = Url::fromString($route); + $url = $url->withPort(null); + $fqdn = data_get($settings, 'fqdn'); + $fqdn = str_replace(['http://', 'https://'], '', $fqdn); + $url = $url->withHost($fqdn); + + $this->urls[$resource->name] = $url->__toString(); + } else { + $this->urls[$resource->name] = $route; + } + } + } + }); + } + + public function via(object $notifiable): array + { + return $notifiable->getEnabledChannels('ssl_certificate_renewal'); + } + + public function toMail(): MailMessage + { + $mail = new MailMessage; + $mail->subject('Coolify: [Action Required] SSL Certificates Renewed - Manual Redeployment Needed'); + $mail->view('emails.ssl-certificate-renewed', [ + 'resources' => $this->resources, + 'urls' => $this->urls, + ]); + + return $mail; + } + + public function toDiscord(): DiscordMessage + { + $resourceNames = $this->resources->pluck('name')->join(', '); + + $message = new DiscordMessage( + title: '🔒 SSL Certificates Renewed', + description: "SSL certificates have been renewed for: {$resourceNames}.\n\n**Action Required:** These resources need to be redeployed manually.", + color: DiscordMessage::warningColor(), + ); + + foreach ($this->urls as $name => $url) { + $message->addField($name, "[View Resource]({$url})"); + } + + return $message; + } + + public function toTelegram(): array + { + $resourceNames = $this->resources->pluck('name')->join(', '); + $message = "Coolify: SSL certificates have been renewed for: {$resourceNames}.\n\nAction Required: These resources need to be redeployed manually for the new SSL certificates to take effect."; + + $buttons = []; + foreach ($this->urls as $name => $url) { + $buttons[] = [ + 'text' => "View {$name}", + 'url' => $url, + ]; + } + + return [ + 'message' => $message, + 'buttons' => $buttons, + ]; + } + + public function toPushover(): PushoverMessage + { + $resourceNames = $this->resources->pluck('name')->join(', '); + $message = "SSL certificates have been renewed for: {$resourceNames}

"; + $message .= 'Action Required: These resources need to be redeployed manually for the new SSL certificates to take effect.'; + + $buttons = []; + foreach ($this->urls as $name => $url) { + $buttons[] = [ + 'text' => "View {$name}", + 'url' => $url, + ]; + } + + return new PushoverMessage( + title: 'SSL Certificates Renewed', + level: 'warning', + message: $message, + buttons: $buttons, + ); + } + + public function toSlack(): SlackMessage + { + $resourceNames = $this->resources->pluck('name')->join(', '); + $description = "SSL certificates have been renewed for: {$resourceNames}\n\n"; + $description .= '**Action Required:** These resources need to be redeployed manually for the new SSL certificates to take effect.'; + + if (! empty($this->urls)) { + $description .= "\n\n**Resource URLs:**\n"; + foreach ($this->urls as $name => $url) { + $description .= "• {$name}: {$url}\n"; + } + } + + return new SlackMessage( + title: '🔒 SSL Certificates Renewed', + description: $description, + color: SlackMessage::warningColor() + ); + } +} diff --git a/app/Notifications/Test.php b/app/Notifications/Test.php index ebb8735f5..0b1d8d6b1 100644 --- a/app/Notifications/Test.php +++ b/app/Notifications/Test.php @@ -22,7 +22,7 @@ class Test extends Notification implements ShouldQueue public $tries = 5; - public function __construct(public ?string $emails = null, public ?string $channel = null) + public function __construct(public ?string $emails = null, public ?string $channel = null, public ?bool $ping = false) { $this->onQueue('high'); } @@ -68,6 +68,7 @@ class Test extends Notification implements ShouldQueue title: ':white_check_mark: Test Success', description: 'This is a test Discord notification from Coolify. :cross_mark: :warning: :information_source:', color: DiscordMessage::successColor(), + isCritical: $this->ping, ); $message->addField(name: 'Dashboard', value: '[Link]('.base_url().')', inline: true); @@ -82,7 +83,7 @@ class Test extends Notification implements ShouldQueue 'buttons' => [ [ 'text' => 'Go to your dashboard', - 'url' => base_url(), + 'url' => isDev() ? 'https://staging-but-dev.coolify.io' : base_url(), ], ], ]; diff --git a/app/Notifications/TransactionalEmails/InvitationLink.php b/app/Notifications/TransactionalEmails/InvitationLink.php index 30ace99dc..9bfb54798 100644 --- a/app/Notifications/TransactionalEmails/InvitationLink.php +++ b/app/Notifications/TransactionalEmails/InvitationLink.php @@ -16,7 +16,7 @@ class InvitationLink extends CustomEmailNotification return [TransactionalEmailChannel::class]; } - public function __construct(public User $user) + public function __construct(public User $user, public bool $isTransactionalEmail = true) { $this->onQueue('high'); } diff --git a/app/Notifications/TransactionalEmails/ResetPassword.php b/app/Notifications/TransactionalEmails/ResetPassword.php index 4593ddb0d..179c8d948 100644 --- a/app/Notifications/TransactionalEmails/ResetPassword.php +++ b/app/Notifications/TransactionalEmails/ResetPassword.php @@ -17,7 +17,7 @@ class ResetPassword extends Notification public InstanceSettings $settings; - public function __construct($token) + public function __construct($token, public bool $isTransactionalEmail = true) { $this->settings = instanceSettings(); $this->token = $token; diff --git a/app/Notifications/TransactionalEmails/Test.php b/app/Notifications/TransactionalEmails/Test.php index eeb32a254..3add70db2 100644 --- a/app/Notifications/TransactionalEmails/Test.php +++ b/app/Notifications/TransactionalEmails/Test.php @@ -8,7 +8,7 @@ use Illuminate\Notifications\Messages\MailMessage; class Test extends CustomEmailNotification { - public function __construct(public string $emails) + public function __construct(public string $emails, public bool $isTransactionalEmail = true) { $this->onQueue('high'); } diff --git a/app/Providers/ConfigurationServiceProvider.php b/app/Providers/ConfigurationServiceProvider.php new file mode 100644 index 000000000..3ff459ef6 --- /dev/null +++ b/app/Providers/ConfigurationServiceProvider.php @@ -0,0 +1,21 @@ +app->singleton(ConfigurationRepository::class, function ($app) { + return new ConfigurationRepository($app['config']); + }); + } + + public function boot(): void + { + // + } +} diff --git a/app/Services/ConfigurationRepository.php b/app/Services/ConfigurationRepository.php new file mode 100644 index 000000000..ff2e73eed --- /dev/null +++ b/app/Services/ConfigurationRepository.php @@ -0,0 +1,56 @@ +config = $config; + } + + public function updateMailConfig($settings): void + { + if ($settings->resend_enabled) { + $this->config->set('mail.default', 'resend'); + $this->config->set('mail.from.address', $settings->smtp_from_address ?? 'test@example.com'); + $this->config->set('mail.from.name', $settings->smtp_from_name ?? 'Test'); + $this->config->set('resend.api_key', $settings->resend_api_key); + + return; + } + + if ($settings->smtp_enabled) { + $encryption = match (strtolower($settings->smtp_encryption)) { + 'starttls' => null, + 'tls' => 'tls', + 'none' => null, + default => null, + }; + + $this->config->set('mail.default', 'smtp'); + $this->config->set('mail.from.address', $settings->smtp_from_address ?? 'test@example.com'); + $this->config->set('mail.from.name', $settings->smtp_from_name ?? 'Test'); + $this->config->set('mail.mailers.smtp', [ + 'transport' => 'smtp', + 'host' => $settings->smtp_host, + 'port' => $settings->smtp_port, + 'encryption' => $encryption, + 'username' => $settings->smtp_username, + 'password' => $settings->smtp_password, + 'timeout' => $settings->smtp_timeout, + 'local_domain' => null, + 'auto_tls' => $settings->smtp_encryption === 'none' ? '0' : '', + ]); + } + } + + public function disableSshMux(): void + { + $this->config->set('constants.ssh.mux_enabled', false); + } +} diff --git a/app/Traits/DeletesUserSessions.php b/app/Traits/DeletesUserSessions.php new file mode 100644 index 000000000..2581d4203 --- /dev/null +++ b/app/Traits/DeletesUserSessions.php @@ -0,0 +1,34 @@ +where('user_id', $this->id)->delete(); + } + + /** + * Boot the trait. + */ + protected static function bootDeletesUserSessions() + { + static::updated(function ($user) { + // Check if password was changed + if ($user->isDirty('password')) { + $user->deleteAllSessions(); + } + }); + } +} diff --git a/app/Traits/EnvironmentVariableProtection.php b/app/Traits/EnvironmentVariableProtection.php new file mode 100644 index 000000000..b6b8d2687 --- /dev/null +++ b/app/Traits/EnvironmentVariableProtection.php @@ -0,0 +1,63 @@ +startsWith('SERVICE_FQDN') || str($key)->startsWith('SERVICE_URL'); + } + + /** + * Check if an environment variable is used in Docker Compose + * + * @param string $key The environment variable key to check + * @param string|null $dockerCompose The Docker Compose YAML content + * @return array [bool $isUsed, string $reason] Whether the variable is used and the reason if it is + */ + protected function isEnvironmentVariableUsedInDockerCompose(string $key, ?string $dockerCompose): array + { + if (empty($dockerCompose)) { + return [false, '']; + } + + try { + $dockerComposeData = Yaml::parse($dockerCompose); + $dockerEnvVars = data_get($dockerComposeData, 'services.*.environment'); + + foreach ($dockerEnvVars as $serviceEnvs) { + if (! is_array($serviceEnvs)) { + continue; + } + + // Check for direct variable usage + foreach ($serviceEnvs as $env => $value) { + if ($env === $key) { + return [true, "Environment variable '{$key}' is used directly in the Docker Compose file."]; + } + } + + // Check for variable references in values + foreach ($serviceEnvs as $env => $value) { + if (is_string($value) && str_contains($value, '$'.$key)) { + return [true, "Environment variable '{$key}' is referenced in the Docker Compose file."]; + } + } + } + } catch (\Exception $e) { + // If there's an error parsing the Docker Compose file, we'll assume it's not used + return [false, '']; + } + + return [false, '']; + } +} diff --git a/app/Traits/HasNotificationSettings.php b/app/Traits/HasNotificationSettings.php index bb088896a..236e4d97c 100644 --- a/app/Traits/HasNotificationSettings.php +++ b/app/Traits/HasNotificationSettings.php @@ -16,6 +16,7 @@ trait HasNotificationSettings 'server_force_disabled', 'general', 'test', + 'ssl_certificate_renewal', ]; /** diff --git a/app/View/Components/Forms/Select.php b/app/View/Components/Forms/Select.php index dd5ba66b7..feb4bf343 100644 --- a/app/View/Components/Forms/Select.php +++ b/app/View/Components/Forms/Select.php @@ -4,7 +4,6 @@ namespace App\View\Components\Forms; use Closure; use Illuminate\Contracts\View\View; -use Illuminate\Support\Str; use Illuminate\View\Component; use Visus\Cuid2\Cuid2; @@ -19,7 +18,8 @@ class Select extends Component public ?string $label = null, public ?string $helper = null, public bool $required = false, - public string $defaultClass = 'select' + public bool $disabled = false, + public string $defaultClass = 'select w-full' ) { // } @@ -36,8 +36,6 @@ class Select extends Component $this->name = $this->id; } - $this->label = Str::title($this->label); - return view('components.forms.select'); } } diff --git a/bootstrap/helpers/databases.php b/bootstrap/helpers/databases.php index 2fd85337d..48962f89c 100644 --- a/bootstrap/helpers/databases.php +++ b/bootstrap/helpers/databases.php @@ -16,16 +16,12 @@ use Illuminate\Support\Collection; use Illuminate\Support\Facades\Storage; use Visus\Cuid2\Cuid2; -function generate_database_name(string $type): string -{ - return $type.'-database-'.(new Cuid2); -} - function create_standalone_postgresql($environmentId, $destinationUuid, ?array $otherData = null, string $databaseImage = 'postgres:16-alpine'): StandalonePostgresql { $destination = StandaloneDocker::where('uuid', $destinationUuid)->firstOrFail(); $database = new StandalonePostgresql; - $database->name = generate_database_name('postgresql'); + $database->uuid = (new Cuid2); + $database->name = 'postgresql-database-'.$database->uuid; $database->image = $databaseImage; $database->postgres_password = \Illuminate\Support\Str::password(length: 64, symbols: false); $database->environment_id = $environmentId; @@ -43,7 +39,8 @@ function create_standalone_redis($environment_id, $destination_uuid, ?array $oth { $destination = StandaloneDocker::where('uuid', $destination_uuid)->firstOrFail(); $database = new StandaloneRedis; - $database->name = generate_database_name('redis'); + $database->uuid = (new Cuid2); + $database->name = 'redis-database-'.$database->uuid; $redis_password = \Illuminate\Support\Str::password(length: 64, symbols: false); $database->environment_id = $environment_id; $database->destination_id = $destination->id; @@ -76,7 +73,8 @@ function create_standalone_mongodb($environment_id, $destination_uuid, ?array $o { $destination = StandaloneDocker::where('uuid', $destination_uuid)->firstOrFail(); $database = new StandaloneMongodb; - $database->name = generate_database_name('mongodb'); + $database->uuid = (new Cuid2); + $database->name = 'mongodb-database-'.$database->uuid; $database->mongo_initdb_root_password = \Illuminate\Support\Str::password(length: 64, symbols: false); $database->environment_id = $environment_id; $database->destination_id = $destination->id; @@ -93,7 +91,8 @@ function create_standalone_mysql($environment_id, $destination_uuid, ?array $oth { $destination = StandaloneDocker::where('uuid', $destination_uuid)->firstOrFail(); $database = new StandaloneMysql; - $database->name = generate_database_name('mysql'); + $database->uuid = (new Cuid2); + $database->name = 'mysql-database-'.$database->uuid; $database->mysql_root_password = \Illuminate\Support\Str::password(length: 64, symbols: false); $database->mysql_password = \Illuminate\Support\Str::password(length: 64, symbols: false); $database->environment_id = $environment_id; @@ -111,7 +110,8 @@ function create_standalone_mariadb($environment_id, $destination_uuid, ?array $o { $destination = StandaloneDocker::where('uuid', $destination_uuid)->firstOrFail(); $database = new StandaloneMariadb; - $database->name = generate_database_name('mariadb'); + $database->uuid = (new Cuid2); + $database->name = 'mariadb-database-'.$database->uuid; $database->mariadb_root_password = \Illuminate\Support\Str::password(length: 64, symbols: false); $database->mariadb_password = \Illuminate\Support\Str::password(length: 64, symbols: false); $database->environment_id = $environment_id; @@ -129,7 +129,8 @@ function create_standalone_keydb($environment_id, $destination_uuid, ?array $oth { $destination = StandaloneDocker::where('uuid', $destination_uuid)->firstOrFail(); $database = new StandaloneKeydb; - $database->name = generate_database_name('keydb'); + $database->uuid = (new Cuid2); + $database->name = 'keydb-database-'.$database->uuid; $database->keydb_password = \Illuminate\Support\Str::password(length: 64, symbols: false); $database->environment_id = $environment_id; $database->destination_id = $destination->id; @@ -146,7 +147,8 @@ function create_standalone_dragonfly($environment_id, $destination_uuid, ?array { $destination = StandaloneDocker::where('uuid', $destination_uuid)->firstOrFail(); $database = new StandaloneDragonfly; - $database->name = generate_database_name('dragonfly'); + $database->uuid = (new Cuid2); + $database->name = 'dragonfly-database-'.$database->uuid; $database->dragonfly_password = \Illuminate\Support\Str::password(length: 64, symbols: false); $database->environment_id = $environment_id; $database->destination_id = $destination->id; @@ -163,7 +165,8 @@ function create_standalone_clickhouse($environment_id, $destination_uuid, ?array { $destination = StandaloneDocker::where('uuid', $destination_uuid)->firstOrFail(); $database = new StandaloneClickhouse; - $database->name = generate_database_name('clickhouse'); + $database->uuid = (new Cuid2); + $database->name = 'clickhouse-database-'.$database->uuid; $database->clickhouse_admin_password = \Illuminate\Support\Str::password(length: 64, symbols: false); $database->environment_id = $environment_id; $database->destination_id = $destination->id; diff --git a/bootstrap/helpers/docker.php b/bootstrap/helpers/docker.php index 80e19d80f..de80adbef 100644 --- a/bootstrap/helpers/docker.php +++ b/bootstrap/helpers/docker.php @@ -8,6 +8,7 @@ use App\Models\ServiceApplication; use Illuminate\Support\Collection; use Illuminate\Support\Str; use Spatie\Url\Url; +use Symfony\Component\Yaml\Yaml; use Visus\Cuid2\Cuid2; function getCurrentApplicationContainerStatus(Server $server, int $id, ?int $pullRequestId = null, ?bool $includePullrequests = false): Collection @@ -834,7 +835,15 @@ function validateComposeFile(string $compose, int $server_id): string|Throwable if (! $server) { throw new \Exception('Server not found'); } - $base64_compose = base64_encode($compose); + $yaml_compose = Yaml::parse($compose); + foreach ($yaml_compose['services'] as $service_name => $service) { + foreach ($service['volumes'] as $volume_name => $volume) { + if (data_get($volume, 'type') === 'bind' && data_get($volume, 'content')) { + unset($yaml_compose['services'][$service_name]['volumes'][$volume_name]['content']); + } + } + } + $base64_compose = base64_encode(Yaml::dump($yaml_compose)); instant_remote_process([ "echo {$base64_compose} | base64 -d | tee /tmp/{$uuid}.yml > /dev/null", "chmod 600 /tmp/{$uuid}.yml", diff --git a/bootstrap/helpers/github.php b/bootstrap/helpers/github.php index 3a3f6e7b2..81f8ff18a 100644 --- a/bootstrap/helpers/github.php +++ b/bootstrap/helpers/github.php @@ -129,3 +129,27 @@ function getPermissionsPath(GithubApp $source) return "$github->html_url/settings/apps/$name/permissions"; } + +function loadRepositoryByPage(GithubApp $source, string $token, int $page) +{ + $response = Http::withToken($token)->get("{$source->api_url}/installation/repositories?per_page=100&page={$page}"); + $json = $response->json(); + if ($response->status() !== 200) { + return [ + 'total_count' => 0, + 'repositories' => [], + ]; + } + + if ($json['total_count'] === 0) { + return [ + 'total_count' => 0, + 'repositories' => [], + ]; + } + + return [ + 'total_count' => $json['total_count'], + 'repositories' => $json['repositories'], + ]; +} diff --git a/bootstrap/helpers/notifications.php b/bootstrap/helpers/notifications.php index b0345df7e..bee39ef01 100644 --- a/bootstrap/helpers/notifications.php +++ b/bootstrap/helpers/notifications.php @@ -1,6 +1,5 @@ set('mail.default', 'resend'); - config()->set('mail.from.address', data_get($settings, 'smtp_from_address')); - config()->set('mail.from.name', data_get($settings, 'smtp_from_name')); - config()->set('resend.api_key', data_get($settings, 'resend_api_key')); + $configRepository = app('App\Services\ConfigurationRepository'::class); + $configRepository->updateMailConfig($settings); + if (data_get($settings, 'resend_enabled')) { return 'resend'; } - $encryption = match (strtolower(data_get($settings, 'smtp_encryption'))) { - 'starttls' => null, - 'tls' => 'tls', - 'none' => null, - default => null, - }; - if (data_get($settings, 'smtp_enabled')) { - config()->set('mail.from.address', data_get($settings, 'smtp_from_address')); - config()->set('mail.from.name', data_get($settings, 'smtp_from_name')); - config()->set('mail.default', 'smtp'); - config()->set('mail.mailers.smtp', [ - 'transport' => 'smtp', - 'host' => data_get($settings, 'smtp_host'), - 'port' => data_get($settings, 'smtp_port'), - 'encryption' => $encryption, - 'username' => data_get($settings, 'smtp_username'), - 'password' => data_get($settings, 'smtp_password'), - 'timeout' => data_get($settings, 'smtp_timeout'), - 'local_domain' => null, - 'auto_tls' => data_get($settings, 'smtp_encryption') === 'none' ? '0' : '', // If encryption is "none", it will not try to upgrade to TLS via StartTLS to make sure it is unencrypted. - ]); - return 'smtp'; } + + return null; } diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index db3085649..b90de4dbc 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -1250,13 +1250,23 @@ function get_public_ips() function isAnyDeploymentInprogress() { $runningJobs = ApplicationDeploymentQueue::where('horizon_job_worker', gethostname())->where('status', ApplicationDeploymentStatus::IN_PROGRESS->value)->get(); + $basicDetails = $runningJobs->map(function ($job) { + return [ + 'id' => $job->id, + 'created_at' => $job->created_at, + 'application_id' => $job->application_id, + 'server_id' => $job->server_id, + 'horizon_job_id' => $job->horizon_job_id, + 'status' => $job->status, + ]; + }); + echo 'Running jobs: '.json_encode($basicDetails)."\n"; $horizonJobIds = []; foreach ($runningJobs as $runningJob) { $horizonJobStatus = getJobStatus($runningJob->horizon_job_id); - if ($horizonJobStatus === 'unknown') { - return true; + if ($horizonJobStatus === 'unknown' || $horizonJobStatus === 'reserved') { + $horizonJobIds[] = $runningJob->horizon_job_id; } - $horizonJobIds[] = $runningJob->horizon_job_id; } if (count($horizonJobIds) === 0) { echo "No deployments in progress.\n"; @@ -1665,6 +1675,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal if ($source->value() === '/tmp' || $source->value() === '/tmp/') { return $volume; } + LocalFileVolume::updateOrCreate( [ 'mount_path' => $target, @@ -3164,6 +3175,13 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int } } + $serviceAppsLogDrainEnabledMap = collect([]); + if ($resource instanceof Service) { + $serviceAppsLogDrainEnabledMap = $resource->applications()->get()->keyBy('name')->map(function ($app) { + return $app->isLogDrainEnabled(); + }); + } + // Parse the rest of the services foreach ($services as $serviceName => $service) { $image = data_get_str($service, 'image'); @@ -3174,6 +3192,9 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int if ($resource instanceof Application && $resource->isLogDrainEnabled()) { $logging = generate_fluentd_configuration(); } + if ($resource instanceof Service && $serviceAppsLogDrainEnabledMap->get($serviceName)) { + $logging = generate_fluentd_configuration(); + } } $volumes = collect(data_get($service, 'volumes', [])); $networks = collect(data_get($service, 'networks', [])); @@ -4050,9 +4071,35 @@ function isEmailRateLimited(string $limiterKey, int $decaySeconds = 3600, ?calla return $rateLimited; } -function defaultNginxConfiguration(): string +function defaultNginxConfiguration(string $type = 'static'): string { - return 'server { + if ($type === 'spa') { + return <<<'NGINX' +server { + location / { + root /usr/share/nginx/html; + index index.html; + try_files $uri $uri/ /index.html; + } + + # Handle 404 errors + error_page 404 /404.html; + location = /404.html { + root /usr/share/nginx/html; + internal; + } + + # Handle server errors (50x) + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + internal; + } +} +NGINX; + } else { + return <<<'NGINX' +server { location / { root /usr/share/nginx/html; index index.html index.htm; @@ -4072,7 +4119,9 @@ function defaultNginxConfiguration(): string root /usr/share/nginx/html; internal; } -}'; +} +NGINX; + } } function convertGitUrl(string $gitRepository, string $deploymentType, ?GithubApp $source = null): array @@ -4137,3 +4186,35 @@ function getJobStatus(?string $jobId = null) return $jobFound->first()->status; } + +function parseDockerfileInterval(string $something) +{ + $value = preg_replace('/[^0-9]/', '', $something); + $unit = preg_replace('/[0-9]/', '', $something); + + // Default to seconds if no unit specified + $unit = $unit ?: 's'; + + // Convert to seconds based on unit + $seconds = (int) $value; + switch ($unit) { + case 'ns': + $seconds = (int) ($value / 1000000000); + break; + case 'us': + case 'µs': + $seconds = (int) ($value / 1000000); + break; + case 'ms': + $seconds = (int) ($value / 1000); + break; + case 'm': + $seconds = (int) ($value * 60); + break; + case 'h': + $seconds = (int) ($value * 3600); + break; + } + + return $seconds; +} diff --git a/composer.json b/composer.json index e5aeb6126..5b97e76e9 100644 --- a/composer.json +++ b/composer.json @@ -12,65 +12,63 @@ ], "require": { "php": "^8.4", - "3sidedcube/laravel-redoc": "^1.0", - "danharrin/livewire-rate-limiting": "2.0.0", - "doctrine/dbal": "^4.2", - "guzzlehttp/guzzle": "^7.5.0", - "laravel/fortify": "^1.16.0", - "laravel/framework": "^11.0", - "laravel/horizon": "^5.29.1", - "laravel/pail": "^1.1", - "laravel/prompts": "^0.1.18|^0.2.0|^0.3.0", - "laravel/sanctum": "^4.0", - "laravel/socialite": "^5.14.0", - "laravel/tinker": "^2.8.1", - "laravel/ui": "^4.2", - "lcobucci/jwt": "^5.0.0", - "league/flysystem-aws-s3-v3": "^3.0", - "league/flysystem-sftp-v3": "^3.0", - "livewire/livewire": "^3.5", - "log1x/laravel-webfonts": "^1.0", - "lorisleiva/laravel-actions": "^2.8", + "danharrin/livewire-rate-limiting": "2.1.0", + "doctrine/dbal": "^4.2.2", + "guzzlehttp/guzzle": "^7.9.2", + "laravel/fortify": "^1.25.4", + "laravel/framework": "12.4.1", + "laravel/horizon": "^5.30.3", + "laravel/pail": "^1.2.2", + "laravel/prompts": "^0.3.5|^0.3.5|^0.3.5", + "laravel/sanctum": "^4.0.8", + "laravel/socialite": "^5.18.0", + "laravel/tinker": "^2.10.1", + "laravel/ui": "^4.6.1", + "lcobucci/jwt": "^5.5.0", + "league/flysystem-aws-s3-v3": "^3.29", + "league/flysystem-sftp-v3": "^3.29", + "livewire/livewire": "^3.5.20", + "log1x/laravel-webfonts": "^2.0.1", + "lorisleiva/laravel-actions": "^2.8.6", "nubs/random-name-generator": "^2.2", - "phpseclib/phpseclib": "^3.0", - "pion/laravel-chunk-upload": "^1.5", - "poliander/cron": "^3.0", - "purplepixie/phpdns": "^2.1", - "pusher/pusher-php-server": "^7.2", - "resend/resend-laravel": "^0.15.0", - "sentry/sentry-laravel": "^4.6", + "phpseclib/phpseclib": "^3.0.43", + "pion/laravel-chunk-upload": "^1.5.4", + "poliander/cron": "^3.2.1", + "purplepixie/phpdns": "^2.2", + "pusher/pusher-php-server": "^7.2.7", + "resend/resend-laravel": "^0.17.0", + "sentry/sentry-laravel": "^4.13", "socialiteproviders/authentik": "^5.2", "socialiteproviders/google": "^4.1", "socialiteproviders/infomaniak": "^4.0", - "socialiteproviders/microsoft-azure": "^5.1", - "spatie/laravel-activitylog": "^4.7.3", - "spatie/laravel-data": "^4.11", - "spatie/laravel-ray": "^1.37", - "spatie/laravel-schemaless-attributes": "^2.4", - "spatie/url": "^2.2", - "stevebauman/purify": "^6.2", - "stripe/stripe-php": "^16.2.0", - "symfony/yaml": "^7.1.6", + "socialiteproviders/microsoft-azure": "^5.2", + "spatie/laravel-activitylog": "^4.10.1", + "spatie/laravel-data": "^4.13.1", + "spatie/laravel-ray": "^1.39.1", + "spatie/laravel-schemaless-attributes": "^2.5.1", + "spatie/url": "^2.4", + "stevebauman/purify": "^6.3", + "stripe/stripe-php": "^16.5.1", + "symfony/yaml": "^7.2.3", "visus/cuid2": "^4.1.0", - "yosymfony/toml": "^1.0", - "zircote/swagger-php": "^5.0" + "yosymfony/toml": "^1.0.4", + "zircote/swagger-php": "^5.0.5" }, "require-dev": { - "barryvdh/laravel-debugbar": "^3.13", - "driftingly/rector-laravel": "^2.0", - "fakerphp/faker": "^1.21.0", - "laravel/dusk": "^8.0", - "laravel/pint": "^1.16", - "laravel/telescope": "^5.2", - "mockery/mockery": "^1.5.1", - "nunomaduro/collision": "^8.1", - "pestphp/pest": "^3.5", - "phpstan/phpstan": "^2.1", - "phpunit/phpunit": "^11.5", - "rector/rector": "^2.0", - "serversideup/spin": "^3.0", - "spatie/laravel-ignition": "^2.1.0", - "symfony/http-client": "^7.1" + "barryvdh/laravel-debugbar": "^3.15.1", + "driftingly/rector-laravel": "^2.0.2", + "fakerphp/faker": "^1.24.1", + "laravel/dusk": "^8.3.1", + "laravel/pint": "^1.21", + "laravel/telescope": "^5.5", + "mockery/mockery": "^1.6.12", + "nunomaduro/collision": "^8.6.1", + "pestphp/pest": "^3.8.0", + "phpstan/phpstan": "^2.1.6", + "rector/rector": "^2.0.9", + "serversideup/spin": "^3.0.2", + "spatie/laravel-ignition": "^2.9.1", + "symfony/http-client": "^7.2.3" }, "minimum-stability": "stable", "prefer-stable": true, diff --git a/composer.lock b/composer.lock index 2a3fc1ddb..939178d38 100644 --- a/composer.lock +++ b/composer.lock @@ -4,66 +4,8 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "dcf6b2f554372a570628d7f85184df7b", + "content-hash": "f0ef990aa6e05ab48d61fcc22c0c1ca2", "packages": [ - { - "name": "3sidedcube/laravel-redoc", - "version": "v1.0.1", - "source": { - "type": "git", - "url": "https://github.com/3sidedcube/laravel-redoc.git", - "reference": "c33a563885dcdf1e0f623df5a56c106d130261da" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/3sidedcube/laravel-redoc/zipball/c33a563885dcdf1e0f623df5a56c106d130261da", - "reference": "c33a563885dcdf1e0f623df5a56c106d130261da", - "shasum": "" - }, - "require": { - "illuminate/routing": "^8.0|^9.0|^10.0|^11.0", - "illuminate/support": "^8.0|^9.0|^10.0|^11.0", - "php": "^7.4|^8.0|^8.1|^8.2|^8.3" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^3.3", - "orchestra/testbench": "^6.0|^7.0|^8.0|^9.0" - }, - "type": "library", - "extra": { - "laravel": { - "providers": [ - "ThreeSidedCube\\LaravelRedoc\\RedocServiceProvider" - ] - } - }, - "autoload": { - "psr-4": { - "ThreeSidedCube\\LaravelRedoc\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ben Sherred", - "role": "Developer" - } - ], - "description": "A lightweight package for rendering API documentation using OpenAPI and Redoc.", - "homepage": "https://github.com/3sidedcube/laravel-redoc", - "keywords": [ - "3sidedcube", - "laravel-redoc" - ], - "support": { - "issues": "https://github.com/3sidedcube/laravel-redoc/issues", - "source": "https://github.com/3sidedcube/laravel-redoc/tree/v1.0.1" - }, - "time": "2024-05-20T11:37:55+00:00" - }, { "name": "amphp/amp", "version": "v3.1.0", @@ -147,16 +89,16 @@ }, { "name": "amphp/byte-stream", - "version": "v2.1.1", + "version": "v2.1.2", "source": { "type": "git", "url": "https://github.com/amphp/byte-stream.git", - "reference": "daa00f2efdbd71565bf64ffefa89e37542addf93" + "reference": "55a6bd071aec26fa2a3e002618c20c35e3df1b46" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/byte-stream/zipball/daa00f2efdbd71565bf64ffefa89e37542addf93", - "reference": "daa00f2efdbd71565bf64ffefa89e37542addf93", + "url": "https://api.github.com/repos/amphp/byte-stream/zipball/55a6bd071aec26fa2a3e002618c20c35e3df1b46", + "reference": "55a6bd071aec26fa2a3e002618c20c35e3df1b46", "shasum": "" }, "require": { @@ -210,7 +152,7 @@ ], "support": { "issues": "https://github.com/amphp/byte-stream/issues", - "source": "https://github.com/amphp/byte-stream/tree/v2.1.1" + "source": "https://github.com/amphp/byte-stream/tree/v2.1.2" }, "funding": [ { @@ -218,7 +160,7 @@ "type": "github" } ], - "time": "2024-02-17T04:49:38+00:00" + "time": "2025-03-16T17:10:27+00:00" }, { "name": "amphp/cache", @@ -522,16 +464,16 @@ }, { "name": "amphp/pipeline", - "version": "v1.2.2", + "version": "v1.2.3", "source": { "type": "git", "url": "https://github.com/amphp/pipeline.git", - "reference": "97cbf289f4d8877acfe58dd90ed5a4370a43caa4" + "reference": "7b52598c2e9105ebcddf247fc523161581930367" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/pipeline/zipball/97cbf289f4d8877acfe58dd90ed5a4370a43caa4", - "reference": "97cbf289f4d8877acfe58dd90ed5a4370a43caa4", + "url": "https://api.github.com/repos/amphp/pipeline/zipball/7b52598c2e9105ebcddf247fc523161581930367", + "reference": "7b52598c2e9105ebcddf247fc523161581930367", "shasum": "" }, "require": { @@ -577,7 +519,7 @@ ], "support": { "issues": "https://github.com/amphp/pipeline/issues", - "source": "https://github.com/amphp/pipeline/tree/v1.2.2" + "source": "https://github.com/amphp/pipeline/tree/v1.2.3" }, "funding": [ { @@ -585,7 +527,7 @@ "type": "github" } ], - "time": "2025-01-19T15:42:46+00:00" + "time": "2025-03-16T16:33:53+00:00" }, { "name": "amphp/process", @@ -928,16 +870,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.339.19", + "version": "3.342.17", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "18f05efe983860ad899082e04c13f06ec9fd6e41" + "reference": "110ca44672e59ec3255c693e9e761001f2706b53" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/18f05efe983860ad899082e04c13f06ec9fd6e41", - "reference": "18f05efe983860ad899082e04c13f06ec9fd6e41", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/110ca44672e59ec3255c693e9e761001f2706b53", + "reference": "110ca44672e59ec3255c693e9e761001f2706b53", "shasum": "" }, "require": { @@ -1019,9 +961,9 @@ "support": { "forum": "https://github.com/aws/aws-sdk-php/discussions", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.339.19" + "source": "https://github.com/aws/aws-sdk-php/tree/3.342.17" }, - "time": "2025-02-21T19:13:15+00:00" + "time": "2025-03-31T18:16:40+00:00" }, { "name": "bacon/bacon-qr-code", @@ -1208,27 +1150,27 @@ }, { "name": "danharrin/livewire-rate-limiting", - "version": "v2.0.0", + "version": "v2.1.0", "source": { "type": "git", "url": "https://github.com/danharrin/livewire-rate-limiting.git", - "reference": "0d9c1890174b3d1857dba6ce76de7c178fe20283" + "reference": "14dde653a9ae8f38af07a0ba4921dc046235e1a0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/danharrin/livewire-rate-limiting/zipball/0d9c1890174b3d1857dba6ce76de7c178fe20283", - "reference": "0d9c1890174b3d1857dba6ce76de7c178fe20283", + "url": "https://api.github.com/repos/danharrin/livewire-rate-limiting/zipball/14dde653a9ae8f38af07a0ba4921dc046235e1a0", + "reference": "14dde653a9ae8f38af07a0ba4921dc046235e1a0", "shasum": "" }, "require": { - "illuminate/support": "^9.0|^10.0|^11.0", + "illuminate/support": "^9.0|^10.0|^11.0|^12.0", "php": "^8.0" }, "require-dev": { "livewire/livewire": "^3.0", "livewire/volt": "^1.3", - "orchestra/testbench": "^7.0|^8.0|^9.0", - "phpunit/phpunit": "^9.0|^10.0" + "orchestra/testbench": "^7.0|^8.0|^9.0|^10.0", + "phpunit/phpunit": "^9.0|^10.0|^11.5.3" }, "type": "library", "autoload": { @@ -1258,7 +1200,7 @@ "type": "github" } ], - "time": "2024-11-24T16:57:47+00:00" + "time": "2025-02-21T08:52:11+00:00" }, { "name": "dasprid/enum", @@ -1431,16 +1373,16 @@ }, { "name": "doctrine/dbal", - "version": "4.2.2", + "version": "4.2.3", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "19a2b7deb5fe8c2df0ff817ecea305e50acb62ec" + "reference": "33d2d7fe1269b2301640c44cf2896ea607b30e3e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/19a2b7deb5fe8c2df0ff817ecea305e50acb62ec", - "reference": "19a2b7deb5fe8c2df0ff817ecea305e50acb62ec", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/33d2d7fe1269b2301640c44cf2896ea607b30e3e", + "reference": "33d2d7fe1269b2301640c44cf2896ea607b30e3e", "shasum": "" }, "require": { @@ -1517,7 +1459,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/4.2.2" + "source": "https://github.com/doctrine/dbal/tree/4.2.3" }, "funding": [ { @@ -1533,7 +1475,7 @@ "type": "tidelift" } ], - "time": "2025-01-16T08:40:56+00:00" + "time": "2025-03-07T18:29:05+00:00" }, { "name": "doctrine/deprecations", @@ -1815,16 +1757,16 @@ }, { "name": "egulias/email-validator", - "version": "4.0.3", + "version": "4.0.4", "source": { "type": "git", "url": "https://github.com/egulias/EmailValidator.git", - "reference": "b115554301161fa21467629f1e1391c1936de517" + "reference": "d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/b115554301161fa21467629f1e1391c1936de517", - "reference": "b115554301161fa21467629f1e1391c1936de517", + "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa", + "reference": "d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa", "shasum": "" }, "require": { @@ -1870,7 +1812,7 @@ ], "support": { "issues": "https://github.com/egulias/EmailValidator/issues", - "source": "https://github.com/egulias/EmailValidator/tree/4.0.3" + "source": "https://github.com/egulias/EmailValidator/tree/4.0.4" }, "funding": [ { @@ -1878,7 +1820,7 @@ "type": "github" } ], - "time": "2024-12-27T00:36:43+00:00" + "time": "2025-03-06T22:45:56+00:00" }, { "name": "ezyang/htmlpurifier", @@ -2139,16 +2081,16 @@ }, { "name": "guzzlehttp/guzzle", - "version": "7.9.2", + "version": "7.9.3", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "d281ed313b989f213357e3be1a179f02196ac99b" + "reference": "7b2f29fe81dc4da0ca0ea7d42107a0845946ea77" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/d281ed313b989f213357e3be1a179f02196ac99b", - "reference": "d281ed313b989f213357e3be1a179f02196ac99b", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/7b2f29fe81dc4da0ca0ea7d42107a0845946ea77", + "reference": "7b2f29fe81dc4da0ca0ea7d42107a0845946ea77", "shasum": "" }, "require": { @@ -2245,7 +2187,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.9.2" + "source": "https://github.com/guzzle/guzzle/tree/7.9.3" }, "funding": [ { @@ -2261,20 +2203,20 @@ "type": "tidelift" } ], - "time": "2024-07-24T11:22:20+00:00" + "time": "2025-03-27T13:37:11+00:00" }, { "name": "guzzlehttp/promises", - "version": "2.0.4", + "version": "2.2.0", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "f9c436286ab2892c7db7be8c8da4ef61ccf7b455" + "reference": "7c69f28996b0a6920945dd20b3857e499d9ca96c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/f9c436286ab2892c7db7be8c8da4ef61ccf7b455", - "reference": "f9c436286ab2892c7db7be8c8da4ef61ccf7b455", + "url": "https://api.github.com/repos/guzzle/promises/zipball/7c69f28996b0a6920945dd20b3857e499d9ca96c", + "reference": "7c69f28996b0a6920945dd20b3857e499d9ca96c", "shasum": "" }, "require": { @@ -2328,7 +2270,7 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/2.0.4" + "source": "https://github.com/guzzle/promises/tree/2.2.0" }, "funding": [ { @@ -2344,20 +2286,20 @@ "type": "tidelift" } ], - "time": "2024-10-17T10:06:22+00:00" + "time": "2025-03-27T13:27:01+00:00" }, { "name": "guzzlehttp/psr7", - "version": "2.7.0", + "version": "2.7.1", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201" + "reference": "c2270caaabe631b3b44c85f99e5a04bbb8060d16" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/a70f5c95fb43bc83f07c9c948baa0dc1829bf201", - "reference": "a70f5c95fb43bc83f07c9c948baa0dc1829bf201", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/c2270caaabe631b3b44c85f99e5a04bbb8060d16", + "reference": "c2270caaabe631b3b44c85f99e5a04bbb8060d16", "shasum": "" }, "require": { @@ -2444,7 +2386,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.7.0" + "source": "https://github.com/guzzle/psr7/tree/2.7.1" }, "funding": [ { @@ -2460,7 +2402,7 @@ "type": "tidelift" } ], - "time": "2024-07-18T11:15:46+00:00" + "time": "2025-03-27T12:30:47+00:00" }, { "name": "guzzlehttp/uri-template", @@ -2550,16 +2492,16 @@ }, { "name": "jean85/pretty-package-versions", - "version": "2.1.0", + "version": "2.1.1", "source": { "type": "git", "url": "https://github.com/Jean85/pretty-package-versions.git", - "reference": "3c4e5f62ba8d7de1734312e4fff32f67a8daaf10" + "reference": "4d7aa5dab42e2a76d99559706022885de0e18e1a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/3c4e5f62ba8d7de1734312e4fff32f67a8daaf10", - "reference": "3c4e5f62ba8d7de1734312e4fff32f67a8daaf10", + "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/4d7aa5dab42e2a76d99559706022885de0e18e1a", + "reference": "4d7aa5dab42e2a76d99559706022885de0e18e1a", "shasum": "" }, "require": { @@ -2569,8 +2511,9 @@ "require-dev": { "friendsofphp/php-cs-fixer": "^3.2", "jean85/composer-provided-replaced-stub-package": "^1.0", - "phpstan/phpstan": "^1.4", + "phpstan/phpstan": "^2.0", "phpunit/phpunit": "^7.5|^8.5|^9.6", + "rector/rector": "^2.0", "vimeo/psalm": "^4.3 || ^5.0" }, "type": "library", @@ -2603,9 +2546,9 @@ ], "support": { "issues": "https://github.com/Jean85/pretty-package-versions/issues", - "source": "https://github.com/Jean85/pretty-package-versions/tree/2.1.0" + "source": "https://github.com/Jean85/pretty-package-versions/tree/2.1.1" }, - "time": "2024-11-18T16:19:46+00:00" + "time": "2025-03-19T14:43:43+00:00" }, { "name": "kelunik/certificate", @@ -2732,20 +2675,20 @@ }, { "name": "laravel/framework", - "version": "v11.44.1", + "version": "v12.4.1", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "0883d4175f4e2b5c299e7087ad3c74f2ce195c6d" + "reference": "cdefd852ecb459a65392cd6ccb578c92a15b8e2b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/0883d4175f4e2b5c299e7087ad3c74f2ce195c6d", - "reference": "0883d4175f4e2b5c299e7087ad3c74f2ce195c6d", + "url": "https://api.github.com/repos/laravel/framework/zipball/cdefd852ecb459a65392cd6ccb578c92a15b8e2b", + "reference": "cdefd852ecb459a65392cd6ccb578c92a15b8e2b", "shasum": "" }, "require": { - "brick/math": "^0.9.3|^0.10.2|^0.11|^0.12", + "brick/math": "^0.11|^0.12", "composer-runtime-api": "^2.2", "doctrine/inflector": "^2.0.5", "dragonmantank/cron-expression": "^3.4", @@ -2760,32 +2703,32 @@ "fruitcake/php-cors": "^1.3", "guzzlehttp/guzzle": "^7.8.2", "guzzlehttp/uri-template": "^1.0", - "laravel/prompts": "^0.1.18|^0.2.0|^0.3.0", + "laravel/prompts": "^0.3.0", "laravel/serializable-closure": "^1.3|^2.0", "league/commonmark": "^2.6", "league/flysystem": "^3.25.1", "league/flysystem-local": "^3.25.1", "league/uri": "^7.5.1", "monolog/monolog": "^3.0", - "nesbot/carbon": "^2.72.6|^3.8.4", + "nesbot/carbon": "^3.8.4", "nunomaduro/termwind": "^2.0", "php": "^8.2", "psr/container": "^1.1.1|^2.0.1", "psr/log": "^1.0|^2.0|^3.0", "psr/simple-cache": "^1.0|^2.0|^3.0", "ramsey/uuid": "^4.7", - "symfony/console": "^7.0.3", - "symfony/error-handler": "^7.0.3", - "symfony/finder": "^7.0.3", + "symfony/console": "^7.2.0", + "symfony/error-handler": "^7.2.0", + "symfony/finder": "^7.2.0", "symfony/http-foundation": "^7.2.0", - "symfony/http-kernel": "^7.0.3", - "symfony/mailer": "^7.0.3", - "symfony/mime": "^7.0.3", + "symfony/http-kernel": "^7.2.0", + "symfony/mailer": "^7.2.0", + "symfony/mime": "^7.2.0", "symfony/polyfill-php83": "^1.31", - "symfony/process": "^7.0.3", - "symfony/routing": "^7.0.3", - "symfony/uid": "^7.0.3", - "symfony/var-dumper": "^7.0.3", + "symfony/process": "^7.2.0", + "symfony/routing": "^7.2.0", + "symfony/uid": "^7.2.0", + "symfony/var-dumper": "^7.2.0", "tijsverkoyen/css-to-inline-styles": "^2.2.5", "vlucas/phpdotenv": "^5.6.1", "voku/portable-ascii": "^2.0.2" @@ -2849,17 +2792,17 @@ "league/flysystem-read-only": "^3.25.1", "league/flysystem-sftp-v3": "^3.25.1", "mockery/mockery": "^1.6.10", - "orchestra/testbench-core": "^9.11.2", + "orchestra/testbench-core": "^10.0.0", "pda/pheanstalk": "^5.0.6", "php-http/discovery": "^1.15", "phpstan/phpstan": "^2.0", - "phpunit/phpunit": "^10.5.35|^11.3.6|^12.0.1", + "phpunit/phpunit": "^10.5.35|^11.5.3|^12.0.1", "predis/predis": "^2.3", "resend/resend-php": "^0.10.0", - "symfony/cache": "^7.0.3", - "symfony/http-client": "^7.0.3", - "symfony/psr-http-message-bridge": "^7.0.3", - "symfony/translation": "^7.0.3" + "symfony/cache": "^7.2.0", + "symfony/http-client": "^7.2.0", + "symfony/psr-http-message-bridge": "^7.2.0", + "symfony/translation": "^7.2.0" }, "suggest": { "ably/ably-php": "Required to use the Ably broadcast driver (^1.0).", @@ -2885,22 +2828,22 @@ "mockery/mockery": "Required to use mocking (^1.6).", "pda/pheanstalk": "Required to use the beanstalk queue driver (^5.0).", "php-http/discovery": "Required to use PSR-7 bridging features (^1.15).", - "phpunit/phpunit": "Required to use assertions and run tests (^10.5.35|^11.3.6|^12.0.1).", + "phpunit/phpunit": "Required to use assertions and run tests (^10.5.35|^11.5.3|^12.0.1).", "predis/predis": "Required to use the predis connector (^2.3).", "psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0).", "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^6.0|^7.0).", "resend/resend-php": "Required to enable support for the Resend mail transport (^0.10.0).", - "symfony/cache": "Required to PSR-6 cache bridge (^7.0).", - "symfony/filesystem": "Required to enable support for relative symbolic links (^7.0).", - "symfony/http-client": "Required to enable support for the Symfony API mail transports (^7.0).", - "symfony/mailgun-mailer": "Required to enable support for the Mailgun mail transport (^7.0).", - "symfony/postmark-mailer": "Required to enable support for the Postmark mail transport (^7.0).", - "symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^7.0)." + "symfony/cache": "Required to PSR-6 cache bridge (^7.2).", + "symfony/filesystem": "Required to enable support for relative symbolic links (^7.2).", + "symfony/http-client": "Required to enable support for the Symfony API mail transports (^7.2).", + "symfony/mailgun-mailer": "Required to enable support for the Mailgun mail transport (^7.2).", + "symfony/postmark-mailer": "Required to enable support for the Postmark mail transport (^7.2).", + "symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^7.2)." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "11.x-dev" + "dev-master": "12.x-dev" } }, "autoload": { @@ -2943,20 +2886,20 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-03-05T15:34:10+00:00" + "time": "2025-03-30T16:27:26+00:00" }, { "name": "laravel/horizon", - "version": "v5.30.3", + "version": "v5.31.0", "source": { "type": "git", "url": "https://github.com/laravel/horizon.git", - "reference": "7b9ee870bf0e425b956fd0433f616f98fe951f72" + "reference": "a21e7d64784b24eaf3bf873f82affbf67707a72a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/horizon/zipball/7b9ee870bf0e425b956fd0433f616f98fe951f72", - "reference": "7b9ee870bf0e425b956fd0433f616f98fe951f72", + "url": "https://api.github.com/repos/laravel/horizon/zipball/a21e7d64784b24eaf3bf873f82affbf67707a72a", + "reference": "a21e7d64784b24eaf3bf873f82affbf67707a72a", "shasum": "" }, "require": { @@ -3021,9 +2964,9 @@ ], "support": { "issues": "https://github.com/laravel/horizon/issues", - "source": "https://github.com/laravel/horizon/tree/v5.30.3" + "source": "https://github.com/laravel/horizon/tree/v5.31.0" }, - "time": "2025-02-11T13:52:50+00:00" + "time": "2025-03-04T14:56:42+00:00" }, { "name": "laravel/pail", @@ -3228,16 +3171,16 @@ }, { "name": "laravel/serializable-closure", - "version": "v2.0.3", + "version": "v2.0.4", "source": { "type": "git", "url": "https://github.com/laravel/serializable-closure.git", - "reference": "f379c13663245f7aa4512a7869f62eb14095f23f" + "reference": "b352cf0534aa1ae6b4d825d1e762e35d43f8a841" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/f379c13663245f7aa4512a7869f62eb14095f23f", - "reference": "f379c13663245f7aa4512a7869f62eb14095f23f", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/b352cf0534aa1ae6b4d825d1e762e35d43f8a841", + "reference": "b352cf0534aa1ae6b4d825d1e762e35d43f8a841", "shasum": "" }, "require": { @@ -3285,7 +3228,7 @@ "issues": "https://github.com/laravel/serializable-closure/issues", "source": "https://github.com/laravel/serializable-closure" }, - "time": "2025-02-11T15:03:05+00:00" + "time": "2025-03-19T13:51:03+00:00" }, { "name": "laravel/socialite", @@ -4294,16 +4237,16 @@ }, { "name": "livewire/livewire", - "version": "v3.5.20", + "version": "v3.6.2", "source": { "type": "git", "url": "https://github.com/livewire/livewire.git", - "reference": "509f2258c51741f6d06deb65d4437654520694e6" + "reference": "8f8914731f5eb43b6bb145d87c8d5a9edfc89313" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/livewire/livewire/zipball/509f2258c51741f6d06deb65d4437654520694e6", - "reference": "509f2258c51741f6d06deb65d4437654520694e6", + "url": "https://api.github.com/repos/livewire/livewire/zipball/8f8914731f5eb43b6bb145d87c8d5a9edfc89313", + "reference": "8f8914731f5eb43b6bb145d87c8d5a9edfc89313", "shasum": "" }, "require": { @@ -4358,7 +4301,7 @@ "description": "A front-end framework for Laravel.", "support": { "issues": "https://github.com/livewire/livewire/issues", - "source": "https://github.com/livewire/livewire/tree/v3.5.20" + "source": "https://github.com/livewire/livewire/tree/v3.6.2" }, "funding": [ { @@ -4366,20 +4309,20 @@ "type": "github" } ], - "time": "2025-02-13T21:05:24+00:00" + "time": "2025-03-12T20:24:15+00:00" }, { "name": "log1x/laravel-webfonts", - "version": "v1.0.2", + "version": "v2.0.1", "source": { "type": "git", "url": "https://github.com/Log1x/laravel-webfonts.git", - "reference": "128a20af26f02db84df21abc6524e5a069cf20a4" + "reference": "41bea5529ff2fe0c7969e3b9fed2ee55b95c8b60" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Log1x/laravel-webfonts/zipball/128a20af26f02db84df21abc6524e5a069cf20a4", - "reference": "128a20af26f02db84df21abc6524e5a069cf20a4", + "url": "https://api.github.com/repos/Log1x/laravel-webfonts/zipball/41bea5529ff2fe0c7969e3b9fed2ee55b95c8b60", + "reference": "41bea5529ff2fe0c7969e3b9fed2ee55b95c8b60", "shasum": "" }, "require": { @@ -4388,9 +4331,9 @@ "php": ">=8.1" }, "require-dev": { - "illuminate/console": "^10.0|^11.0", - "illuminate/http": "^10.0|^11.0", - "illuminate/support": "^10.0|^11.0", + "illuminate/console": "^10.0|^11.0|^12.0", + "illuminate/http": "^10.0|^11.0|^12.0", + "illuminate/support": "^10.0|^11.0|^12.0", "laravel/pint": "^1.13" }, "type": "package", @@ -4420,7 +4363,7 @@ "description": "Download, install, and preload over 1500 Google fonts locally in your Laravel project", "support": { "issues": "https://github.com/Log1x/laravel-webfonts/issues", - "source": "https://github.com/Log1x/laravel-webfonts/tree/v1.0.2" + "source": "https://github.com/Log1x/laravel-webfonts/tree/v2.0.1" }, "funding": [ { @@ -4428,31 +4371,31 @@ "type": "github" } ], - "time": "2024-11-12T19:00:31+00:00" + "time": "2025-02-28T20:07:12+00:00" }, { "name": "lorisleiva/laravel-actions", - "version": "v2.8.6", + "version": "v2.9.0", "source": { "type": "git", "url": "https://github.com/lorisleiva/laravel-actions.git", - "reference": "4647523599bee13cfd6b9bc9acdaf4503d4801ce" + "reference": "807f9cbd8fdb60713dfd9b2175861052f933c6e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/lorisleiva/laravel-actions/zipball/4647523599bee13cfd6b9bc9acdaf4503d4801ce", - "reference": "4647523599bee13cfd6b9bc9acdaf4503d4801ce", + "url": "https://api.github.com/repos/lorisleiva/laravel-actions/zipball/807f9cbd8fdb60713dfd9b2175861052f933c6e5", + "reference": "807f9cbd8fdb60713dfd9b2175861052f933c6e5", "shasum": "" }, "require": { - "illuminate/contracts": "^10.0|^11.0", - "lorisleiva/lody": "^0.5", + "illuminate/contracts": "^10.0|^11.0|^12.0", + "lorisleiva/lody": "^0.6", "php": "^8.1" }, "require-dev": { - "orchestra/testbench": "^8.0|^9.0", - "pestphp/pest": "^1.23|^2.34", - "phpunit/phpunit": "^9.6|^10.0" + "orchestra/testbench": "^10.0", + "pestphp/pest": "^2.34|^3.0", + "phpunit/phpunit": "^10.5|^11.5" }, "type": "library", "extra": { @@ -4496,7 +4439,7 @@ ], "support": { "issues": "https://github.com/lorisleiva/laravel-actions/issues", - "source": "https://github.com/lorisleiva/laravel-actions/tree/v2.8.6" + "source": "https://github.com/lorisleiva/laravel-actions/tree/v2.9.0" }, "funding": [ { @@ -4504,30 +4447,30 @@ "type": "github" } ], - "time": "2025-02-04T08:36:29+00:00" + "time": "2025-03-01T19:32:31+00:00" }, { "name": "lorisleiva/lody", - "version": "v0.5.0", + "version": "v0.6.0", "source": { "type": "git", "url": "https://github.com/lorisleiva/lody.git", - "reference": "c2f51b070e99f3a240d66cf68ef1f232036917fe" + "reference": "6bada710ebc75f06fdf62db26327be1592c4f014" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/lorisleiva/lody/zipball/c2f51b070e99f3a240d66cf68ef1f232036917fe", - "reference": "c2f51b070e99f3a240d66cf68ef1f232036917fe", + "url": "https://api.github.com/repos/lorisleiva/lody/zipball/6bada710ebc75f06fdf62db26327be1592c4f014", + "reference": "6bada710ebc75f06fdf62db26327be1592c4f014", "shasum": "" }, "require": { - "illuminate/contracts": "^9.0|^10.0|^11.0", - "php": "^8.0" + "illuminate/contracts": "^10.0|^11.0|^12.0", + "php": "^8.1" }, "require-dev": { - "orchestra/testbench": "^9.0", - "pestphp/pest": "^1.20|^2.34", - "phpunit/phpunit": "^9.5.10|^10.5" + "orchestra/testbench": "^10.0", + "pestphp/pest": "^2.34|^3.0", + "phpunit/phpunit": "^10.5|^11.5" }, "type": "library", "extra": { @@ -4568,7 +4511,7 @@ ], "support": { "issues": "https://github.com/lorisleiva/lody/issues", - "source": "https://github.com/lorisleiva/lody/tree/v0.5.0" + "source": "https://github.com/lorisleiva/lody/tree/v0.6.0" }, "funding": [ { @@ -4576,20 +4519,20 @@ "type": "github" } ], - "time": "2024-03-13T12:08:59+00:00" + "time": "2025-03-01T19:21:17+00:00" }, { "name": "monolog/monolog", - "version": "3.8.1", + "version": "3.9.0", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "aef6ee73a77a66e404dd6540934a9ef1b3c855b4" + "reference": "10d85740180ecba7896c87e06a166e0c95a0e3b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/aef6ee73a77a66e404dd6540934a9ef1b3c855b4", - "reference": "aef6ee73a77a66e404dd6540934a9ef1b3c855b4", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/10d85740180ecba7896c87e06a166e0c95a0e3b6", + "reference": "10d85740180ecba7896c87e06a166e0c95a0e3b6", "shasum": "" }, "require": { @@ -4667,7 +4610,7 @@ ], "support": { "issues": "https://github.com/Seldaek/monolog/issues", - "source": "https://github.com/Seldaek/monolog/tree/3.8.1" + "source": "https://github.com/Seldaek/monolog/tree/3.9.0" }, "funding": [ { @@ -4679,7 +4622,7 @@ "type": "tidelift" } ], - "time": "2024-12-05T17:15:07+00:00" + "time": "2025-03-24T10:02:05+00:00" }, { "name": "mtdowling/jmespath.php", @@ -4917,16 +4860,16 @@ }, { "name": "nette/utils", - "version": "v4.0.5", + "version": "v4.0.6", "source": { "type": "git", "url": "https://github.com/nette/utils.git", - "reference": "736c567e257dbe0fcf6ce81b4d6dbe05c6899f96" + "reference": "ce708655043c7050eb050df361c5e313cf708309" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/736c567e257dbe0fcf6ce81b4d6dbe05c6899f96", - "reference": "736c567e257dbe0fcf6ce81b4d6dbe05c6899f96", + "url": "https://api.github.com/repos/nette/utils/zipball/ce708655043c7050eb050df361c5e313cf708309", + "reference": "ce708655043c7050eb050df361c5e313cf708309", "shasum": "" }, "require": { @@ -4997,9 +4940,9 @@ ], "support": { "issues": "https://github.com/nette/utils/issues", - "source": "https://github.com/nette/utils/tree/v4.0.5" + "source": "https://github.com/nette/utils/tree/v4.0.6" }, - "time": "2024-08-07T15:39:19+00:00" + "time": "2025-03-30T21:06:30+00:00" }, { "name": "nikic/php-parser", @@ -5542,16 +5485,16 @@ }, { "name": "php-di/php-di", - "version": "7.0.8", + "version": "7.0.9", "source": { "type": "git", "url": "https://github.com/PHP-DI/PHP-DI.git", - "reference": "98ddc81f8f768a2ad39e4cbe737285eaeabe577a" + "reference": "d8480267f5cf239650debba704f3ecd15b638cde" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/98ddc81f8f768a2ad39e4cbe737285eaeabe577a", - "reference": "98ddc81f8f768a2ad39e4cbe737285eaeabe577a", + "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/d8480267f5cf239650debba704f3ecd15b638cde", + "reference": "d8480267f5cf239650debba704f3ecd15b638cde", "shasum": "" }, "require": { @@ -5568,7 +5511,7 @@ "friendsofphp/proxy-manager-lts": "^1", "mnapoli/phpunit-easymock": "^1.3", "phpunit/phpunit": "^9.6", - "vimeo/psalm": "^4.6" + "vimeo/psalm": "^5|^6" }, "suggest": { "friendsofphp/proxy-manager-lts": "Install it if you want to use lazy injection (version ^1)" @@ -5599,7 +5542,7 @@ ], "support": { "issues": "https://github.com/PHP-DI/PHP-DI/issues", - "source": "https://github.com/PHP-DI/PHP-DI/tree/7.0.8" + "source": "https://github.com/PHP-DI/PHP-DI/tree/7.0.9" }, "funding": [ { @@ -5611,7 +5554,7 @@ "type": "tidelift" } ], - "time": "2025-01-28T21:02:46+00:00" + "time": "2025-02-28T12:46:35+00:00" }, { "name": "phpdocumentor/reflection", @@ -6089,23 +6032,23 @@ }, { "name": "pion/laravel-chunk-upload", - "version": "v1.5.4", + "version": "v1.5.6", "source": { "type": "git", "url": "https://github.com/pionl/laravel-chunk-upload.git", - "reference": "cfbc4292ddcace51308a4f2f446d310aa04e6133" + "reference": "5cfdb8d9058bb4ecdf3a3100b6c7bb197c21e4d4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pionl/laravel-chunk-upload/zipball/cfbc4292ddcace51308a4f2f446d310aa04e6133", - "reference": "cfbc4292ddcace51308a4f2f446d310aa04e6133", + "url": "https://api.github.com/repos/pionl/laravel-chunk-upload/zipball/5cfdb8d9058bb4ecdf3a3100b6c7bb197c21e4d4", + "reference": "5cfdb8d9058bb4ecdf3a3100b6c7bb197c21e4d4", "shasum": "" }, "require": { - "illuminate/console": "5.2 - 5.8 | ^6.0 | ^7.0 | ^8.0 | ^9.0 | ^10.0 | ^11.0", - "illuminate/filesystem": "5.2 - 5.8 | ^6.0 | ^7.0 | ^8.0 | ^9.0 | ^10.0 | ^11.0", - "illuminate/http": "5.2 - 5.8 | ^6.0 | ^7.0 | ^8.0 | ^9.0 | ^10.0 | ^11.0", - "illuminate/support": "5.2 - 5.8 | ^6.0 | ^7.0 | ^8.0 | ^9.0 | ^10.0 | ^11.0" + "illuminate/console": "5.2 - 5.8 | ^6.0 | ^7.0 | ^8.0 | ^9.0 | ^10.0 | ^11.0 | ^12.0", + "illuminate/filesystem": "5.2 - 5.8 | ^6.0 | ^7.0 | ^8.0 | ^9.0 | ^10.0 | ^11.0 | ^12.0", + "illuminate/http": "5.2 - 5.8 | ^6.0 | ^7.0 | ^8.0 | ^9.0 | ^10.0 | ^11.0 | ^12.0", + "illuminate/support": "5.2 - 5.8 | ^6.0 | ^7.0 | ^8.0 | ^9.0 | ^10.0 | ^11.0 | ^12.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "^2.16.0 | ^3.52.0", @@ -6139,7 +6082,7 @@ "description": "Service for chunked upload with several js providers", "support": { "issues": "https://github.com/pionl/laravel-chunk-upload/issues", - "source": "https://github.com/pionl/laravel-chunk-upload/tree/v1.5.4" + "source": "https://github.com/pionl/laravel-chunk-upload/tree/v1.5.6" }, "funding": [ { @@ -6151,7 +6094,7 @@ "type": "github" } ], - "time": "2024-03-25T15:50:07+00:00" + "time": "2025-03-19T16:30:08+00:00" }, { "name": "poliander/cron", @@ -6712,16 +6655,16 @@ }, { "name": "psy/psysh", - "version": "v0.12.7", + "version": "v0.12.8", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "d73fa3c74918ef4522bb8a3bf9cab39161c4b57c" + "reference": "85057ceedee50c49d4f6ecaff73ee96adb3b3625" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/d73fa3c74918ef4522bb8a3bf9cab39161c4b57c", - "reference": "d73fa3c74918ef4522bb8a3bf9cab39161c4b57c", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/85057ceedee50c49d4f6ecaff73ee96adb3b3625", + "reference": "85057ceedee50c49d4f6ecaff73ee96adb3b3625", "shasum": "" }, "require": { @@ -6785,9 +6728,9 @@ ], "support": { "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.12.7" + "source": "https://github.com/bobthecow/psysh/tree/v0.12.8" }, - "time": "2024-12-10T01:58:33+00:00" + "time": "2025-03-16T03:05:19+00:00" }, { "name": "purplepixie/phpdns", @@ -6944,16 +6887,16 @@ }, { "name": "ramsey/collection", - "version": "2.1.0", + "version": "2.1.1", "source": { "type": "git", "url": "https://github.com/ramsey/collection.git", - "reference": "3c5990b8a5e0b79cd1cf11c2dc1229e58e93f109" + "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/collection/zipball/3c5990b8a5e0b79cd1cf11c2dc1229e58e93f109", - "reference": "3c5990b8a5e0b79cd1cf11c2dc1229e58e93f109", + "url": "https://api.github.com/repos/ramsey/collection/zipball/344572933ad0181accbf4ba763e85a0306a8c5e2", + "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2", "shasum": "" }, "require": { @@ -7014,9 +6957,9 @@ ], "support": { "issues": "https://github.com/ramsey/collection/issues", - "source": "https://github.com/ramsey/collection/tree/2.1.0" + "source": "https://github.com/ramsey/collection/tree/2.1.1" }, - "time": "2025-03-02T04:48:29+00:00" + "time": "2025-03-22T05:38:12+00:00" }, { "name": "ramsey/uuid", @@ -7112,30 +7055,30 @@ }, { "name": "resend/resend-laravel", - "version": "v0.15.0", + "version": "v0.17.0", "source": { "type": "git", "url": "https://github.com/resend/resend-laravel.git", - "reference": "af914817abc6abaa4522b5cfb177f3519493fd6e" + "reference": "fbdf51872ff296af545d72acc6de03c0d910202c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/resend/resend-laravel/zipball/af914817abc6abaa4522b5cfb177f3519493fd6e", - "reference": "af914817abc6abaa4522b5cfb177f3519493fd6e", + "url": "https://api.github.com/repos/resend/resend-laravel/zipball/fbdf51872ff296af545d72acc6de03c0d910202c", + "reference": "fbdf51872ff296af545d72acc6de03c0d910202c", "shasum": "" }, "require": { - "illuminate/http": "^10.0|^11.0", - "illuminate/support": "^10.0|^11.0", + "illuminate/http": "^10.0|^11.0|^12.0", + "illuminate/support": "^10.0|^11.0|^12.0", "php": "^8.1", - "resend/resend-php": "^0.14.0", + "resend/resend-php": "^0.16.0", "symfony/mailer": "^6.2|^7.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "^3.14", "mockery/mockery": "^1.5", - "orchestra/testbench": "^8.17|^9.0", - "pestphp/pest": "^2.0" + "orchestra/testbench": "^8.17|^9.0|^10.0", + "pestphp/pest": "^2.0|^3.7" }, "type": "library", "extra": { @@ -7175,22 +7118,22 @@ ], "support": { "issues": "https://github.com/resend/resend-laravel/issues", - "source": "https://github.com/resend/resend-laravel/tree/v0.15.0" + "source": "https://github.com/resend/resend-laravel/tree/v0.17.0" }, - "time": "2024-11-04T18:34:08+00:00" + "time": "2025-03-25T00:42:52+00:00" }, { "name": "resend/resend-php", - "version": "v0.14.0", + "version": "v0.16.0", "source": { "type": "git", "url": "https://github.com/resend/resend-php.git", - "reference": "d7900752bb9839421d40d9e66362bffb3ec07aac" + "reference": "6e9be898c9e0035a5da3c2904e86d0b12999c2fc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/resend/resend-php/zipball/d7900752bb9839421d40d9e66362bffb3ec07aac", - "reference": "d7900752bb9839421d40d9e66362bffb3ec07aac", + "url": "https://api.github.com/repos/resend/resend-php/zipball/6e9be898c9e0035a5da3c2904e86d0b12999c2fc", + "reference": "6e9be898c9e0035a5da3c2904e86d0b12999c2fc", "shasum": "" }, "require": { @@ -7232,9 +7175,9 @@ ], "support": { "issues": "https://github.com/resend/resend-php/issues", - "source": "https://github.com/resend/resend-php/tree/v0.14.0" + "source": "https://github.com/resend/resend-php/tree/v0.16.0" }, - "time": "2024-11-01T02:00:44+00:00" + "time": "2025-03-24T22:12:48+00:00" }, { "name": "revolt/event-loop", @@ -7630,20 +7573,20 @@ }, { "name": "socialiteproviders/manager", - "version": "v4.8.0", + "version": "v4.8.1", "source": { "type": "git", "url": "https://github.com/SocialiteProviders/Manager.git", - "reference": "e93acc38f8464cc775a2b8bf09df311d1fdfefcb" + "reference": "8180ec14bef230ec2351cff993d5d2d7ca470ef4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/SocialiteProviders/Manager/zipball/e93acc38f8464cc775a2b8bf09df311d1fdfefcb", - "reference": "e93acc38f8464cc775a2b8bf09df311d1fdfefcb", + "url": "https://api.github.com/repos/SocialiteProviders/Manager/zipball/8180ec14bef230ec2351cff993d5d2d7ca470ef4", + "reference": "8180ec14bef230ec2351cff993d5d2d7ca470ef4", "shasum": "" }, "require": { - "illuminate/support": "^8.0 || ^9.0 || ^10.0 || ^11.0", + "illuminate/support": "^8.0 || ^9.0 || ^10.0 || ^11.0 || ^12.0", "laravel/socialite": "^5.5", "php": "^8.1" }, @@ -7700,7 +7643,7 @@ "issues": "https://github.com/socialiteproviders/manager/issues", "source": "https://github.com/socialiteproviders/manager" }, - "time": "2025-01-03T09:40:37+00:00" + "time": "2025-02-24T19:33:30+00:00" }, { "name": "socialiteproviders/microsoft-azure", @@ -7909,16 +7852,16 @@ }, { "name": "spatie/laravel-data", - "version": "4.13.1", + "version": "4.14.1", "source": { "type": "git", "url": "https://github.com/spatie/laravel-data.git", - "reference": "14252330397cf84647cc086324ea1a30dbe5f7cc" + "reference": "edd61b4dca5acdcfd1e3b7f2c19b75e83730f87c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-data/zipball/14252330397cf84647cc086324ea1a30dbe5f7cc", - "reference": "14252330397cf84647cc086324ea1a30dbe5f7cc", + "url": "https://api.github.com/repos/spatie/laravel-data/zipball/edd61b4dca5acdcfd1e3b7f2c19b75e83730f87c", + "reference": "edd61b4dca5acdcfd1e3b7f2c19b75e83730f87c", "shasum": "" }, "require": { @@ -7979,7 +7922,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-data/issues", - "source": "https://github.com/spatie/laravel-data/tree/4.13.1" + "source": "https://github.com/spatie/laravel-data/tree/4.14.1" }, "funding": [ { @@ -7987,20 +7930,20 @@ "type": "github" } ], - "time": "2025-02-14T14:29:16+00:00" + "time": "2025-03-17T13:54:28+00:00" }, { "name": "spatie/laravel-package-tools", - "version": "1.19.0", + "version": "1.92.0", "source": { "type": "git", "url": "https://github.com/spatie/laravel-package-tools.git", - "reference": "1c9c30ac6a6576b8d15c6c37b6cf23d748df2faa" + "reference": "dd46cd0ed74015db28822d88ad2e667f4496a6f6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/1c9c30ac6a6576b8d15c6c37b6cf23d748df2faa", - "reference": "1c9c30ac6a6576b8d15c6c37b6cf23d748df2faa", + "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/dd46cd0ed74015db28822d88ad2e667f4496a6f6", + "reference": "dd46cd0ed74015db28822d88ad2e667f4496a6f6", "shasum": "" }, "require": { @@ -8039,7 +7982,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-package-tools/issues", - "source": "https://github.com/spatie/laravel-package-tools/tree/1.19.0" + "source": "https://github.com/spatie/laravel-package-tools/tree/1.92.0" }, "funding": [ { @@ -8047,20 +7990,20 @@ "type": "github" } ], - "time": "2025-02-06T14:58:20+00:00" + "time": "2025-03-27T08:34:10+00:00" }, { "name": "spatie/laravel-ray", - "version": "1.39.1", + "version": "1.40.2", "source": { "type": "git", "url": "https://github.com/spatie/laravel-ray.git", - "reference": "0d890fa2cd2c0b6175cf54c56b9321d81047571d" + "reference": "1d1b31eb83cb38b41975c37363c7461de6d86b25" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-ray/zipball/0d890fa2cd2c0b6175cf54c56b9321d81047571d", - "reference": "0d890fa2cd2c0b6175cf54c56b9321d81047571d", + "url": "https://api.github.com/repos/spatie/laravel-ray/zipball/1d1b31eb83cb38b41975c37363c7461de6d86b25", + "reference": "1d1b31eb83cb38b41975c37363c7461de6d86b25", "shasum": "" }, "require": { @@ -8123,7 +8066,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-ray/issues", - "source": "https://github.com/spatie/laravel-ray/tree/1.39.1" + "source": "https://github.com/spatie/laravel-ray/tree/1.40.2" }, "funding": [ { @@ -8135,7 +8078,7 @@ "type": "other" } ], - "time": "2025-02-05T08:16:15+00:00" + "time": "2025-03-27T08:26:55+00:00" }, { "name": "spatie/laravel-schemaless-attributes", @@ -8344,16 +8287,16 @@ }, { "name": "spatie/ray", - "version": "1.41.5", + "version": "1.41.6", "source": { "type": "git", "url": "https://github.com/spatie/ray.git", - "reference": "9d078f04ffa32ad543a20716844ec343fdd7d856" + "reference": "ae6e32a54a901544a3d70b12b865900bc240f71c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/ray/zipball/9d078f04ffa32ad543a20716844ec343fdd7d856", - "reference": "9d078f04ffa32ad543a20716844ec343fdd7d856", + "url": "https://api.github.com/repos/spatie/ray/zipball/ae6e32a54a901544a3d70b12b865900bc240f71c", + "reference": "ae6e32a54a901544a3d70b12b865900bc240f71c", "shasum": "" }, "require": { @@ -8413,7 +8356,7 @@ ], "support": { "issues": "https://github.com/spatie/ray/issues", - "source": "https://github.com/spatie/ray/tree/1.41.5" + "source": "https://github.com/spatie/ray/tree/1.41.6" }, "funding": [ { @@ -8425,7 +8368,7 @@ "type": "other" } ], - "time": "2025-02-14T12:51:43+00:00" + "time": "2025-03-21T08:56:30+00:00" }, { "name": "spatie/url", @@ -8557,16 +8500,16 @@ }, { "name": "stripe/stripe-php", - "version": "v16.5.1", + "version": "v16.6.0", "source": { "type": "git", "url": "https://github.com/stripe/stripe-php.git", - "reference": "05c7c3a8a15b1bc396f09d17c88539c0db3d3255" + "reference": "d6de0a536f00b5c5c74f36b8f4d0d93b035499ff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/stripe/stripe-php/zipball/05c7c3a8a15b1bc396f09d17c88539c0db3d3255", - "reference": "05c7c3a8a15b1bc396f09d17c88539c0db3d3255", + "url": "https://api.github.com/repos/stripe/stripe-php/zipball/d6de0a536f00b5c5c74f36b8f4d0d93b035499ff", + "reference": "d6de0a536f00b5c5c74f36b8f4d0d93b035499ff", "shasum": "" }, "require": { @@ -8610,9 +8553,9 @@ ], "support": { "issues": "https://github.com/stripe/stripe-php/issues", - "source": "https://github.com/stripe/stripe-php/tree/v16.5.1" + "source": "https://github.com/stripe/stripe-php/tree/v16.6.0" }, - "time": "2025-02-07T21:24:29+00:00" + "time": "2025-02-24T22:35:29+00:00" }, { "name": "symfony/clock", @@ -8690,16 +8633,16 @@ }, { "name": "symfony/console", - "version": "v7.2.1", + "version": "v7.2.5", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3" + "reference": "e51498ea18570c062e7df29d05a7003585b19b88" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/fefcc18c0f5d0efe3ab3152f15857298868dc2c3", - "reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3", + "url": "https://api.github.com/repos/symfony/console/zipball/e51498ea18570c062e7df29d05a7003585b19b88", + "reference": "e51498ea18570c062e7df29d05a7003585b19b88", "shasum": "" }, "require": { @@ -8763,7 +8706,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.2.1" + "source": "https://github.com/symfony/console/tree/v7.2.5" }, "funding": [ { @@ -8779,7 +8722,7 @@ "type": "tidelift" } ], - "time": "2024-12-11T03:49:26+00:00" + "time": "2025-03-12T08:11:12+00:00" }, { "name": "symfony/css-selector", @@ -8915,16 +8858,16 @@ }, { "name": "symfony/error-handler", - "version": "v7.2.4", + "version": "v7.2.5", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "aabf79938aa795350c07ce6464dd1985607d95d5" + "reference": "102be5e6a8e4f4f3eb3149bcbfa33a80d1ee374b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/aabf79938aa795350c07ce6464dd1985607d95d5", - "reference": "aabf79938aa795350c07ce6464dd1985607d95d5", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/102be5e6a8e4f4f3eb3149bcbfa33a80d1ee374b", + "reference": "102be5e6a8e4f4f3eb3149bcbfa33a80d1ee374b", "shasum": "" }, "require": { @@ -8970,7 +8913,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.2.4" + "source": "https://github.com/symfony/error-handler/tree/v7.2.5" }, "funding": [ { @@ -8986,7 +8929,7 @@ "type": "tidelift" } ], - "time": "2025-02-02T20:27:07+00:00" + "time": "2025-03-03T07:12:39+00:00" }, { "name": "symfony/event-dispatcher", @@ -9210,16 +9153,16 @@ }, { "name": "symfony/http-foundation", - "version": "v7.2.3", + "version": "v7.2.5", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "ee1b504b8926198be89d05e5b6fc4c3810c090f0" + "reference": "371272aeb6286f8135e028ca535f8e4d6f114126" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/ee1b504b8926198be89d05e5b6fc4c3810c090f0", - "reference": "ee1b504b8926198be89d05e5b6fc4c3810c090f0", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/371272aeb6286f8135e028ca535f8e4d6f114126", + "reference": "371272aeb6286f8135e028ca535f8e4d6f114126", "shasum": "" }, "require": { @@ -9268,7 +9211,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.2.3" + "source": "https://github.com/symfony/http-foundation/tree/v7.2.5" }, "funding": [ { @@ -9284,20 +9227,20 @@ "type": "tidelift" } ], - "time": "2025-01-17T10:56:55+00:00" + "time": "2025-03-25T15:54:33+00:00" }, { "name": "symfony/http-kernel", - "version": "v7.2.4", + "version": "v7.2.5", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "9f1103734c5789798fefb90e91de4586039003ed" + "reference": "b1fe91bc1fa454a806d3f98db4ba826eb9941a54" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/9f1103734c5789798fefb90e91de4586039003ed", - "reference": "9f1103734c5789798fefb90e91de4586039003ed", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/b1fe91bc1fa454a806d3f98db4ba826eb9941a54", + "reference": "b1fe91bc1fa454a806d3f98db4ba826eb9941a54", "shasum": "" }, "require": { @@ -9382,7 +9325,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.2.4" + "source": "https://github.com/symfony/http-kernel/tree/v7.2.5" }, "funding": [ { @@ -9398,7 +9341,7 @@ "type": "tidelift" } ], - "time": "2025-02-26T11:01:22+00:00" + "time": "2025-03-28T13:32:50+00:00" }, { "name": "symfony/mailer", @@ -10349,16 +10292,16 @@ }, { "name": "symfony/process", - "version": "v7.2.4", + "version": "v7.2.5", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "d8f411ff3c7ddc4ae9166fb388d1190a2df5b5cf" + "reference": "87b7c93e57df9d8e39a093d32587702380ff045d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/d8f411ff3c7ddc4ae9166fb388d1190a2df5b5cf", - "reference": "d8f411ff3c7ddc4ae9166fb388d1190a2df5b5cf", + "url": "https://api.github.com/repos/symfony/process/zipball/87b7c93e57df9d8e39a093d32587702380ff045d", + "reference": "87b7c93e57df9d8e39a093d32587702380ff045d", "shasum": "" }, "require": { @@ -10390,7 +10333,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.2.4" + "source": "https://github.com/symfony/process/tree/v7.2.5" }, "funding": [ { @@ -10406,7 +10349,7 @@ "type": "tidelift" } ], - "time": "2025-02-05T08:33:46+00:00" + "time": "2025-03-13T12:21:46+00:00" }, { "name": "symfony/psr-http-message-bridge", @@ -10657,16 +10600,16 @@ }, { "name": "symfony/stopwatch", - "version": "v7.2.2", + "version": "v7.2.4", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "e46690d5b9d7164a6d061cab1e8d46141b9f49df" + "reference": "5a49289e2b308214c8b9c2fda4ea454d8b8ad7cd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/e46690d5b9d7164a6d061cab1e8d46141b9f49df", - "reference": "e46690d5b9d7164a6d061cab1e8d46141b9f49df", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/5a49289e2b308214c8b9c2fda4ea454d8b8ad7cd", + "reference": "5a49289e2b308214c8b9c2fda4ea454d8b8ad7cd", "shasum": "" }, "require": { @@ -10699,7 +10642,7 @@ "description": "Provides a way to profile code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/stopwatch/tree/v7.2.2" + "source": "https://github.com/symfony/stopwatch/tree/v7.2.4" }, "funding": [ { @@ -10715,7 +10658,7 @@ "type": "tidelift" } ], - "time": "2024-12-18T14:28:33+00:00" + "time": "2025-02-24T10:49:57+00:00" }, { "name": "symfony/string", @@ -11136,16 +11079,16 @@ }, { "name": "symfony/yaml", - "version": "v7.2.3", + "version": "v7.2.5", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "ac238f173df0c9c1120f862d0f599e17535a87ec" + "reference": "4c4b6f4cfcd7e52053f0c8bfad0f7f30fb924912" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/ac238f173df0c9c1120f862d0f599e17535a87ec", - "reference": "ac238f173df0c9c1120f862d0f599e17535a87ec", + "url": "https://api.github.com/repos/symfony/yaml/zipball/4c4b6f4cfcd7e52053f0c8bfad0f7f30fb924912", + "reference": "4c4b6f4cfcd7e52053f0c8bfad0f7f30fb924912", "shasum": "" }, "require": { @@ -11188,7 +11131,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v7.2.3" + "source": "https://github.com/symfony/yaml/tree/v7.2.5" }, "funding": [ { @@ -11204,7 +11147,7 @@ "type": "tidelift" } ], - "time": "2025-01-07T12:55:42+00:00" + "time": "2025-03-03T07:12:39+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", @@ -11855,16 +11798,16 @@ }, { "name": "zircote/swagger-php", - "version": "5.0.5", + "version": "5.0.7", "source": { "type": "git", "url": "https://github.com/zircote/swagger-php.git", - "reference": "2eb4005840058d8844a0bcc14403932331331068" + "reference": "18457fa71f753cfd4a2b21916baf329864fdfaa6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zircote/swagger-php/zipball/2eb4005840058d8844a0bcc14403932331331068", - "reference": "2eb4005840058d8844a0bcc14403932331331068", + "url": "https://api.github.com/repos/zircote/swagger-php/zipball/18457fa71f753cfd4a2b21916baf329864fdfaa6", + "reference": "18457fa71f753cfd4a2b21916baf329864fdfaa6", "shasum": "" }, "require": { @@ -11935,24 +11878,24 @@ ], "support": { "issues": "https://github.com/zircote/swagger-php/issues", - "source": "https://github.com/zircote/swagger-php/tree/5.0.5" + "source": "https://github.com/zircote/swagger-php/tree/5.0.7" }, - "time": "2025-02-24T00:48:00+00:00" + "time": "2025-03-19T03:31:11+00:00" } ], "packages-dev": [ { "name": "barryvdh/laravel-debugbar", - "version": "v3.15.1", + "version": "v3.15.2", "source": { "type": "git", "url": "https://github.com/barryvdh/laravel-debugbar.git", - "reference": "62b222166a9505926d54fadf7663bb4a26ed9014" + "reference": "0bc1e1361e7fffc2be156f46ad1fba6927c01729" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/62b222166a9505926d54fadf7663bb4a26ed9014", - "reference": "62b222166a9505926d54fadf7663bb4a26ed9014", + "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/0bc1e1361e7fffc2be156f46ad1fba6927c01729", + "reference": "0bc1e1361e7fffc2be156f46ad1fba6927c01729", "shasum": "" }, "require": { @@ -12015,7 +11958,7 @@ ], "support": { "issues": "https://github.com/barryvdh/laravel-debugbar/issues", - "source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.15.1" + "source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.15.2" }, "funding": [ { @@ -12027,20 +11970,20 @@ "type": "github" } ], - "time": "2025-02-24T10:48:28+00:00" + "time": "2025-02-25T15:25:22+00:00" }, { "name": "brianium/paratest", - "version": "v7.7.0", + "version": "v7.8.3", "source": { "type": "git", "url": "https://github.com/paratestphp/paratest.git", - "reference": "4fb3f73bc5a4c3146bac2850af7dc72435a32daf" + "reference": "a585c346ddf1bec22e51e20b5387607905604a71" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paratestphp/paratest/zipball/4fb3f73bc5a4c3146bac2850af7dc72435a32daf", - "reference": "4fb3f73bc5a4c3146bac2850af7dc72435a32daf", + "url": "https://api.github.com/repos/paratestphp/paratest/zipball/a585c346ddf1bec22e51e20b5387607905604a71", + "reference": "a585c346ddf1bec22e51e20b5387607905604a71", "shasum": "" }, "require": { @@ -12051,23 +11994,23 @@ "fidry/cpu-core-counter": "^1.2.0", "jean85/pretty-package-versions": "^2.1.0", "php": "~8.2.0 || ~8.3.0 || ~8.4.0", - "phpunit/php-code-coverage": "^11.0.8", - "phpunit/php-file-iterator": "^5.1.0", - "phpunit/php-timer": "^7.0.1", - "phpunit/phpunit": "^11.5.1", - "sebastian/environment": "^7.2.0", - "symfony/console": "^6.4.14 || ^7.2.1", - "symfony/process": "^6.4.14 || ^7.2.0" + "phpunit/php-code-coverage": "^11.0.9 || ^12.0.4", + "phpunit/php-file-iterator": "^5.1.0 || ^6", + "phpunit/php-timer": "^7.0.1 || ^8", + "phpunit/phpunit": "^11.5.11 || ^12.0.6", + "sebastian/environment": "^7.2.0 || ^8", + "symfony/console": "^6.4.17 || ^7.2.1", + "symfony/process": "^6.4.19 || ^7.2.4" }, "require-dev": { "doctrine/coding-standard": "^12.0.0", "ext-pcov": "*", "ext-posix": "*", - "phpstan/phpstan": "^2.0.3", + "phpstan/phpstan": "^2.1.6", "phpstan/phpstan-deprecation-rules": "^2.0.1", - "phpstan/phpstan-phpunit": "^2.0.1", - "phpstan/phpstan-strict-rules": "^2", - "squizlabs/php_codesniffer": "^3.11.1", + "phpstan/phpstan-phpunit": "^2.0.4", + "phpstan/phpstan-strict-rules": "^2.0.3", + "squizlabs/php_codesniffer": "^3.11.3", "symfony/filesystem": "^6.4.13 || ^7.2.0" }, "bin": [ @@ -12108,7 +12051,7 @@ ], "support": { "issues": "https://github.com/paratestphp/paratest/issues", - "source": "https://github.com/paratestphp/paratest/tree/v7.7.0" + "source": "https://github.com/paratestphp/paratest/tree/v7.8.3" }, "funding": [ { @@ -12120,7 +12063,7 @@ "type": "paypal" } ], - "time": "2024-12-11T14:50:44+00:00" + "time": "2025-03-05T08:29:11+00:00" }, { "name": "driftingly/rector-laravel", @@ -12283,16 +12226,16 @@ }, { "name": "filp/whoops", - "version": "2.17.0", + "version": "2.18.0", "source": { "type": "git", "url": "https://github.com/filp/whoops.git", - "reference": "075bc0c26631110584175de6523ab3f1652eb28e" + "reference": "a7de6c3c6c3c022f5cfc337f8ede6a14460cf77e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/075bc0c26631110584175de6523ab3f1652eb28e", - "reference": "075bc0c26631110584175de6523ab3f1652eb28e", + "url": "https://api.github.com/repos/filp/whoops/zipball/a7de6c3c6c3c022f5cfc337f8ede6a14460cf77e", + "reference": "a7de6c3c6c3c022f5cfc337f8ede6a14460cf77e", "shasum": "" }, "require": { @@ -12342,7 +12285,7 @@ ], "support": { "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.17.0" + "source": "https://github.com/filp/whoops/tree/2.18.0" }, "funding": [ { @@ -12350,7 +12293,7 @@ "type": "github" } ], - "time": "2025-01-25T12:00:00+00:00" + "time": "2025-03-15T12:00:00+00:00" }, { "name": "hamcrest/hamcrest-php", @@ -12405,16 +12348,16 @@ }, { "name": "laravel/dusk", - "version": "v8.3.1", + "version": "v8.3.2", "source": { "type": "git", "url": "https://github.com/laravel/dusk.git", - "reference": "541ca2d2004ae4ed04446b9e712b68180fca158c" + "reference": "bb701836357bf6f6c6658ef90b5a0f8232affb0f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/dusk/zipball/541ca2d2004ae4ed04446b9e712b68180fca158c", - "reference": "541ca2d2004ae4ed04446b9e712b68180fca158c", + "url": "https://api.github.com/repos/laravel/dusk/zipball/bb701836357bf6f6c6658ef90b5a0f8232affb0f", + "reference": "bb701836357bf6f6c6658ef90b5a0f8232affb0f", "shasum": "" }, "require": { @@ -12473,22 +12416,22 @@ ], "support": { "issues": "https://github.com/laravel/dusk/issues", - "source": "https://github.com/laravel/dusk/tree/v8.3.1" + "source": "https://github.com/laravel/dusk/tree/v8.3.2" }, - "time": "2025-02-12T16:14:51+00:00" + "time": "2025-02-20T14:42:00+00:00" }, { "name": "laravel/pint", - "version": "v1.21.0", + "version": "v1.21.2", "source": { "type": "git", "url": "https://github.com/laravel/pint.git", - "reference": "531fa0871fbde719c51b12afa3a443b8f4e4b425" + "reference": "370772e7d9e9da087678a0edf2b11b6960e40558" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pint/zipball/531fa0871fbde719c51b12afa3a443b8f4e4b425", - "reference": "531fa0871fbde719c51b12afa3a443b8f4e4b425", + "url": "https://api.github.com/repos/laravel/pint/zipball/370772e7d9e9da087678a0edf2b11b6960e40558", + "reference": "370772e7d9e9da087678a0edf2b11b6960e40558", "shasum": "" }, "require": { @@ -12499,9 +12442,9 @@ "php": "^8.2.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.68.5", - "illuminate/view": "^11.42.0", - "larastan/larastan": "^3.0.4", + "friendsofphp/php-cs-fixer": "^3.72.0", + "illuminate/view": "^11.44.2", + "larastan/larastan": "^3.2.0", "laravel-zero/framework": "^11.36.1", "mockery/mockery": "^1.6.12", "nunomaduro/termwind": "^2.3", @@ -12541,20 +12484,20 @@ "issues": "https://github.com/laravel/pint/issues", "source": "https://github.com/laravel/pint" }, - "time": "2025-02-18T03:18:57+00:00" + "time": "2025-03-14T22:31:42+00:00" }, { "name": "laravel/telescope", - "version": "v5.5.0", + "version": "v5.6.0", "source": { "type": "git", "url": "https://github.com/laravel/telescope.git", - "reference": "2594b20b946155ba767002d8af971e33e1095637" + "reference": "352dc633c2fe500c169e3401a10a7efae4fc3327" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/telescope/zipball/2594b20b946155ba767002d8af971e33e1095637", - "reference": "2594b20b946155ba767002d8af971e33e1095637", + "url": "https://api.github.com/repos/laravel/telescope/zipball/352dc633c2fe500c169e3401a10a7efae4fc3327", + "reference": "352dc633c2fe500c169e3401a10a7efae4fc3327", "shasum": "" }, "require": { @@ -12608,9 +12551,9 @@ ], "support": { "issues": "https://github.com/laravel/telescope/issues", - "source": "https://github.com/laravel/telescope/tree/v5.5.0" + "source": "https://github.com/laravel/telescope/tree/v5.6.0" }, - "time": "2025-02-11T15:01:27+00:00" + "time": "2025-03-17T20:24:10+00:00" }, { "name": "mockery/mockery", @@ -12757,20 +12700,20 @@ }, { "name": "nunomaduro/collision", - "version": "v8.6.1", + "version": "v8.7.0", "source": { "type": "git", "url": "https://github.com/nunomaduro/collision.git", - "reference": "86f003c132143d5a2ab214e19933946409e0cae7" + "reference": "586cb8181a257a2152b6a855ca8d9598878a1a26" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/collision/zipball/86f003c132143d5a2ab214e19933946409e0cae7", - "reference": "86f003c132143d5a2ab214e19933946409e0cae7", + "url": "https://api.github.com/repos/nunomaduro/collision/zipball/586cb8181a257a2152b6a855ca8d9598878a1a26", + "reference": "586cb8181a257a2152b6a855ca8d9598878a1a26", "shasum": "" }, "require": { - "filp/whoops": "^2.16.0", + "filp/whoops": "^2.17.0", "nunomaduro/termwind": "^2.3.0", "php": "^8.2.0", "symfony/console": "^7.2.1" @@ -12780,14 +12723,14 @@ "phpunit/phpunit": "<11.5.3 || >=12.0.0" }, "require-dev": { - "larastan/larastan": "^2.9.12", - "laravel/framework": "^11.39.1", - "laravel/pint": "^1.20.0", - "laravel/sail": "^1.40.0", - "laravel/sanctum": "^4.0.7", - "laravel/tinker": "^2.10.0", - "orchestra/testbench-core": "^9.9.2", - "pestphp/pest": "^3.7.3", + "larastan/larastan": "^2.10.0", + "laravel/framework": "^11.44.2", + "laravel/pint": "^1.21.2", + "laravel/sail": "^1.41.0", + "laravel/sanctum": "^4.0.8", + "laravel/tinker": "^2.10.1", + "orchestra/testbench-core": "^9.12.0", + "pestphp/pest": "^3.7.4", "sebastian/environment": "^6.1.0 || ^7.2.0" }, "type": "library", @@ -12851,42 +12794,42 @@ "type": "patreon" } ], - "time": "2025-01-23T13:41:43+00:00" + "time": "2025-03-14T22:37:40+00:00" }, { "name": "pestphp/pest", - "version": "v3.7.4", + "version": "v3.8.0", "source": { "type": "git", "url": "https://github.com/pestphp/pest.git", - "reference": "4a987d3d5c4e3ba36c76fecbf56113baac2d1b2b" + "reference": "42e1b9f17fc2b2036701f4b968158264bde542d4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pestphp/pest/zipball/4a987d3d5c4e3ba36c76fecbf56113baac2d1b2b", - "reference": "4a987d3d5c4e3ba36c76fecbf56113baac2d1b2b", + "url": "https://api.github.com/repos/pestphp/pest/zipball/42e1b9f17fc2b2036701f4b968158264bde542d4", + "reference": "42e1b9f17fc2b2036701f4b968158264bde542d4", "shasum": "" }, "require": { - "brianium/paratest": "^7.7.0", - "nunomaduro/collision": "^8.6.1", + "brianium/paratest": "^7.8.3", + "nunomaduro/collision": "^8.7.0", "nunomaduro/termwind": "^2.3.0", "pestphp/pest-plugin": "^3.0.0", - "pestphp/pest-plugin-arch": "^3.0.0", + "pestphp/pest-plugin-arch": "^3.1.0", "pestphp/pest-plugin-mutate": "^3.0.5", "php": "^8.2.0", - "phpunit/phpunit": "^11.5.3" + "phpunit/phpunit": "^11.5.15" }, "conflict": { "filp/whoops": "<2.16.0", - "phpunit/phpunit": ">11.5.3", + "phpunit/phpunit": ">11.5.15", "sebastian/exporter": "<6.0.0", "webmozart/assert": "<1.11.0" }, "require-dev": { - "pestphp/pest-dev-tools": "^3.3.0", - "pestphp/pest-plugin-type-coverage": "^3.2.3", - "symfony/process": "^7.2.0" + "pestphp/pest-dev-tools": "^3.4.0", + "pestphp/pest-plugin-type-coverage": "^3.5.0", + "symfony/process": "^7.2.5" }, "bin": [ "bin/pest" @@ -12951,7 +12894,7 @@ ], "support": { "issues": "https://github.com/pestphp/pest/issues", - "source": "https://github.com/pestphp/pest/tree/v3.7.4" + "source": "https://github.com/pestphp/pest/tree/v3.8.0" }, "funding": [ { @@ -12963,7 +12906,7 @@ "type": "github" } ], - "time": "2025-01-23T14:03:29+00:00" + "time": "2025-03-30T17:49:10+00:00" }, { "name": "pestphp/pest-plugin", @@ -13037,16 +12980,16 @@ }, { "name": "pestphp/pest-plugin-arch", - "version": "v3.0.0", + "version": "v3.1.0", "source": { "type": "git", "url": "https://github.com/pestphp/pest-plugin-arch.git", - "reference": "0a27e55a270cfe73d8cb70551b91002ee2cb64b0" + "reference": "ebec636b97ee73936ee8485e15a59c3f5a4c21b2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pestphp/pest-plugin-arch/zipball/0a27e55a270cfe73d8cb70551b91002ee2cb64b0", - "reference": "0a27e55a270cfe73d8cb70551b91002ee2cb64b0", + "url": "https://api.github.com/repos/pestphp/pest-plugin-arch/zipball/ebec636b97ee73936ee8485e15a59c3f5a4c21b2", + "reference": "ebec636b97ee73936ee8485e15a59c3f5a4c21b2", "shasum": "" }, "require": { @@ -13055,8 +12998,8 @@ "ta-tikoma/phpunit-architecture-test": "^0.8.4" }, "require-dev": { - "pestphp/pest": "^3.0.0", - "pestphp/pest-dev-tools": "^3.0.0" + "pestphp/pest": "^3.7.5", + "pestphp/pest-dev-tools": "^3.4.0" }, "type": "library", "extra": { @@ -13091,7 +13034,7 @@ "unit" ], "support": { - "source": "https://github.com/pestphp/pest-plugin-arch/tree/v3.0.0" + "source": "https://github.com/pestphp/pest-plugin-arch/tree/v3.1.0" }, "funding": [ { @@ -13103,7 +13046,7 @@ "type": "github" } ], - "time": "2024-09-08T23:23:55+00:00" + "time": "2025-03-30T17:28:50+00:00" }, { "name": "pestphp/pest-plugin-mutate", @@ -13433,16 +13376,16 @@ }, { "name": "phpstan/phpstan", - "version": "2.1.6", + "version": "2.1.11", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "6eaec7c6c9e90dcfe46ad1e1ffa5171e2dab641c" + "reference": "8ca5f79a8f63c49b2359065832a654e1ec70ac30" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/6eaec7c6c9e90dcfe46ad1e1ffa5171e2dab641c", - "reference": "6eaec7c6c9e90dcfe46ad1e1ffa5171e2dab641c", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/8ca5f79a8f63c49b2359065832a654e1ec70ac30", + "reference": "8ca5f79a8f63c49b2359065832a654e1ec70ac30", "shasum": "" }, "require": { @@ -13487,27 +13430,27 @@ "type": "github" } ], - "time": "2025-02-19T15:46:42+00:00" + "time": "2025-03-24T13:45:00+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "11.0.8", + "version": "11.0.9", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "418c59fd080954f8c4aa5631d9502ecda2387118" + "reference": "14d63fbcca18457e49c6f8bebaa91a87e8e188d7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/418c59fd080954f8c4aa5631d9502ecda2387118", - "reference": "418c59fd080954f8c4aa5631d9502ecda2387118", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/14d63fbcca18457e49c6f8bebaa91a87e8e188d7", + "reference": "14d63fbcca18457e49c6f8bebaa91a87e8e188d7", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^5.3.1", + "nikic/php-parser": "^5.4.0", "php": ">=8.2", "phpunit/php-file-iterator": "^5.1.0", "phpunit/php-text-template": "^4.0.1", @@ -13519,7 +13462,7 @@ "theseer/tokenizer": "^1.2.3" }, "require-dev": { - "phpunit/phpunit": "^11.5.0" + "phpunit/phpunit": "^11.5.2" }, "suggest": { "ext-pcov": "PHP extension that provides line coverage", @@ -13557,7 +13500,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/11.0.8" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.9" }, "funding": [ { @@ -13565,7 +13508,7 @@ "type": "github" } ], - "time": "2024-12-11T12:34:27+00:00" + "time": "2025-02-25T13:26:39+00:00" }, { "name": "phpunit/php-file-iterator", @@ -13814,16 +13757,16 @@ }, { "name": "phpunit/phpunit", - "version": "11.5.3", + "version": "11.5.15", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "30e319e578a7b5da3543073e30002bf82042f701" + "reference": "4b6a4ee654e5e0c5e1f17e2f83c0f4c91dee1f9c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/30e319e578a7b5da3543073e30002bf82042f701", - "reference": "30e319e578a7b5da3543073e30002bf82042f701", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/4b6a4ee654e5e0c5e1f17e2f83c0f4c91dee1f9c", + "reference": "4b6a4ee654e5e0c5e1f17e2f83c0f4c91dee1f9c", "shasum": "" }, "require": { @@ -13833,24 +13776,24 @@ "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.12.1", + "myclabs/deep-copy": "^1.13.0", "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=8.2", - "phpunit/php-code-coverage": "^11.0.8", + "phpunit/php-code-coverage": "^11.0.9", "phpunit/php-file-iterator": "^5.1.0", "phpunit/php-invoker": "^5.0.1", "phpunit/php-text-template": "^4.0.1", "phpunit/php-timer": "^7.0.1", "sebastian/cli-parser": "^3.0.2", - "sebastian/code-unit": "^3.0.2", - "sebastian/comparator": "^6.3.0", + "sebastian/code-unit": "^3.0.3", + "sebastian/comparator": "^6.3.1", "sebastian/diff": "^6.0.2", "sebastian/environment": "^7.2.0", "sebastian/exporter": "^6.3.0", "sebastian/global-state": "^7.0.2", "sebastian/object-enumerator": "^6.0.1", - "sebastian/type": "^5.1.0", + "sebastian/type": "^5.1.2", "sebastian/version": "^5.0.2", "staabm/side-effects-detector": "^1.0.5" }, @@ -13895,7 +13838,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.3" + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.15" }, "funding": [ { @@ -13911,25 +13854,25 @@ "type": "tidelift" } ], - "time": "2025-01-13T09:36:00+00:00" + "time": "2025-03-23T16:02:11+00:00" }, { "name": "rector/rector", - "version": "2.0.9", + "version": "2.0.11", "source": { "type": "git", "url": "https://github.com/rectorphp/rector.git", - "reference": "4393230e478c0006795770fe74c223b5c64ed68c" + "reference": "059b827cc648929711606e9824337e41e2f9ed92" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/rectorphp/rector/zipball/4393230e478c0006795770fe74c223b5c64ed68c", - "reference": "4393230e478c0006795770fe74c223b5c64ed68c", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/059b827cc648929711606e9824337e41e2f9ed92", + "reference": "059b827cc648929711606e9824337e41e2f9ed92", "shasum": "" }, "require": { "php": "^7.4|^8.0", - "phpstan/phpstan": "^2.1.3" + "phpstan/phpstan": "^2.1.9" }, "conflict": { "rector/rector-doctrine": "*", @@ -13962,7 +13905,7 @@ ], "support": { "issues": "https://github.com/rectorphp/rector/issues", - "source": "https://github.com/rectorphp/rector/tree/2.0.9" + "source": "https://github.com/rectorphp/rector/tree/2.0.11" }, "funding": [ { @@ -13970,7 +13913,7 @@ "type": "github" } ], - "time": "2025-02-10T08:14:01+00:00" + "time": "2025-03-28T10:25:17+00:00" }, { "name": "sebastian/cli-parser", @@ -14031,16 +13974,16 @@ }, { "name": "sebastian/code-unit", - "version": "3.0.2", + "version": "3.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit.git", - "reference": "ee88b0cdbe74cf8dd3b54940ff17643c0d6543ca" + "reference": "54391c61e4af8078e5b276ab082b6d3c54c9ad64" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/ee88b0cdbe74cf8dd3b54940ff17643c0d6543ca", - "reference": "ee88b0cdbe74cf8dd3b54940ff17643c0d6543ca", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/54391c61e4af8078e5b276ab082b6d3c54c9ad64", + "reference": "54391c61e4af8078e5b276ab082b6d3c54c9ad64", "shasum": "" }, "require": { @@ -14076,7 +14019,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/code-unit/issues", "security": "https://github.com/sebastianbergmann/code-unit/security/policy", - "source": "https://github.com/sebastianbergmann/code-unit/tree/3.0.2" + "source": "https://github.com/sebastianbergmann/code-unit/tree/3.0.3" }, "funding": [ { @@ -14084,7 +14027,7 @@ "type": "github" } ], - "time": "2024-12-12T09:59:06+00:00" + "time": "2025-03-19T07:56:08+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", @@ -14144,16 +14087,16 @@ }, { "name": "sebastian/comparator", - "version": "6.3.0", + "version": "6.3.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "d4e47a769525c4dd38cea90e5dcd435ddbbc7115" + "reference": "24b8fbc2c8e201bb1308e7b05148d6ab393b6959" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/d4e47a769525c4dd38cea90e5dcd435ddbbc7115", - "reference": "d4e47a769525c4dd38cea90e5dcd435ddbbc7115", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/24b8fbc2c8e201bb1308e7b05148d6ab393b6959", + "reference": "24b8fbc2c8e201bb1308e7b05148d6ab393b6959", "shasum": "" }, "require": { @@ -14172,7 +14115,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "6.2-dev" + "dev-main": "6.3-dev" } }, "autoload": { @@ -14212,7 +14155,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", "security": "https://github.com/sebastianbergmann/comparator/security/policy", - "source": "https://github.com/sebastianbergmann/comparator/tree/6.3.0" + "source": "https://github.com/sebastianbergmann/comparator/tree/6.3.1" }, "funding": [ { @@ -14220,7 +14163,7 @@ "type": "github" } ], - "time": "2025-01-06T10:28:19+00:00" + "time": "2025-03-07T06:57:01+00:00" }, { "name": "sebastian/complexity", @@ -14789,16 +14732,16 @@ }, { "name": "sebastian/type", - "version": "5.1.0", + "version": "5.1.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "461b9c5da241511a2a0e8f240814fb23ce5c0aac" + "reference": "a8a7e30534b0eb0c77cd9d07e82de1a114389f5e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/461b9c5da241511a2a0e8f240814fb23ce5c0aac", - "reference": "461b9c5da241511a2a0e8f240814fb23ce5c0aac", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/a8a7e30534b0eb0c77cd9d07e82de1a114389f5e", + "reference": "a8a7e30534b0eb0c77cd9d07e82de1a114389f5e", "shasum": "" }, "require": { @@ -14834,7 +14777,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/type/issues", "security": "https://github.com/sebastianbergmann/type/security/policy", - "source": "https://github.com/sebastianbergmann/type/tree/5.1.0" + "source": "https://github.com/sebastianbergmann/type/tree/5.1.2" }, "funding": [ { @@ -14842,7 +14785,7 @@ "type": "github" } ], - "time": "2024-09-17T13:12:04+00:00" + "time": "2025-03-18T13:35:50+00:00" }, { "name": "sebastian/version", @@ -15314,16 +15257,16 @@ }, { "name": "symfony/http-client", - "version": "v7.2.3", + "version": "v7.2.4", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "7ce6078c79a4a7afff931c413d2959d3bffbfb8d" + "reference": "78981a2ffef6437ed92d4d7e2a86a82f256c6dc6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/7ce6078c79a4a7afff931c413d2959d3bffbfb8d", - "reference": "7ce6078c79a4a7afff931c413d2959d3bffbfb8d", + "url": "https://api.github.com/repos/symfony/http-client/zipball/78981a2ffef6437ed92d4d7e2a86a82f256c6dc6", + "reference": "78981a2ffef6437ed92d4d7e2a86a82f256c6dc6", "shasum": "" }, "require": { @@ -15389,7 +15332,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v7.2.3" + "source": "https://github.com/symfony/http-client/tree/v7.2.4" }, "funding": [ { @@ -15405,7 +15348,7 @@ "type": "tidelift" } ], - "time": "2025-01-28T15:51:35+00:00" + "time": "2025-02-13T10:27:23+00:00" }, { "name": "symfony/http-client-contracts", @@ -15597,12 +15540,12 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": true, "prefer-lowest": false, "platform": { "php": "^8.4" }, - "platform-dev": [], - "plugin-api-version": "2.3.0" + "platform-dev": {}, + "plugin-api-version": "2.6.0" } diff --git a/config/app.php b/config/app.php index 371ac44ec..a94cfadd8 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\ConfigurationServiceProvider::class, ], /* diff --git a/config/constants.php b/config/constants.php index 9d15da1da..439f32940 100644 --- a/config/constants.php +++ b/config/constants.php @@ -2,13 +2,14 @@ return [ 'coolify' => [ - 'version' => '4.0.0-beta.399', - 'helper_version' => '1.0.7', + 'version' => '4.0.0-beta.407', + 'helper_version' => '1.0.8', 'realtime_version' => '1.0.6', 'self_hosted' => env('SELF_HOSTED', true), 'autoupdate' => env('AUTOUPDATE'), 'base_config_path' => env('BASE_CONFIG_PATH', '/data/coolify'), - 'helper_image' => env('HELPER_IMAGE', 'ghcr.io/coollabsio/coolify-helper'), + 'registry_url' => env('REGISTRY_URL', 'ghcr.io'), + 'helper_image' => env('HELPER_IMAGE', env('REGISTRY_URL', 'ghcr.io').'/coollabsio/coolify-helper'), 'is_windows_docker_desktop' => env('IS_WINDOWS_DOCKER_DESKTOP', false), ], diff --git a/config/redoc.php b/config/redoc.php deleted file mode 100644 index 439e93e7b..000000000 --- a/config/redoc.php +++ /dev/null @@ -1,28 +0,0 @@ - '', - - /* - |-------------------------------------------------------------------------- - | Variables - |-------------------------------------------------------------------------- - | - | You can automatically replace variables in your OpenAPI definitions by - | adding a key value pair to the array below. This will replace any - | instances of :key with the given value. - | - */ - - 'variables' => [], - -]; diff --git a/database/migrations/2025_01_27_102616_add_ssl_fields_to_database_tables.php b/database/migrations/2025_01_27_102616_add_ssl_fields_to_database_tables.php new file mode 100644 index 000000000..14162133a --- /dev/null +++ b/database/migrations/2025_01_27_102616_add_ssl_fields_to_database_tables.php @@ -0,0 +1,70 @@ +boolean('enable_ssl')->default(false); + $table->enum('ssl_mode', ['allow', 'prefer', 'require', 'verify-ca', 'verify-full'])->default('require'); + }); + Schema::table('standalone_mysqls', function (Blueprint $table) { + $table->boolean('enable_ssl')->default(false); + $table->enum('ssl_mode', ['PREFERRED', 'REQUIRED', 'VERIFY_CA', 'VERIFY_IDENTITY'])->default('REQUIRED'); + }); + Schema::table('standalone_mariadbs', function (Blueprint $table) { + $table->boolean('enable_ssl')->default(false); + }); + Schema::table('standalone_redis', function (Blueprint $table) { + $table->boolean('enable_ssl')->default(false); + }); + Schema::table('standalone_keydbs', function (Blueprint $table) { + $table->boolean('enable_ssl')->default(false); + }); + Schema::table('standalone_dragonflies', function (Blueprint $table) { + $table->boolean('enable_ssl')->default(false); + }); + Schema::table('standalone_mongodbs', function (Blueprint $table) { + $table->boolean('enable_ssl')->default(true); + $table->enum('ssl_mode', ['allow', 'prefer', 'require', 'verify-full'])->default('require'); + }); + } + + /** + * Reverse the migrations. + */ + public function down() + { + Schema::table('standalone_postgresqls', function (Blueprint $table) { + $table->dropColumn('enable_ssl'); + $table->dropColumn('ssl_mode'); + }); + Schema::table('standalone_mysqls', function (Blueprint $table) { + $table->dropColumn('enable_ssl'); + $table->dropColumn('ssl_mode'); + }); + Schema::table('standalone_mariadbs', function (Blueprint $table) { + $table->dropColumn('enable_ssl'); + }); + Schema::table('standalone_redis', function (Blueprint $table) { + $table->dropColumn('enable_ssl'); + }); + Schema::table('standalone_keydbs', function (Blueprint $table) { + $table->dropColumn('enable_ssl'); + }); + Schema::table('standalone_dragonflies', function (Blueprint $table) { + $table->dropColumn('enable_ssl'); + }); + Schema::table('standalone_mongodbs', function (Blueprint $table) { + $table->dropColumn('enable_ssl'); + $table->dropColumn('ssl_mode'); + }); + } +}; diff --git a/database/migrations/2025_01_27_153741_create_ssl_certificates_table.php b/database/migrations/2025_01_27_153741_create_ssl_certificates_table.php new file mode 100644 index 000000000..7907fb090 --- /dev/null +++ b/database/migrations/2025_01_27_153741_create_ssl_certificates_table.php @@ -0,0 +1,34 @@ +id(); + $table->text('ssl_certificate'); + $table->text('ssl_private_key'); + $table->text('configuration_dir')->nullable(); + $table->text('mount_path')->nullable(); + $table->string('resource_type')->nullable(); + $table->unsignedBigInteger('resource_id')->nullable(); + $table->unsignedBigInteger('server_id'); + $table->text('common_name'); + $table->json('subject_alternative_names')->nullable(); + $table->timestamp('valid_until'); + $table->boolean('is_ca_certificate')->default(false); + $table->timestamps(); + + $table->foreign('server_id')->references('id')->on('servers'); + }); + } + + public function down() + { + Schema::dropIfExists('ssl_certificates'); + } +}; diff --git a/database/migrations/2025_01_30_125223_encrypt_local_file_volumes_fields.php b/database/migrations/2025_01_30_125223_encrypt_local_file_volumes_fields.php new file mode 100644 index 000000000..dbf287ff3 --- /dev/null +++ b/database/migrations/2025_01_30_125223_encrypt_local_file_volumes_fields.php @@ -0,0 +1,133 @@ +text('mount_path')->nullable()->change(); + }); + + if (DB::table('local_file_volumes')->exists()) { + DB::beginTransaction(); + DB::table('local_file_volumes') + ->orderBy('id') + ->chunk(100, function ($volumes) { + foreach ($volumes as $volume) { + try { + $fs_path = $volume->fs_path; + $mount_path = $volume->mount_path; + $content = $volume->content; + // Check if fields are already encrypted by attempting to decrypt + try { + if ($fs_path) { + Crypt::decryptString($fs_path); + } + } catch (\Exception $e) { + $fs_path = $fs_path ? Crypt::encryptString($fs_path) : null; + } + + try { + if ($mount_path) { + Crypt::decryptString($mount_path); + } + } catch (\Exception $e) { + $mount_path = $mount_path ? Crypt::encryptString($mount_path) : null; + } + + try { + if ($content) { + Crypt::decryptString($content); + } + } catch (\Exception $e) { + $content = $content ? Crypt::encryptString($content) : null; + } + + DB::table('local_file_volumes')->where('id', $volume->id)->update([ + 'fs_path' => $fs_path, + 'mount_path' => $mount_path, + 'content' => $content, + ]); + echo "Updated volume {$volume->id}\n"; + } catch (\Exception $e) { + echo "Error encrypting local file volume fields: {$e->getMessage()}\n"; + Log::error('Error encrypting local file volume fields: '.$e->getMessage()); + } + } + }); + DB::commit(); + } + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('local_file_volumes', function (Blueprint $table) { + $table->string('fs_path')->change(); + $table->string('mount_path')->nullable()->change(); + $table->longText('content')->nullable()->change(); + }); + + if (DB::table('local_file_volumes')->exists()) { + DB::beginTransaction(); + DB::table('local_file_volumes') + ->orderBy('id') + ->chunk(100, function ($volumes) { + foreach ($volumes as $volume) { + try { + $fs_path = $volume->fs_path; + $mount_path = $volume->mount_path; + $content = $volume->content; + // Check if fields are already decrypted by attempting to decrypt + try { + if ($fs_path) { + Crypt::decryptString($fs_path); + } + } catch (\Exception $e) { + $fs_path = $fs_path ? Crypt::decryptString($fs_path) : null; + } + + try { + if ($mount_path) { + Crypt::decryptString($mount_path); + } + } catch (\Exception $e) { + $mount_path = $mount_path ? Crypt::decryptString($mount_path) : null; + } + + try { + if ($content) { + Crypt::decryptString($content); + } + } catch (\Exception $e) { + $content = $content ? Crypt::decryptString($content) : null; + } + + DB::table('local_file_volumes')->where('id', $volume->id)->update([ + 'fs_path' => $fs_path, + 'mount_path' => $mount_path, + 'content' => $content, + ]); + echo "Updated volume {$volume->id}\n"; + } catch (\Exception $e) { + echo "Error decrypting local file volume fields: {$e->getMessage()}\n"; + Log::error('Error decrypting local file volume fields: '.$e->getMessage()); + } + } + }); + DB::commit(); + } + } +}; diff --git a/database/migrations/2025_03_21_104103_disable_discord_here.php b/database/migrations/2025_03_21_104103_disable_discord_here.php new file mode 100644 index 000000000..6aef45c04 --- /dev/null +++ b/database/migrations/2025_03_21_104103_disable_discord_here.php @@ -0,0 +1,28 @@ +boolean('discord_ping_enabled')->default(true); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('discord_notification_settings', function (Blueprint $table) { + $table->dropColumn('discord_ping_enabled'); + }); + } +}; diff --git a/database/migrations/2025_03_26_104103_disable_mongodb_ssl_by_default.php b/database/migrations/2025_03_26_104103_disable_mongodb_ssl_by_default.php new file mode 100644 index 000000000..80dddb089 --- /dev/null +++ b/database/migrations/2025_03_26_104103_disable_mongodb_ssl_by_default.php @@ -0,0 +1,28 @@ +boolean('enable_ssl')->default(false)->change(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('standalone_mongodbs', function (Blueprint $table) { + $table->boolean('enable_ssl')->default(true)->change(); + }); + } +}; diff --git a/database/migrations/2025_03_29_204400_revert_some_local_volume_encryption.php b/database/migrations/2025_03_29_204400_revert_some_local_volume_encryption.php new file mode 100644 index 000000000..a4d1fc1df --- /dev/null +++ b/database/migrations/2025_03_29_204400_revert_some_local_volume_encryption.php @@ -0,0 +1,160 @@ +exists()) { + echo "Found local_file_volumes table, proceeding with migration...\n"; + // First, get all volumes and decrypt their values + $decryptedVolumes = collect(); + $totalVolumes = DB::table('local_file_volumes')->count(); + echo "Total volumes to process: {$totalVolumes}\n"; + + DB::table('local_file_volumes') + ->orderBy('id') + ->chunk(100, function ($volumes) use (&$decryptedVolumes) { + echo 'Processing chunk of '.count($volumes)." volumes...\n"; + foreach ($volumes as $volume) { + try { + $fs_path = $volume->fs_path; + $mount_path = $volume->mount_path; + + try { + if ($fs_path) { + $fs_path = Crypt::decryptString($fs_path); + } + } catch (\Exception $e) { + echo "Warning: Could not decrypt fs_path for volume {$volume->id}\n"; + } + + try { + if ($mount_path) { + $mount_path = Crypt::decryptString($mount_path); + } + } catch (\Exception $e) { + echo "Warning: Could not decrypt mount_path for volume {$volume->id}\n"; + } + + $decryptedVolumes->push([ + 'id' => $volume->id, + 'fs_path' => $fs_path, + 'mount_path' => $mount_path, + 'resource_id' => $volume->resource_id, + 'resource_type' => $volume->resource_type, + ]); + + } catch (\Exception $e) { + echo "Error decrypting volume {$volume->id}: {$e->getMessage()}\n"; + Log::error("Error decrypting volume {$volume->id}: ".$e->getMessage()); + } + } + }); + + echo 'Finished processing all volumes. Found '.$decryptedVolumes->count()." total volumes.\n"; + + // Group by the unique constraint fields and keep only the first occurrence + $uniqueVolumes = $decryptedVolumes->groupBy(function ($volume) { + return $volume['mount_path'].'|'.$volume['resource_id'].'|'.$volume['resource_type']; + })->map(function ($group) { + return $group->first(); + }); + + echo 'After deduplication, found '.$uniqueVolumes->count()." unique volumes.\n"; + + // Get IDs to delete (all except the ones we're keeping) + $idsToKeep = $uniqueVolumes->pluck('id')->toArray(); + $idsToDelete = $decryptedVolumes->pluck('id')->diff($idsToKeep)->toArray(); + + // Delete duplicate records + if (! empty($idsToDelete)) { + echo "\nFound ".count($idsToDelete)." duplicate volumes to delete.\n"; + // Show details of volumes being deleted + $volumesToDelete = $decryptedVolumes->whereIn('id', $idsToDelete); + echo "\nVolumes to be deleted:\n"; + foreach ($volumesToDelete as $volume) { + echo "ID: {$volume['id']}, Mount Path: {$volume['mount_path']}, Resource ID: {$volume['resource_id']}, Resource Type: {$volume['resource_type']}\n"; + echo "FS Path: {$volume['fs_path']}\n"; + echo "-------------------\n"; + } + + DB::table('local_file_volumes')->whereIn('id', $idsToDelete)->delete(); + echo 'Deleted '.count($idsToDelete)." duplicate volume(s)\n"; + } + + echo "\nUpdating remaining volumes with decrypted values...\n"; + $updateCount = 0; + // Update the remaining records with decrypted values + foreach ($uniqueVolumes as $volume) { + try { + DB::table('local_file_volumes')->where('id', $volume['id'])->update([ + 'fs_path' => $volume['fs_path'], + 'mount_path' => $volume['mount_path'], + ]); + $updateCount++; + } catch (\Exception $e) { + echo "Error updating volume {$volume['id']}: {$e->getMessage()}\n"; + Log::error("Error updating volume {$volume['id']}: ".$e->getMessage()); + } + } + echo "Successfully updated {$updateCount} volumes.\n"; + } else { + echo "No local_file_volumes table found, skipping migration.\n"; + } + + echo "Migration completed successfully.\n"; + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + if (DB::table('local_file_volumes')->exists()) { + DB::table('local_file_volumes') + ->orderBy('id') + ->chunk(100, function ($volumes) { + foreach ($volumes as $volume) { + DB::beginTransaction(); + try { + $fs_path = $volume->fs_path; + $mount_path = $volume->mount_path; + try { + if ($fs_path) { + $fs_path = Crypt::encrypt($fs_path); + } + } catch (\Exception $e) { + } + + try { + if ($mount_path) { + $mount_path = Crypt::encrypt($mount_path); + } + } catch (\Exception $e) { + } + + DB::table('local_file_volumes')->where('id', $volume->id)->update([ + 'fs_path' => $fs_path, + 'mount_path' => $mount_path, + ]); + echo "Updated volume {$volume->id}\n"; + } catch (\Exception $e) { + echo "Error decrypting local file volume fields: {$e->getMessage()}\n"; + Log::error('Error decrypting local file volume fields: '.$e->getMessage()); + } + DB::commit(); + } + }); + } + } +}; diff --git a/database/migrations/2025_03_31_124212_add_specific_spa_configuration.php b/database/migrations/2025_03_31_124212_add_specific_spa_configuration.php new file mode 100644 index 000000000..1ec0d722b --- /dev/null +++ b/database/migrations/2025_03_31_124212_add_specific_spa_configuration.php @@ -0,0 +1,28 @@ +boolean('is_spa')->default(false); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('application_settings', function (Blueprint $table) { + $table->dropColumn('is_spa'); + }); + } +}; diff --git a/database/seeders/CaSslCertSeeder.php b/database/seeders/CaSslCertSeeder.php new file mode 100644 index 000000000..09f6cc984 --- /dev/null +++ b/database/seeders/CaSslCertSeeder.php @@ -0,0 +1,43 @@ +id)->where('is_ca_certificate', true)->first(); + + if (! $existingCaCert) { + $caCert = SslHelper::generateSslCertificate( + commonName: 'Coolify CA Certificate', + serverId: $server->id, + isCaCertificate: true, + validityDays: 10 * 365 + ); + } else { + $caCert = $existingCaCert; + } + $caCertPath = config('constants.coolify.base_config_path').'/ssl/'; + + $commands = collect([ + "mkdir -p $caCertPath", + "chown -R 9999:root $caCertPath", + "chmod -R 700 $caCertPath", + "rm -rf $caCertPath/coolify-ca.crt", + "echo '{$caCert->ssl_certificate}' > $caCertPath/coolify-ca.crt", + "chmod 644 $caCertPath/coolify-ca.crt", + ]); + + remote_process($commands, $server); + } + }); + } +} diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index 6e66c64f4..e0e7a3ba5 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -28,6 +28,7 @@ class DatabaseSeeder extends Seeder OauthSettingSeeder::class, DisableTwoStepConfirmationSeeder::class, SentinelSeeder::class, + CaSslCertSeeder::class, ]); } } diff --git a/database/seeders/ProductionSeeder.php b/database/seeders/ProductionSeeder.php index bbb9fcb75..058d4c8e4 100644 --- a/database/seeders/ProductionSeeder.php +++ b/database/seeders/ProductionSeeder.php @@ -193,5 +193,6 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA== $this->call(PopulateSshKeysDirectorySeeder::class); $this->call(SentinelSeeder::class); $this->call(RootUserSeeder::class); + $this->call(CaSslCertSeeder::class); } } diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 23a65cca6..35fea6403 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -1,6 +1,6 @@ services: coolify: - image: "ghcr.io/coollabsio/coolify:${LATEST_IMAGE:-latest}" + image: "${REGISTRY_URL:-ghcr.io}/coollabsio/coolify:${LATEST_IMAGE:-latest}" volumes: - type: bind source: /data/coolify/source/.env @@ -61,7 +61,7 @@ services: retries: 10 timeout: 2s soketi: - image: 'ghcr.io/coollabsio/coolify-realtime:1.0.6' + image: '${REGISTRY_URL:-ghcr.io}/coollabsio/coolify-realtime:1.0.6' ports: - "${SOKETI_PORT:-6001}:6001" - "6002:6002" diff --git a/docker/coolify-helper/Dockerfile b/docker/coolify-helper/Dockerfile index 657610014..b62469cef 100644 --- a/docker/coolify-helper/Dockerfile +++ b/docker/coolify-helper/Dockerfile @@ -4,15 +4,15 @@ ARG BASE_IMAGE=alpine:3.21 # https://download.docker.com/linux/static/stable/ ARG DOCKER_VERSION=28.0.0 # https://github.com/docker/compose/releases -ARG DOCKER_COMPOSE_VERSION=2.33.1 +ARG DOCKER_COMPOSE_VERSION=2.34.0 # https://github.com/docker/buildx/releases -ARG DOCKER_BUILDX_VERSION=0.21.1 +ARG DOCKER_BUILDX_VERSION=0.22.0 # https://github.com/buildpacks/pack/releases -ARG PACK_VERSION=0.36.4 +ARG PACK_VERSION=0.37.0 # https://github.com/railwayapp/nixpacks/releases -ARG NIXPACKS_VERSION=1.33.0 +ARG NIXPACKS_VERSION=1.34.1 # https://github.com/minio/mc/releases -ARG MINIO_VERSION=RELEASE.2025-02-15T10-36-16Z +ARG MINIO_VERSION=RELEASE.2025-03-12T17-29-24Z FROM minio/mc:${MINIO_VERSION} AS minio-client diff --git a/docker/development/Dockerfile b/docker/development/Dockerfile index 094600455..8c5beec07 100644 --- a/docker/development/Dockerfile +++ b/docker/development/Dockerfile @@ -2,7 +2,7 @@ # https://hub.docker.com/r/serversideup/php/tags?name=8.4-fpm-nginx-alpine ARG SERVERSIDEUP_PHP_VERSION=8.4-fpm-nginx-alpine # https://github.com/minio/mc/releases -ARG MINIO_VERSION=RELEASE.2025-02-15T10-36-16Z +ARG MINIO_VERSION=RELEASE.2025-03-12T17-29-24Z # https://github.com/cloudflare/cloudflared/releases ARG CLOUDFLARED_VERSION=2025.2.0 # https://www.postgresql.org/support/versioning/ diff --git a/docker/production/Dockerfile b/docker/production/Dockerfile index 86fae4a13..38bb50f3f 100644 --- a/docker/production/Dockerfile +++ b/docker/production/Dockerfile @@ -2,7 +2,7 @@ # https://hub.docker.com/r/serversideup/php/tags?name=8.4-fpm-nginx-alpine ARG SERVERSIDEUP_PHP_VERSION=8.4-fpm-nginx-alpine # https://github.com/minio/mc/releases -ARG MINIO_VERSION=RELEASE.2025-02-15T10-36-16Z +ARG MINIO_VERSION=RELEASE.2025-03-12T17-29-24Z # https://github.com/cloudflare/cloudflared/releases ARG CLOUDFLARED_VERSION=2025.2.0 # https://www.postgresql.org/support/versioning/ diff --git a/docker/testing-host/Dockerfile b/docker/testing-host/Dockerfile index c6f5d800e..b19d0875c 100644 --- a/docker/testing-host/Dockerfile +++ b/docker/testing-host/Dockerfile @@ -2,9 +2,9 @@ # https://download.docker.com/linux/static/stable/ ARG DOCKER_VERSION=28.0.0 # https://github.com/docker/compose/releases -ARG DOCKER_COMPOSE_VERSION=2.33.1 +ARG DOCKER_COMPOSE_VERSION=2.34.0 # https://github.com/docker/buildx/releases -ARG DOCKER_BUILDX_VERSION=0.21.1 +ARG DOCKER_BUILDX_VERSION=0.22.0 FROM debian:12-slim diff --git a/lang/az.json b/lang/az.json new file mode 100644 index 000000000..973c70c2f --- /dev/null +++ b/lang/az.json @@ -0,0 +1,40 @@ +{ + "auth.login": "Daxil ol", + "auth.login.authentik": "Authentik ilə daxil ol", + "auth.login.azure": "Azure ilə daxil ol", + "auth.login.bitbucket": "Bitbucket ilə daxil ol", + "auth.login.github": "Github ilə daxil ol", + "auth.login.gitlab": "GitLab ilə daxil ol", + "auth.login.google": "Google ilə daxil ol", + "auth.login.infomaniak": "Infomaniak ilə daxil ol", + "auth.already_registered": "Qeytiyatınız var?", + "auth.confirm_password": "Şifrəni təsdiqləyin", + "auth.forgot_password": "Şifrəmi unutdum", + "auth.forgot_password_send_email": "Şifrəni sıfırlamaq üçün e-poçt göndər", + "auth.register_now": "Qeydiyyat", + "auth.logout": "Çıxış", + "auth.register": "Qeydiyyat", + "auth.registration_disabled": "Qeydiyyat bağlıdır. Administratorla əlaqə saxlayın.", + "auth.reset_password": "Şifrənin bərpası", + "auth.failed": "Bu məlumatlar bizim qeydlərimizlə uyğun gəlmir.", + "auth.failed.callback": "Giriş təminatçısından geri çağırma işlənə bilmədi.", + "auth.failed.password": "Daxil etdiyiniz şifrə yanlışdır.", + "auth.failed.email": "Bu e-poçt ünvanı ilə istifadəçi tapılmadı.", + "auth.throttle": "Çox sayda uğursuz giriş cəhdi. Zəhmət olmasa :seconds saniyə sonra yenidən cəhd edin.", + "input.name": "Ad", + "input.email": "E-poçt", + "input.password": "Şifrə", + "input.password.again": "Şifrəni təkrar daxil edin", + "input.code": "Bir dəfəlik kod", + "input.recovery_code": "Bərpa kodu", + "button.save": "Yadda saxla", + "repository.url": "Nümunələr
Publik repozitoriyalar üçün https://... istifadə edin.
Özəl repozitoriyalar üçün git@... istifadə edin.

https://github.com/coollabsio/coolify-examples main branch-ı seçiləcək
https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify nodejs-fastify branch-ı seçiləcək.
https://gitea.com/sedlav/expressjs.git main branch-ı seçiləcək.
https://gitlab.com/andrasbacsai/nodejs-example.git main branch-ı seçiləcək.", + "service.stop": "Bu xidmət dayandırılacaq.", + "resource.docker_cleanup": "Docker təmizlənməsini işə salın (istifadə olunmayan şəkillər və builder keşini silin).", + "resource.non_persistent": "Bütün qeyri-daimi məlumatlar silinəcək.", + "resource.delete_volumes": "Bu resursla əlaqəli bütün həcm məlumatları tamamilə silinəcək.", + "resource.delete_connected_networks": "Bu resursla əlaqəli bütün əvvəlcədən təyin olunmamış şəbəkələr tamamilə silinəcək.", + "resource.delete_configurations": "Serverdən bütün konfiqurasiya faylları tamamilə silinəcək.", + "database.delete_backups_locally": "Bütün ehtiyat nüsxələr lokal yaddaşdan tamamilə silinəcək.", + "warning.sslipdomain": "Konfiqurasiya yadda saxlanıldı, lakin sslip domeni ilə https TÖVSİYƏ EDİLMİR, çünki Let's Encrypt serverləri bu ümumi domenlə məhdudlaşdırılır (SSL sertifikatının təsdiqlənməsi uğursuz olacaq).

Əvəzində öz domeninizdən istifadə edin." +} diff --git a/lang/id.json b/lang/id.json new file mode 100644 index 000000000..d35556402 --- /dev/null +++ b/lang/id.json @@ -0,0 +1,40 @@ +{ + "auth.login": "Masuk", + "auth.login.authentik": "Masuk dengan Authentik", + "auth.login.azure": "Masuk dengan Microsoft", + "auth.login.bitbucket": "Masuk dengan Bitbucket", + "auth.login.github": "Masuk dengan GitHub", + "auth.login.gitlab": "Masuk dengan Gitlab", + "auth.login.google": "Masuk dengan Google", + "auth.login.infomaniak": "Masuk dengan Infomaniak", + "auth.already_registered": "Sudah terdaftar?", + "auth.confirm_password": "Konfirmasi kata sandi", + "auth.forgot_password": "Lupa kata sandi", + "auth.forgot_password_send_email": "Kirim email reset kata sandi", + "auth.register_now": "Daftar", + "auth.logout": "Keluar", + "auth.register": "Daftar", + "auth.registration_disabled": "Pendaftaran dinonaktifkan. Harap hubungi administrator.", + "auth.reset_password": "Reset kata sandi", + "auth.failed": "Kredensial ini tidak cocok dengan catatan kami.", + "auth.failed.callback": "Gagal memproses callback dari penyedia login.", + "auth.failed.password": "Kata sandi yang diberikan salah.", + "auth.failed.email": "Kami tidak dapat menemukan pengguna dengan alamat e-mail tersebut.", + "auth.throttle": "Terlalu banyak percobaan login. Silakan coba lagi dalam :seconds detik.", + "input.name": "Nama", + "input.email": "Email", + "input.password": "Kata sandi", + "input.password.again": "Kata sandi lagi", + "input.code": "Kode sekali pakai", + "input.recovery_code": "Kode pemulihan", + "button.save": "Simpan", + "repository.url": "Contoh
Untuk repositori Publik, gunakan https://....
Untuk repositori Privat, gunakan git@....

https://github.com/coollabsio/coolify-examples cabang main akan dipilih
https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify cabang nodejs-fastify akan dipilih.
https://gitea.com/sedlav/expressjs.git cabang main akan dipilih.
https://gitlab.com/andrasbacsai/nodejs-example.git cabang main akan dipilih.", + "service.stop": "Layanan ini akan dihentikan.", + "resource.docker_cleanup": "Jalankan Pembersihan Docker (hapus gambar yang tidak digunakan dan cache builder).", + "resource.non_persistent": "Semua data non-persisten akan dihapus.", + "resource.delete_volumes": "Hapus permanen semua volume yang terkait dengan sumber daya ini.", + "resource.delete_connected_networks": "Hapus permanen semua jaringan non-predefined yang terkait dengan sumber daya ini.", + "resource.delete_configurations": "Hapus permanen semua file konfigurasi dari server.", + "database.delete_backups_locally": "Semua backup akan dihapus permanen dari penyimpanan lokal.", + "warning.sslipdomain": "Konfigurasi Anda disimpan, tetapi domain sslip dengan https TIDAK direkomendasikan, karena server Let's Encrypt dengan domain publik ini dibatasi (validasi sertifikat SSL akan gagal).

Gunakan domain Anda sendiri sebagai gantinya." +} diff --git a/lang/no.json b/lang/no.json new file mode 100644 index 000000000..29d5af124 --- /dev/null +++ b/lang/no.json @@ -0,0 +1,40 @@ +{ + "auth.login": "Logg inn", + "auth.login.authentik": "Logg inn med Authentik", + "auth.login.azure": "Logg inn med Microsoft", + "auth.login.bitbucket": "Logg inn med Bitbucket", + "auth.login.github": "Logg inn med GitHub", + "auth.login.gitlab": "Logg inn med Gitlab", + "auth.login.google": "Logg inn med Google", + "auth.login.infomaniak": "Logg inn med Infomaniak", + "auth.already_registered": "Allerede registrert?", + "auth.confirm_password": "Bekreft passord", + "auth.forgot_password": "Glemt passord", + "auth.forgot_password_send_email": "Send e-post for tilbakestilling av passord", + "auth.register_now": "Registrer deg", + "auth.logout": "Logg ut", + "auth.register": "Registrer", + "auth.registration_disabled": "Registrering er deaktivert. Vennligst kontakt administrator.", + "auth.reset_password": "Tilbakestill passord", + "auth.failed": "Disse legitimasjonene samsvarer ikke med våre registre.", + "auth.failed.callback": "Klarte ikke å behandle tilbakekall fra innloggingsleverandør.", + "auth.failed.password": "Det oppgitte passordet er feil.", + "auth.failed.email": "Vi finner ingen bruker med den e-postadressen.", + "auth.throttle": "For mange innloggingsforsøk. Vennligst prøv igjen om :seconds sekunder.", + "input.name": "Navn", + "input.email": "E-post", + "input.password": "Passord", + "input.password.again": "Passord igjen", + "input.code": "Engangskode", + "input.recovery_code": "Gjenopprettingskode", + "button.save": "Lagre", + "repository.url": "Eksempler
For offentlige repositorier, bruk https://....
For private repositorier, bruk git@....

https://github.com/coollabsio/coolify-examples main gren vil bli valgt
https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify nodejs-fastify gren vil bli valgt.
https://gitea.com/sedlav/expressjs.git main gren vil bli valgt.
https://gitlab.com/andrasbacsai/nodejs-example.git main gren vil bli valgt.", + "service.stop": "Denne tjenesten vil bli stoppet.", + "resource.docker_cleanup": "Kjør Docker-opprydding (fjern ubrukte bilder og byggebuffer).", + "resource.non_persistent": "Alle ikke-persistente data vil bli slettet.", + "resource.delete_volumes": "Slett alle volumer tilknyttet denne ressursen permanent.", + "resource.delete_connected_networks": "Slett alle ikke-forhåndsdefinerte nettverk tilknyttet denne ressursen permanent.", + "resource.delete_configurations": "Slett alle konfigurasjonsfiler fra serveren permanent.", + "database.delete_backups_locally": "Alle sikkerhetskopier vil bli slettet permanent fra lokal lagring.", + "warning.sslipdomain": "Konfigurasjonen din er lagret, men sslip-domene med https er IKKE anbefalt, fordi Let's Encrypt-servere med dette offentlige domenet er hastighetsbegrenset (SSL-sertifikatvalidering vil mislykkes).

Bruk ditt eget domene i stedet." +} diff --git a/lang/pt-br.json b/lang/pt-br.json new file mode 100644 index 000000000..2e793890f --- /dev/null +++ b/lang/pt-br.json @@ -0,0 +1,40 @@ +{ + "auth.login": "Entrar", + "auth.login.authentik": "Entrar com Authentik", + "auth.login.azure": "Entrar com Microsoft", + "auth.login.bitbucket": "Entrar com Bitbucket", + "auth.login.github": "Entrar com GitHub", + "auth.login.gitlab": "Entrar com Gitlab", + "auth.login.google": "Entrar com Google", + "auth.login.infomaniak": "Entrar com Infomaniak", + "auth.already_registered": "Já tem uma conta?", + "auth.confirm_password": "Confirmar senha", + "auth.forgot_password": "Esqueceu a senha", + "auth.forgot_password_send_email": "Enviar e-mail para redefinir senha", + "auth.register_now": "Cadastre-se", + "auth.logout": "Sair", + "auth.register": "Cadastrar", + "auth.registration_disabled": "O registro está desativado. Por favor, contate o administrador.", + "auth.reset_password": "Redefinir senha", + "auth.failed": "Essas credenciais não correspondem aos nossos registros.", + "auth.failed.callback": "Falha ao processar o callback do provedor de login.", + "auth.failed.password": "A senha fornecida está incorreta.", + "auth.failed.email": "Não encontramos nenhum usuário com esse endereço de e-mail.", + "auth.throttle": "Muitas tentativas de login. Por favor, tente novamente em :seconds segundos.", + "input.name": "Nome", + "input.email": "E-mail", + "input.password": "Senha", + "input.password.again": "Senha novamente", + "input.code": "Código de uso único", + "input.recovery_code": "Código de recuperação", + "button.save": "Salvar", + "repository.url": "Exemplos
Para repositórios públicos, use https://....
Para repositórios privados, use git@....

https://github.com/coollabsio/coolify-examples main branch será selecionado
https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify nodejs-fastify branch será selecionado.
https://gitea.com/sedlav/expressjs.git main branch será selecionado.
https://gitlab.com/andrasbacsai/nodejs-example.git main branch será selecionado.", + "service.stop": "Este serviço será parado.", + "resource.docker_cleanup": "Executar limpeza do Docker (remover imagens não utilizadas e cache de build).", + "resource.non_persistent": "Todos os dados não persistentes serão excluídos.", + "resource.delete_volumes": "Excluir permanentemente todos os volumes associados a este recurso.", + "resource.delete_connected_networks": "Excluir permanentemente todas as redes não predefinidas associadas a este recurso.", + "resource.delete_configurations": "Excluir permanentemente todos os arquivos de configuração do servidor.", + "database.delete_backups_locally": "Todos os backups serão excluídos permanentemente do armazenamento local.", + "warning.sslipdomain": "Sua configuração foi salva, mas o domínio sslip com https NÃO é recomendado, porque os servidores do Let's Encrypt com este domínio público têm limitação de taxa (a validação do certificado SSL falhará).

Use seu próprio domínio em vez disso." +} diff --git a/lang/tr.json b/lang/tr.json index 3cbcee409..663c756f9 100644 --- a/lang/tr.json +++ b/lang/tr.json @@ -27,5 +27,13 @@ "input.code": "Tek Kullanımlık Kod", "input.recovery_code": "Kurtarma Kodu", "button.save": "Kaydet", - "repository.url": "Örnekler
Halka açık depolar için https://... kullanın.
Özel depolar için git@... kullanın.

https://github.com/coollabsio/coolify-examples main dalı seçilecek
https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify nodejs-fastify dalı seçilecek.
https://gitea.com/sedlav/expressjs.git main dalı seçilecek.
https://gitlab.com/andrasbacsai/nodejs-example.git main dalı seçilecek." + "repository.url": "Örnekler
Halka açık depolar için https://... kullanın.
Özel depolar için git@... kullanın.

https://github.com/coollabsio/coolify-examples main dalı seçilecek
https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify nodejs-fastify dalı seçilecek.
https://gitea.com/sedlav/expressjs.git main dalı seçilecek.
https://gitlab.com/andrasbacsai/nodejs-example.git main dalı seçilecek.", + "service.stop": "Bu servis durdurulacak.", + "resource.docker_cleanup": "Docker temizliği çalıştır (kullanılmayan imajları ve oluşturucu önbelleğini kaldır).", + "resource.non_persistent": "Tüm kalıcı olmayan veriler silinecek.", + "resource.delete_volumes": "Bu kaynakla ilişkili tüm hacimler kalıcı olarak silinecek.", + "resource.delete_connected_networks": "Bu kaynakla ilişkili önceden tanımlanmamış tüm ağlar kalıcı olarak silinecek.", + "resource.delete_configurations": "Sunucudaki tüm yapılandırma dosyaları kalıcı olarak silinecek.", + "database.delete_backups_locally": "Tüm yedekler yerel depolamadan kalıcı olarak silinecek.", + "warning.sslipdomain": "Yapılandırmanız kaydedildi, ancak sslip domain ile https ÖNERİLMEZ, çünkü Let's Encrypt sunucuları bu genel domain ile sınırlandırılmıştır (SSL sertifikası doğrulaması başarısız olur).

Bunun yerine kendi domaininizi kullanın." } diff --git a/openapi.json b/openapi.json index 819f229cc..3956c4380 100644 --- a/openapi.json +++ b/openapi.json @@ -2105,6 +2105,70 @@ ] } }, + "\/applications\/{uuid}\/logs": { + "get": { + "tags": [ + "Applications" + ], + "summary": "Get application logs.", + "description": "Get application logs by UUID.", + "operationId": "get-application-logs-by-uuid", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "UUID of the application.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "lines", + "in": "query", + "description": "Number of lines to show from the end of the logs.", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 100 + } + } + ], + "responses": { + "200": { + "description": "Get application logs by UUID.", + "content": { + "application\/json": { + "schema": { + "properties": { + "logs": { + "type": "string" + } + }, + "type": "object" + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + }, + "404": { + "$ref": "#\/components\/responses\/404" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, "\/applications\/{uuid}\/envs": { "get": { "tags": [ @@ -2707,80 +2771,6 @@ ] } }, - "\/applications\/{uuid}\/execute": { - "post": { - "tags": [ - "Applications" - ], - "summary": "Execute Command", - "description": "Execute a command on the application's current container.", - "operationId": "execute-command-application", - "parameters": [ - { - "name": "uuid", - "in": "path", - "description": "UUID of the application.", - "required": true, - "schema": { - "type": "string", - "format": "uuid" - } - } - ], - "requestBody": { - "description": "Command to execute.", - "required": true, - "content": { - "application\/json": { - "schema": { - "properties": { - "command": { - "type": "string", - "description": "Command to execute." - } - }, - "type": "object" - } - } - } - }, - "responses": { - "200": { - "description": "Execute a command on the application's current container.", - "content": { - "application\/json": { - "schema": { - "properties": { - "message": { - "type": "string", - "example": "Command executed." - }, - "response": { - "type": "string" - } - }, - "type": "object" - } - } - } - }, - "401": { - "$ref": "#\/components\/responses\/401" - }, - "400": { - "$ref": "#\/components\/responses\/400" - }, - "404": { - "$ref": "#\/components\/responses\/404" - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, "\/databases": { "get": { "tags": [ @@ -4477,6 +4467,14 @@ "schema": { "type": "boolean" } + }, + { + "name": "pr", + "in": "query", + "description": "Pull Request Id for deploying specific PR builds. Cannot be used with tag parameter.", + "schema": { + "type": "integer" + } } ], "responses": { @@ -4523,6 +4521,42 @@ ] } }, + "\/deployments\/applications\/{uuid}": { + "get": { + "tags": [ + "Deployments" + ], + "summary": "List application deployments", + "description": "List application deployments by using the app uuid", + "operationId": "list-deployments-by-app-uuid", + "responses": { + "200": { + "description": "List application deployments by using the app uuid.", + "content": { + "application\/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#\/components\/schemas\/Application" + } + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, "\/version": { "get": { "summary": "Version", @@ -5854,8 +5888,8 @@ "tags": [ "Services" ], - "summary": "Create", - "description": "Create a one-click service", + "summary": "Create service", + "description": "Create a one-click \/ custom service", "operationId": "create-service", "requestBody": { "required": true, @@ -5866,8 +5900,7 @@ "server_uuid", "project_uuid", "environment_name", - "environment_uuid", - "type" + "environment_uuid" ], "properties": { "type": { @@ -5996,6 +6029,10 @@ "type": "boolean", "default": false, "description": "Start the service immediately after creation." + }, + "docker_compose_raw": { + "type": "string", + "description": "The Docker Compose raw content." } }, "type": "object" @@ -6005,7 +6042,7 @@ }, "responses": { "201": { - "description": "Create a service.", + "description": "Service created successfully.", "content": { "application\/json": { "schema": { @@ -6177,6 +6214,114 @@ "bearerAuth": [] } ] + }, + "patch": { + "tags": [ + "Services" + ], + "summary": "Update", + "description": "Update service by UUID.", + "operationId": "update-service-by-uuid", + "requestBody": { + "description": "Service updated.", + "required": true, + "content": { + "application\/json": { + "schema": { + "required": [ + "server_uuid", + "project_uuid", + "environment_name", + "environment_uuid", + "docker_compose_raw" + ], + "properties": { + "name": { + "type": "string", + "description": "The service name." + }, + "description": { + "type": "string", + "description": "The service description." + }, + "project_uuid": { + "type": "string", + "description": "The project UUID." + }, + "environment_name": { + "type": "string", + "description": "The environment name." + }, + "environment_uuid": { + "type": "string", + "description": "The environment UUID." + }, + "server_uuid": { + "type": "string", + "description": "The server UUID." + }, + "destination_uuid": { + "type": "string", + "description": "The destination UUID." + }, + "instant_deploy": { + "type": "boolean", + "description": "The flag to indicate if the service should be deployed instantly." + }, + "connect_to_docker_network": { + "type": "boolean", + "default": false, + "description": "Connect the service to the predefined docker network." + }, + "docker_compose_raw": { + "type": "string", + "description": "The Docker Compose raw content." + } + }, + "type": "object" + } + } + } + }, + "responses": { + "200": { + "description": "Service updated.", + "content": { + "application\/json": { + "schema": { + "properties": { + "uuid": { + "type": "string", + "description": "Service UUID." + }, + "domains": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Service domains." + } + }, + "type": "object" + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + }, + "404": { + "$ref": "#\/components\/responses\/404" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] } }, "\/services\/{uuid}\/envs": { diff --git a/openapi.yaml b/openapi.yaml index 2d1803113..a9c538218 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -1494,6 +1494,49 @@ paths: security: - bearerAuth: [] + '/applications/{uuid}/logs': + get: + tags: + - Applications + summary: 'Get application logs.' + description: 'Get application logs by UUID.' + operationId: get-application-logs-by-uuid + parameters: + - + name: uuid + in: path + description: 'UUID of the application.' + required: true + schema: + type: string + format: uuid + - + name: lines + in: query + description: 'Number of lines to show from the end of the logs.' + required: false + schema: + type: integer + format: int32 + default: 100 + responses: + '200': + description: 'Get application logs by UUID.' + content: + application/json: + schema: + properties: + logs: { type: string } + type: object + '401': + $ref: '#/components/responses/401' + '400': + $ref: '#/components/responses/400' + '404': + $ref: '#/components/responses/404' + security: + - + bearerAuth: [] '/applications/{uuid}/envs': get: tags: @@ -1862,52 +1905,6 @@ paths: security: - bearerAuth: [] - '/applications/{uuid}/execute': - post: - tags: - - Applications - summary: 'Execute Command' - description: "Execute a command on the application's current container." - operationId: execute-command-application - parameters: - - - name: uuid - in: path - description: 'UUID of the application.' - required: true - schema: - type: string - format: uuid - requestBody: - description: 'Command to execute.' - required: true - content: - application/json: - schema: - properties: - command: - type: string - description: 'Command to execute.' - type: object - responses: - '200': - description: "Execute a command on the application's current container." - content: - application/json: - schema: - properties: - message: { type: string, example: 'Command executed.' } - response: { type: string } - type: object - '401': - $ref: '#/components/responses/401' - '400': - $ref: '#/components/responses/400' - '404': - $ref: '#/components/responses/404' - security: - - - bearerAuth: [] /databases: get: tags: @@ -3109,6 +3106,12 @@ paths: description: 'Force rebuild (without cache)' schema: type: boolean + - + name: pr + in: query + description: 'Pull Request Id for deploying specific PR builds. Cannot be used with tag parameter.' + schema: + type: integer responses: '200': description: "Get deployment(s) UUID's" @@ -3125,6 +3128,29 @@ paths: security: - bearerAuth: [] + '/deployments/applications/{uuid}': + get: + tags: + - Deployments + summary: 'List application deployments' + description: 'List application deployments by using the app uuid' + operationId: list-deployments-by-app-uuid + responses: + '200': + description: 'List application deployments by using the app uuid.' + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Application' + '401': + $ref: '#/components/responses/401' + '400': + $ref: '#/components/responses/400' + security: + - + bearerAuth: [] /version: get: summary: Version @@ -3952,8 +3978,8 @@ paths: post: tags: - Services - summary: Create - description: 'Create a one-click service' + summary: 'Create service' + description: 'Create a one-click / custom service' operationId: create-service requestBody: required: true @@ -3965,7 +3991,6 @@ paths: - project_uuid - environment_name - environment_uuid - - type properties: type: description: 'The one-click service type' @@ -3998,10 +4023,13 @@ paths: type: boolean default: false description: 'Start the service immediately after creation.' + docker_compose_raw: + type: string + description: 'The Docker Compose raw content.' type: object responses: '201': - description: 'Create a service.' + description: 'Service created successfully.' content: application/json: schema: @@ -4111,6 +4139,76 @@ paths: security: - bearerAuth: [] + patch: + tags: + - Services + summary: Update + description: 'Update service by UUID.' + operationId: update-service-by-uuid + requestBody: + description: 'Service updated.' + required: true + content: + application/json: + schema: + required: + - server_uuid + - project_uuid + - environment_name + - environment_uuid + - docker_compose_raw + properties: + name: + type: string + description: 'The service name.' + description: + type: string + description: 'The service description.' + project_uuid: + type: string + description: 'The project UUID.' + environment_name: + type: string + description: 'The environment name.' + environment_uuid: + type: string + description: 'The environment UUID.' + server_uuid: + type: string + description: 'The server UUID.' + destination_uuid: + type: string + description: 'The destination UUID.' + instant_deploy: + type: boolean + description: 'The flag to indicate if the service should be deployed instantly.' + connect_to_docker_network: + type: boolean + default: false + description: 'Connect the service to the predefined docker network.' + docker_compose_raw: + type: string + description: 'The Docker Compose raw content.' + type: object + responses: + '200': + description: 'Service updated.' + content: + application/json: + schema: + properties: + uuid: { type: string, description: 'Service UUID.' } + domains: { type: array, items: { type: string }, description: 'Service domains.' } + type: object + '401': + $ref: '#/components/responses/401' + '400': + $ref: '#/components/responses/400' + '404': + $ref: '#/components/responses/404' + security: + - + bearerAuth: [] '/services/{uuid}/envs': get: tags: diff --git a/other/nightly/.env.production b/other/nightly/.env.production index 96833c253..fe3c8370e 100644 --- a/other/nightly/.env.production +++ b/other/nightly/.env.production @@ -14,3 +14,5 @@ PUSHER_APP_SECRET= ROOT_USERNAME= ROOT_USER_EMAIL= ROOT_USER_PASSWORD= + +REGISTRY_URL=ghcr.io diff --git a/other/nightly/docker-compose.prod.yml b/other/nightly/docker-compose.prod.yml index 23a65cca6..35fea6403 100644 --- a/other/nightly/docker-compose.prod.yml +++ b/other/nightly/docker-compose.prod.yml @@ -1,6 +1,6 @@ services: coolify: - image: "ghcr.io/coollabsio/coolify:${LATEST_IMAGE:-latest}" + image: "${REGISTRY_URL:-ghcr.io}/coollabsio/coolify:${LATEST_IMAGE:-latest}" volumes: - type: bind source: /data/coolify/source/.env @@ -61,7 +61,7 @@ services: retries: 10 timeout: 2s soketi: - image: 'ghcr.io/coollabsio/coolify-realtime:1.0.6' + image: '${REGISTRY_URL:-ghcr.io}/coollabsio/coolify-realtime:1.0.6' ports: - "${SOKETI_PORT:-6001}:6001" - "6002:6002" diff --git a/other/nightly/install.sh b/other/nightly/install.sh index 944012f86..f0fcdfb4e 100755 --- a/other/nightly/install.sh +++ b/other/nightly/install.sh @@ -9,6 +9,7 @@ ## DOCKER_ADDRESS_POOL_SIZE - Custom Docker address pool size (default: 24) ## DOCKER_POOL_FORCE_OVERRIDE - Force override Docker address pool configuration (default: false) ## AUTOUPDATE - Set to "false" to disable auto-updates +## REGISTRY_URL - Custom registry URL for Docker images (default: ghcr.io) set -e # Exit immediately if a command exits with a non-zero status ## $1 could be empty, so we need to disable this check @@ -17,7 +18,9 @@ set -o pipefail # Cause a pipeline to return the status of the last command that CDN="https://cdn.coollabs.io/coolify-nightly" DATE=$(date +"%Y%m%d-%H%M%S") -VERSION="1.8" +OS_TYPE=$(grep -w "ID" /etc/os-release | cut -d "=" -f 2 | tr -d '"') +ENV_FILE="/data/coolify/source/.env" +VERSION="20" DOCKER_VERSION="27.0" # TODO: Ask for a user CURRENT_USER=$USER @@ -36,6 +39,18 @@ ROOT_USERNAME=${ROOT_USERNAME:-} ROOT_USER_EMAIL=${ROOT_USER_EMAIL:-} ROOT_USER_PASSWORD=${ROOT_USER_PASSWORD:-} +if [ -n "${REGISTRY_URL+x}" ]; then + echo "Using registry URL from environment variable: $REGISTRY_URL" +else + if [ -f "$ENV_FILE" ] && grep -q "^REGISTRY_URL=" "$ENV_FILE"; then + REGISTRY_URL=$(grep "^REGISTRY_URL=" "$ENV_FILE" | cut -d '=' -f2) + echo "Using registry URL from .env: $REGISTRY_URL" + else + REGISTRY_URL="ghcr.io" + echo "Using default registry URL: $REGISTRY_URL" + fi +fi + # Docker address pool configuration defaults DOCKER_ADDRESS_POOL_BASE_DEFAULT="10.0.0.0/8" DOCKER_ADDRESS_POOL_SIZE_DEFAULT=24 @@ -227,8 +242,6 @@ getAJoke() { 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 @@ -305,6 +318,7 @@ echo "| Coolify | $LATEST_VERSION" echo "| Helper | $LATEST_HELPER_VERSION" echo "| Realtime | $LATEST_REALTIME_VERSION" echo "| Docker Pool | $DOCKER_ADDRESS_POOL_BASE (size $DOCKER_ADDRESS_POOL_SIZE)" +echo "| Registry URL | $REGISTRY_URL" echo -e "---------------------------------------------\n" echo -e "1. Installing required packages (curl, wget, git, jq, openssl). " @@ -432,7 +446,7 @@ if [ -x "$(command -v snap)" ]; then fi install_docker() { - curl -s https://releases.rancher.com/install-docker/${DOCKER_VERSION}.sh | sh 2>&1 + curl -s https://releases.rancher.com/install-docker/${DOCKER_VERSION}.sh | sh 2>&1 || true if ! [ -x "$(command -v docker)" ]; then curl -s https://get.docker.com | sh -s -- --version ${DOCKER_VERSION} 2>&1 if ! [ -x "$(command -v docker)" ]; then @@ -481,7 +495,7 @@ if ! [ -x "$(command -v docker)" ]; then 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 + 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 @@ -718,6 +732,16 @@ if [ -n "$ROOT_USERNAME" ] && [ -n "$ROOT_USER_EMAIL" ] && [ -n "$ROOT_USER_PASS fi fi +# Add registry URL to .env file +if [ -n "${REGISTRY_URL+x}" ]; then + # Only update if REGISTRY_URL was explicitly provided + if grep -q "^REGISTRY_URL=" "$ENV_FILE-$DATE"; then + sed -i "s|^REGISTRY_URL=.*|REGISTRY_URL=$REGISTRY_URL|" "$ENV_FILE-$DATE" + else + echo "REGISTRY_URL=$REGISTRY_URL" >>"$ENV_FILE-$DATE" + fi +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 @@ -778,7 +802,11 @@ echo -e " - It could take a while based on your server's performance, network sp echo -e " - Please wait." getAJoke -bash /data/coolify/source/upgrade.sh "${LATEST_VERSION:-latest}" "${LATEST_HELPER_VERSION:-latest}" +if [[ $- == *x* ]]; then + bash -x /data/coolify/source/upgrade.sh "${LATEST_VERSION:-latest}" "${LATEST_HELPER_VERSION:-latest}" "${REGISTRY_URL:-ghcr.io}" +else + bash /data/coolify/source/upgrade.sh "${LATEST_VERSION:-latest}" "${LATEST_HELPER_VERSION:-latest}" "${REGISTRY_URL:-ghcr.io}" +fi echo " - Coolify installed successfully." rm -f $ENV_FILE-$DATE @@ -794,8 +822,17 @@ echo -e "\033[0;35m \____\___/|_| |_|\__, |_| \__,_|\__|\__,_|_|\__,_|\__|_|\___/|_| |_|___(_) |___/ \033[0m" + +IPV4_PUBLIC_IP=$(curl -4s https://ifconfig.io || true) +IPV6_PUBLIC_IP=$(curl -6s https://ifconfig.io || true) + echo -e "\nYour instance is ready to use!\n" -echo -e "You can access Coolify through your Public IP: http://$(curl -4s https://ifconfig.io):8000" +if [ -n "$IPV4_PUBLIC_IP" ]; then + echo -e "You can access Coolify through your Public IPV4: http://$(curl -4s https://ifconfig.io):8000" +fi +if [ -n "$IPV6_PUBLIC_IP" ]; then + echo -e "You can access Coolify through your Public IPv6: http://[$IPV6_PUBLIC_IP]:8000" +fi set +e DEFAULT_PRIVATE_IP=$(ip route get 1 | sed -n 's/^.*src \([0-9.]*\) .*$/\1/p') diff --git a/other/nightly/upgrade.sh b/other/nightly/upgrade.sh index 670072b12..0b031ca75 100644 --- a/other/nightly/upgrade.sh +++ b/other/nightly/upgrade.sh @@ -1,10 +1,11 @@ #!/bin/bash ## Do not modify this file. You will lose the ability to autoupdate! -VERSION="13" +VERSION="15" CDN="https://cdn.coollabs.io/coolify-nightly" LATEST_IMAGE=${1:-latest} LATEST_HELPER_VERSION=${2:-latest} +REGISTRY_URL=${3:-ghcr.io} DATE=$(date +%Y-%m-%d-%H-%M-%S) LOGFILE="/data/coolify/source/upgrade-${DATE}.log" @@ -14,7 +15,7 @@ curl -fsSL $CDN/docker-compose.prod.yml -o /data/coolify/source/docker-compose.p 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 +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 @@ -30,12 +31,17 @@ fi # Make sure coolify network exists # It is created when starting Coolify with docker compose -docker network create --attachable coolify 2>/dev/null +if ! docker network inspect coolify >/dev/null 2>&1; then + if ! docker network create --attachable --ipv6 coolify 2>/dev/null; then + echo "Failed to create coolify network with ipv6. Trying without ipv6..." + docker network create --attachable coolify 2>/dev/null + fi +fi # 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." >> $LOGFILE - 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} bash -c "LATEST_IMAGE=${LATEST_IMAGE} 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" >> $LOGFILE 2>&1 + echo "docker-compose.custom.yml detected." >>$LOGFILE + docker run -v /data/coolify/source:/data/coolify/source -v /var/run/docker.sock:/var/run/docker.sock --rm ${REGISTRY_URL:-ghcr.io}/coollabsio/coolify-helper:${LATEST_HELPER_VERSION} bash -c "LATEST_IMAGE=${LATEST_IMAGE} 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" >>$LOGFILE 2>&1 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} bash -c "LATEST_IMAGE=${LATEST_IMAGE} 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" >> $LOGFILE 2>&1 + docker run -v /data/coolify/source:/data/coolify/source -v /var/run/docker.sock:/var/run/docker.sock --rm ${REGISTRY_URL:-ghcr.io}/coollabsio/coolify-helper:${LATEST_HELPER_VERSION} bash -c "LATEST_IMAGE=${LATEST_IMAGE} 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" >>$LOGFILE 2>&1 fi diff --git a/other/nightly/versions.json b/other/nightly/versions.json index 8d1c91433..50a917a8d 100644 --- a/other/nightly/versions.json +++ b/other/nightly/versions.json @@ -1,13 +1,13 @@ { "coolify": { "v4": { - "version": "4.0.0-beta.397" + "version": "4.0.0-beta.407" }, "nightly": { - "version": "4.0.0-beta.398" + "version": "4.0.0-beta.408" }, "helper": { - "version": "1.0.7" + "version": "1.0.8" }, "realtime": { "version": "1.0.6" diff --git a/package-lock.json b/package-lock.json index 0fb1d210a..ff1e8f7ec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,19 +10,19 @@ "@tailwindcss/typography": "0.5.16", "@xterm/addon-fit": "^0.10.0", "@xterm/xterm": "^5.5.0", - "ioredis": "5.5.0" + "ioredis": "5.6.0" }, "devDependencies": { - "@vitejs/plugin-vue": "5.2.1", - "autoprefixer": "10.4.20", - "axios": "1.7.9", + "@vitejs/plugin-vue": "5.2.3", + "autoprefixer": "10.4.21", + "axios": "1.8.4", "laravel-echo": "2.0.2", "laravel-vite-plugin": "^1.2.0", "postcss": "8.5.3", "pusher-js": "8.4.0", "tailwind-scrollbar": "^3.1.0", "tailwindcss": "3.4.17", - "vite": "^6.1.1", + "vite": "^6.2.4", "vue": "3.5.13" } }, @@ -30,6 +30,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "license": "MIT", "engines": { "node": ">=10" }, @@ -58,13 +59,13 @@ } }, "node_modules/@babel/parser": { - "version": "7.26.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.3.tgz", - "integrity": "sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz", + "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.26.3" + "@babel/types": "^7.27.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -74,9 +75,9 @@ } }, "node_modules/@babel/types": { - "version": "7.26.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.3.tgz", - "integrity": "sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz", + "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==", "dev": true, "license": "MIT", "dependencies": { @@ -88,9 +89,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz", - "integrity": "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.2.tgz", + "integrity": "sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==", "cpu": [ "ppc64" ], @@ -105,9 +106,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.2.tgz", - "integrity": "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.2.tgz", + "integrity": "sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA==", "cpu": [ "arm" ], @@ -122,9 +123,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.2.tgz", - "integrity": "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.2.tgz", + "integrity": "sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w==", "cpu": [ "arm64" ], @@ -139,9 +140,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.2.tgz", - "integrity": "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.2.tgz", + "integrity": "sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==", "cpu": [ "x64" ], @@ -156,9 +157,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.2.tgz", - "integrity": "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.2.tgz", + "integrity": "sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA==", "cpu": [ "arm64" ], @@ -173,9 +174,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.2.tgz", - "integrity": "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.2.tgz", + "integrity": "sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA==", "cpu": [ "x64" ], @@ -190,9 +191,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.2.tgz", - "integrity": "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.2.tgz", + "integrity": "sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w==", "cpu": [ "arm64" ], @@ -207,9 +208,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.2.tgz", - "integrity": "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.2.tgz", + "integrity": "sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ==", "cpu": [ "x64" ], @@ -224,9 +225,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.2.tgz", - "integrity": "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.2.tgz", + "integrity": "sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==", "cpu": [ "arm" ], @@ -241,9 +242,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.2.tgz", - "integrity": "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.2.tgz", + "integrity": "sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==", "cpu": [ "arm64" ], @@ -258,9 +259,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.2.tgz", - "integrity": "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.2.tgz", + "integrity": "sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ==", "cpu": [ "ia32" ], @@ -275,9 +276,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.2.tgz", - "integrity": "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.2.tgz", + "integrity": "sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w==", "cpu": [ "loong64" ], @@ -292,9 +293,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.2.tgz", - "integrity": "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.2.tgz", + "integrity": "sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q==", "cpu": [ "mips64el" ], @@ -309,9 +310,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.2.tgz", - "integrity": "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.2.tgz", + "integrity": "sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==", "cpu": [ "ppc64" ], @@ -326,9 +327,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.2.tgz", - "integrity": "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.2.tgz", + "integrity": "sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==", "cpu": [ "riscv64" ], @@ -343,9 +344,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.2.tgz", - "integrity": "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.2.tgz", + "integrity": "sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q==", "cpu": [ "s390x" ], @@ -360,9 +361,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.2.tgz", - "integrity": "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.2.tgz", + "integrity": "sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg==", "cpu": [ "x64" ], @@ -377,9 +378,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz", - "integrity": "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.2.tgz", + "integrity": "sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw==", "cpu": [ "arm64" ], @@ -394,9 +395,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.2.tgz", - "integrity": "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.2.tgz", + "integrity": "sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg==", "cpu": [ "x64" ], @@ -411,9 +412,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.2.tgz", - "integrity": "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.2.tgz", + "integrity": "sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg==", "cpu": [ "arm64" ], @@ -428,9 +429,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.2.tgz", - "integrity": "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.2.tgz", + "integrity": "sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw==", "cpu": [ "x64" ], @@ -445,9 +446,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.2.tgz", - "integrity": "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.2.tgz", + "integrity": "sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA==", "cpu": [ "x64" ], @@ -462,9 +463,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.2.tgz", - "integrity": "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.2.tgz", + "integrity": "sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q==", "cpu": [ "arm64" ], @@ -479,9 +480,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.2.tgz", - "integrity": "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.2.tgz", + "integrity": "sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==", "cpu": [ "ia32" ], @@ -496,9 +497,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.2.tgz", - "integrity": "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.2.tgz", + "integrity": "sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA==", "cpu": [ "x64" ], @@ -515,7 +516,8 @@ "node_modules/@ioredis/commands": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz", - "integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==" + "integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==", + "license": "MIT" }, "node_modules/@isaacs/cliui": { "version": "8.0.2", @@ -628,9 +630,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.8.tgz", - "integrity": "sha512-q217OSE8DTp8AFHuNHXo0Y86e1wtlfVrXiAlwkIvGRQv9zbc6mE3sjIVfwI8sYUyNxwOg0j/Vm1RKM04JcWLJw==", + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.38.0.tgz", + "integrity": "sha512-ldomqc4/jDZu/xpYU+aRxo3V4mGCV9HeTgUBANI3oIQMOL+SsxB+S2lxMpkFp5UamSS3XuTMQVbsS24R4J4Qjg==", "cpu": [ "arm" ], @@ -642,9 +644,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.8.tgz", - "integrity": "sha512-Gigjz7mNWaOL9wCggvoK3jEIUUbGul656opstjaUSGC3eT0BM7PofdAJaBfPFWWkXNVAXbaQtC99OCg4sJv70Q==", + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.38.0.tgz", + "integrity": "sha512-VUsgcy4GhhT7rokwzYQP+aV9XnSLkkhlEJ0St8pbasuWO/vwphhZQxYEKUP3ayeCYLhk6gEtacRpYP/cj3GjyQ==", "cpu": [ "arm64" ], @@ -656,9 +658,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.8.tgz", - "integrity": "sha512-02rVdZ5tgdUNRxIUrFdcMBZQoaPMrxtwSb+/hOfBdqkatYHR3lZ2A2EGyHq2sGOd0Owk80oV3snlDASC24He3Q==", + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.38.0.tgz", + "integrity": "sha512-buA17AYXlW9Rn091sWMq1xGUvWQFOH4N1rqUxGJtEQzhChxWjldGCCup7r/wUnaI6Au8sKXpoh0xg58a7cgcpg==", "cpu": [ "arm64" ], @@ -670,9 +672,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.8.tgz", - "integrity": "sha512-qIP/elwR/tq/dYRx3lgwK31jkZvMiD6qUtOycLhTzCvrjbZ3LjQnEM9rNhSGpbLXVJYQ3rq39A6Re0h9tU2ynw==", + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.38.0.tgz", + "integrity": "sha512-Mgcmc78AjunP1SKXl624vVBOF2bzwNWFPMP4fpOu05vS0amnLcX8gHIge7q/lDAHy3T2HeR0TqrriZDQS2Woeg==", "cpu": [ "x64" ], @@ -684,9 +686,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.8.tgz", - "integrity": "sha512-IQNVXL9iY6NniYbTaOKdrlVP3XIqazBgJOVkddzJlqnCpRi/yAeSOa8PLcECFSQochzqApIOE1GHNu3pCz+BDA==", + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.38.0.tgz", + "integrity": "sha512-zzJACgjLbQTsscxWqvrEQAEh28hqhebpRz5q/uUd1T7VTwUNZ4VIXQt5hE7ncs0GrF+s7d3S4on4TiXUY8KoQA==", "cpu": [ "arm64" ], @@ -698,9 +700,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.8.tgz", - "integrity": "sha512-TYXcHghgnCqYFiE3FT5QwXtOZqDj5GmaFNTNt3jNC+vh22dc/ukG2cG+pi75QO4kACohZzidsq7yKTKwq/Jq7Q==", + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.38.0.tgz", + "integrity": "sha512-hCY/KAeYMCyDpEE4pTETam0XZS4/5GXzlLgpi5f0IaPExw9kuB+PDTOTLuPtM10TlRG0U9OSmXJ+Wq9J39LvAg==", "cpu": [ "x64" ], @@ -712,9 +714,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.8.tgz", - "integrity": "sha512-A4iphFGNkWRd+5m3VIGuqHnG3MVnqKe7Al57u9mwgbyZ2/xF9Jio72MaY7xxh+Y87VAHmGQr73qoKL9HPbXj1g==", + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.38.0.tgz", + "integrity": "sha512-mimPH43mHl4JdOTD7bUMFhBdrg6f9HzMTOEnzRmXbOZqjijCw8LA5z8uL6LCjxSa67H2xiLFvvO67PT05PRKGg==", "cpu": [ "arm" ], @@ -726,9 +728,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.8.tgz", - "integrity": "sha512-S0lqKLfTm5u+QTxlFiAnb2J/2dgQqRy/XvziPtDd1rKZFXHTyYLoVL58M/XFwDI01AQCDIevGLbQrMAtdyanpA==", + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.38.0.tgz", + "integrity": "sha512-tPiJtiOoNuIH8XGG8sWoMMkAMm98PUwlriOFCCbZGc9WCax+GLeVRhmaxjJtz6WxrPKACgrwoZ5ia/uapq3ZVg==", "cpu": [ "arm" ], @@ -740,9 +742,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.8.tgz", - "integrity": "sha512-jpz9YOuPiSkL4G4pqKrus0pn9aYwpImGkosRKwNi+sJSkz+WU3anZe6hi73StLOQdfXYXC7hUfsQlTnjMd3s1A==", + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.38.0.tgz", + "integrity": "sha512-wZco59rIVuB0tjQS0CSHTTUcEde+pXQWugZVxWaQFdQQ1VYub/sTrNdY76D1MKdN2NB48JDuGABP6o6fqos8mA==", "cpu": [ "arm64" ], @@ -754,9 +756,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.8.tgz", - "integrity": "sha512-KdSfaROOUJXgTVxJNAZ3KwkRc5nggDk+06P6lgi1HLv1hskgvxHUKZ4xtwHkVYJ1Rep4GNo+uEfycCRRxht7+Q==", + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.38.0.tgz", + "integrity": "sha512-fQgqwKmW0REM4LomQ+87PP8w8xvU9LZfeLBKybeli+0yHT7VKILINzFEuggvnV9M3x1Ed4gUBmGUzCo/ikmFbQ==", "cpu": [ "arm64" ], @@ -768,9 +770,9 @@ ] }, "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.8.tgz", - "integrity": "sha512-NyF4gcxwkMFRjgXBM6g2lkT58OWztZvw5KkV2K0qqSnUEqCVcqdh2jN4gQrTn/YUpAcNKyFHfoOZEer9nwo6uQ==", + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.38.0.tgz", + "integrity": "sha512-hz5oqQLXTB3SbXpfkKHKXLdIp02/w3M+ajp8p4yWOWwQRtHWiEOCKtc9U+YXahrwdk+3qHdFMDWR5k+4dIlddg==", "cpu": [ "loong64" ], @@ -782,9 +784,9 @@ ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.8.tgz", - "integrity": "sha512-LMJc999GkhGvktHU85zNTDImZVUCJ1z/MbAJTnviiWmmjyckP5aQsHtcujMjpNdMZPT2rQEDBlJfubhs3jsMfw==", + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.38.0.tgz", + "integrity": "sha512-NXqygK/dTSibQ+0pzxsL3r4Xl8oPqVoWbZV9niqOnIHV/J92fe65pOir0xjkUZDRSPyFRvu+4YOpJF9BZHQImw==", "cpu": [ "ppc64" ], @@ -796,9 +798,23 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.8.tgz", - "integrity": "sha512-xAQCAHPj8nJq1PI3z8CIZzXuXCstquz7cIOL73HHdXiRcKk8Ywwqtx2wrIy23EcTn4aZ2fLJNBB8d0tQENPCmw==", + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.38.0.tgz", + "integrity": "sha512-GEAIabR1uFyvf/jW/5jfu8gjM06/4kZ1W+j1nWTSSB3w6moZEBm7iBtzwQ3a1Pxos2F7Gz+58aVEnZHU295QTg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.38.0.tgz", + "integrity": "sha512-9EYTX+Gus2EGPbfs+fh7l95wVADtSQyYw4DfSBcYdUEAmP2lqSZY0Y17yX/3m5VKGGJ4UmIH5LHLkMJft3bYoA==", "cpu": [ "riscv64" ], @@ -810,9 +826,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.8.tgz", - "integrity": "sha512-DdePVk1NDEuc3fOe3dPPTb+rjMtuFw89gw6gVWxQFAuEqqSdDKnrwzZHrUYdac7A7dXl9Q2Vflxpme15gUWQFA==", + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.38.0.tgz", + "integrity": "sha512-Mpp6+Z5VhB9VDk7RwZXoG2qMdERm3Jw07RNlXHE0bOnEeX+l7Fy4bg+NxfyN15ruuY3/7Vrbpm75J9QHFqj5+Q==", "cpu": [ "s390x" ], @@ -824,9 +840,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.8.tgz", - "integrity": "sha512-8y7ED8gjxITUltTUEJLQdgpbPh1sUQ0kMTmufRF/Ns5tI9TNMNlhWtmPKKHCU0SilX+3MJkZ0zERYYGIVBYHIA==", + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.38.0.tgz", + "integrity": "sha512-vPvNgFlZRAgO7rwncMeE0+8c4Hmc+qixnp00/Uv3ht2x7KYrJ6ERVd3/R0nUtlE6/hu7/HiiNHJ/rP6knRFt1w==", "cpu": [ "x64" ], @@ -838,9 +854,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.8.tgz", - "integrity": "sha512-SCXcP0ZpGFIe7Ge+McxY5zKxiEI5ra+GT3QRxL0pMMtxPfpyLAKleZODi1zdRHkz5/BhueUrYtYVgubqe9JBNQ==", + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.38.0.tgz", + "integrity": "sha512-q5Zv+goWvQUGCaL7fU8NuTw8aydIL/C9abAVGCzRReuj5h30TPx4LumBtAidrVOtXnlB+RZkBtExMsfqkMfb8g==", "cpu": [ "x64" ], @@ -852,9 +868,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.8.tgz", - "integrity": "sha512-YHYsgzZgFJzTRbth4h7Or0m5O74Yda+hLin0irAIobkLQFRQd1qWmnoVfwmKm9TXIZVAD0nZ+GEb2ICicLyCnQ==", + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.38.0.tgz", + "integrity": "sha512-u/Jbm1BU89Vftqyqbmxdq14nBaQjQX1HhmsdBWqSdGClNaKwhjsg5TpW+5Ibs1mb8Es9wJiMdl86BcmtUVXNZg==", "cpu": [ "arm64" ], @@ -866,9 +882,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.8.tgz", - "integrity": "sha512-r3NRQrXkHr4uWy5TOjTpTYojR9XmF0j/RYgKCef+Ag46FWUTltm5ziticv8LdNsDMehjJ543x/+TJAek/xBA2w==", + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.38.0.tgz", + "integrity": "sha512-mqu4PzTrlpNHHbu5qleGvXJoGgHpChBlrBx/mEhTPpnAL1ZAYFlvHD7rLK839LLKQzqEQMFJfGrrOHItN4ZQqA==", "cpu": [ "ia32" ], @@ -880,9 +896,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.8.tgz", - "integrity": "sha512-U0FaE5O1BCpZSeE6gBl3c5ObhePQSfk9vDRToMmTkbhCOgW4jqvtS5LGyQ76L1fH8sM0keRp4uDTsbjiUyjk0g==", + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.38.0.tgz", + "integrity": "sha512-jjqy3uWlecfB98Psxb5cD6Fny9Fupv9LrDSPTQZUROqjvZmcCqNu4UMl7qqhlUUGpwiAkotj6GYu4SZdcr/nLw==", "cpu": [ "x64" ], @@ -920,29 +936,17 @@ "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" } }, - "node_modules/@tailwindcss/typography/node_modules/postcss-selector-parser": { - "version": "6.0.10", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", - "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/@types/estree": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", - "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", "dev": true, "license": "MIT" }, "node_modules/@vitejs/plugin-vue": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.1.tgz", - "integrity": "sha512-cxh314tzaWwOLqVes2gnnCtvBDcM1UMdn+iFR+UjAn411dPT3tOmqrJjbMd7koZpMAmBM/GqeV4n9ge7JSiJJQ==", + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.3.tgz", + "integrity": "sha512-IYSLEQj4LgZZuoVpdSUCw3dIynTWQgPlaRP6iAvMle4My0HdYwr5g5wQAfwOeHQBmYwEkqF70nRpSilr6PoUDg==", "dev": true, "license": "MIT", "engines": { @@ -967,13 +971,6 @@ "source-map-js": "^1.2.0" } }, - "node_modules/@vue/compiler-core/node_modules/@vue/shared": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz", - "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==", - "dev": true, - "license": "MIT" - }, "node_modules/@vue/compiler-dom": { "version": "3.5.13", "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz", @@ -985,13 +982,6 @@ "@vue/shared": "3.5.13" } }, - "node_modules/@vue/compiler-dom/node_modules/@vue/shared": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz", - "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==", - "dev": true, - "license": "MIT" - }, "node_modules/@vue/compiler-sfc": { "version": "3.5.13", "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.13.tgz", @@ -1010,13 +1000,6 @@ "source-map-js": "^1.2.0" } }, - "node_modules/@vue/compiler-sfc/node_modules/@vue/shared": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz", - "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==", - "dev": true, - "license": "MIT" - }, "node_modules/@vue/compiler-ssr": { "version": "3.5.13", "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.13.tgz", @@ -1028,12 +1011,15 @@ "@vue/shared": "3.5.13" } }, - "node_modules/@vue/compiler-ssr/node_modules/@vue/shared": { + "node_modules/@vue/reactivity": { "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz", - "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.13.tgz", + "integrity": "sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.13" + } }, "node_modules/@vue/runtime-core": { "version": "3.5.13", @@ -1046,23 +1032,6 @@ "@vue/shared": "3.5.13" } }, - "node_modules/@vue/runtime-core/node_modules/@vue/reactivity": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.13.tgz", - "integrity": "sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vue/shared": "3.5.13" - } - }, - "node_modules/@vue/runtime-core/node_modules/@vue/shared": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz", - "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==", - "dev": true, - "license": "MIT" - }, "node_modules/@vue/runtime-dom": { "version": "3.5.13", "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.13.tgz", @@ -1076,23 +1045,6 @@ "csstype": "^3.1.3" } }, - "node_modules/@vue/runtime-dom/node_modules/@vue/reactivity": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.13.tgz", - "integrity": "sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vue/shared": "3.5.13" - } - }, - "node_modules/@vue/runtime-dom/node_modules/@vue/shared": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz", - "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==", - "dev": true, - "license": "MIT" - }, "node_modules/@vue/server-renderer": { "version": "3.5.13", "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.13.tgz", @@ -1107,7 +1059,7 @@ "vue": "3.5.13" } }, - "node_modules/@vue/server-renderer/node_modules/@vue/shared": { + "node_modules/@vue/shared": { "version": "3.5.13", "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz", "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==", @@ -1118,6 +1070,7 @@ "version": "0.10.0", "resolved": "https://registry.npmjs.org/@xterm/addon-fit/-/addon-fit-0.10.0.tgz", "integrity": "sha512-UFYkDm4HUahf2lnEyHvio51TNGiLK66mqP2JoATy7hRZeXaGMRDr00JiSF7m63vR5WKATF605yEggJKsw0JpMQ==", + "license": "MIT", "peerDependencies": { "@xterm/xterm": "^5.0.0" } @@ -1125,7 +1078,8 @@ "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==" + "integrity": "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==", + "license": "MIT" }, "node_modules/ansi-regex": { "version": "6.1.0", @@ -1161,6 +1115,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "license": "ISC", "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -1179,12 +1134,13 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/autoprefixer": { - "version": "10.4.20", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz", - "integrity": "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==", + "version": "10.4.21", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", + "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", "dev": true, "funding": [ { @@ -1202,11 +1158,11 @@ ], "license": "MIT", "dependencies": { - "browserslist": "^4.23.3", - "caniuse-lite": "^1.0.30001646", + "browserslist": "^4.24.4", + "caniuse-lite": "^1.0.30001702", "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", - "picocolors": "^1.0.1", + "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, "bin": { @@ -1220,9 +1176,9 @@ } }, "node_modules/axios": { - "version": "1.7.9", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", - "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz", + "integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==", "dev": true, "license": "MIT", "dependencies": { @@ -1238,11 +1194,15 @@ "license": "MIT" }, "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "license": "MIT", "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/brace-expansion": { @@ -1258,6 +1218,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", "dependencies": { "fill-range": "^7.1.1" }, @@ -1298,6 +1259,20 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/camelcase-css": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", @@ -1308,9 +1283,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001700", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001700.tgz", - "integrity": "sha512-2S6XIXwaE7K7erT8dY+kLQcpa5ms63XlRkMkReXjle+kf6c5g38vyMl+Z5y8dSxOFDhcFe+nxnn261PLxBSQsQ==", + "version": "1.0.30001707", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001707.tgz", + "integrity": "sha512-3qtRjw/HQSMlDWf+X79N206fepf4SOOU6SQLMaq/0KkZLmSjPxAkBOQQ+FxbHKfHmYLZFfdWsO3KA90ceHPSnw==", "dev": true, "funding": [ { @@ -1356,6 +1331,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", "dependencies": { "is-glob": "^4.0.1" }, @@ -1367,6 +1343,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", + "license": "Apache-2.0", "engines": { "node": ">=0.10.0" } @@ -1394,6 +1371,7 @@ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "dev": true, + "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" }, @@ -1428,6 +1406,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "license": "MIT", "bin": { "cssesc": "bin/cssesc" }, @@ -1443,11 +1422,12 @@ "license": "MIT" }, "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -1463,6 +1443,7 @@ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.4.0" } @@ -1471,6 +1452,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "license": "Apache-2.0", "engines": { "node": ">=0.10" } @@ -1487,6 +1469,21 @@ "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", "license": "MIT" }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -1494,9 +1491,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.103", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.103.tgz", - "integrity": "sha512-P6+XzIkfndgsrjROJWfSvVEgNHtPgbhVyTkwLjUM2HU/h7pZRORgaTlHqfAikqxKmdJMLW8fftrdGWbd/Ds0FA==", + "version": "1.5.128", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.128.tgz", + "integrity": "sha512-bo1A4HH/NS522Ws0QNFIzyPcyUUNV/yyy70Ho1xqfGYzPUme2F/xr4tlEOuM6/A538U1vDA7a4XfCd1CKRegKQ==", "dev": true, "license": "ISC" }, @@ -1519,10 +1516,59 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/esbuild": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.2.tgz", - "integrity": "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.2.tgz", + "integrity": "sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -1533,31 +1579,31 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.24.2", - "@esbuild/android-arm": "0.24.2", - "@esbuild/android-arm64": "0.24.2", - "@esbuild/android-x64": "0.24.2", - "@esbuild/darwin-arm64": "0.24.2", - "@esbuild/darwin-x64": "0.24.2", - "@esbuild/freebsd-arm64": "0.24.2", - "@esbuild/freebsd-x64": "0.24.2", - "@esbuild/linux-arm": "0.24.2", - "@esbuild/linux-arm64": "0.24.2", - "@esbuild/linux-ia32": "0.24.2", - "@esbuild/linux-loong64": "0.24.2", - "@esbuild/linux-mips64el": "0.24.2", - "@esbuild/linux-ppc64": "0.24.2", - "@esbuild/linux-riscv64": "0.24.2", - "@esbuild/linux-s390x": "0.24.2", - "@esbuild/linux-x64": "0.24.2", - "@esbuild/netbsd-arm64": "0.24.2", - "@esbuild/netbsd-x64": "0.24.2", - "@esbuild/openbsd-arm64": "0.24.2", - "@esbuild/openbsd-x64": "0.24.2", - "@esbuild/sunos-x64": "0.24.2", - "@esbuild/win32-arm64": "0.24.2", - "@esbuild/win32-ia32": "0.24.2", - "@esbuild/win32-x64": "0.24.2" + "@esbuild/aix-ppc64": "0.25.2", + "@esbuild/android-arm": "0.25.2", + "@esbuild/android-arm64": "0.25.2", + "@esbuild/android-x64": "0.25.2", + "@esbuild/darwin-arm64": "0.25.2", + "@esbuild/darwin-x64": "0.25.2", + "@esbuild/freebsd-arm64": "0.25.2", + "@esbuild/freebsd-x64": "0.25.2", + "@esbuild/linux-arm": "0.25.2", + "@esbuild/linux-arm64": "0.25.2", + "@esbuild/linux-ia32": "0.25.2", + "@esbuild/linux-loong64": "0.25.2", + "@esbuild/linux-mips64el": "0.25.2", + "@esbuild/linux-ppc64": "0.25.2", + "@esbuild/linux-riscv64": "0.25.2", + "@esbuild/linux-s390x": "0.25.2", + "@esbuild/linux-x64": "0.25.2", + "@esbuild/netbsd-arm64": "0.25.2", + "@esbuild/netbsd-x64": "0.25.2", + "@esbuild/openbsd-arm64": "0.25.2", + "@esbuild/openbsd-x64": "0.25.2", + "@esbuild/sunos-x64": "0.25.2", + "@esbuild/win32-arm64": "0.25.2", + "@esbuild/win32-ia32": "0.25.2", + "@esbuild/win32-x64": "0.25.2" } }, "node_modules/escalade": { @@ -1606,9 +1652,9 @@ } }, "node_modules/fastq": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.0.tgz", - "integrity": "sha512-7SFSRCNjBQIZH/xZR3iy5iQYR8aGBE0h3VG6/cwlbrpdciNYBMotQav8c1XI3HjHH+NikUpP53nPdlZSdWmFzA==", + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", "license": "ISC", "dependencies": { "reusify": "^1.0.4" @@ -1618,6 +1664,7 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -1626,9 +1673,9 @@ } }, "node_modules/follow-redirects": { - "version": "1.15.6", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", - "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", "dev": true, "funding": [ { @@ -1636,6 +1683,7 @@ "url": "https://github.com/sponsors/RubenVerborgh" } ], + "license": "MIT", "engines": { "node": ">=4.0" }, @@ -1646,12 +1694,12 @@ } }, "node_modules/foreground-child": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", - "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", "license": "ISC", "dependencies": { - "cross-spawn": "^7.0.0", + "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" }, "engines": { @@ -1662,13 +1710,15 @@ } }, "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", "dev": true, + "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", "mime-types": "^2.1.12" }, "engines": { @@ -1694,6 +1744,7 @@ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "hasInstallScript": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -1711,6 +1762,45 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/glob": { "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", @@ -1743,6 +1833,48 @@ "node": ">=10.13.0" } }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -1756,9 +1888,9 @@ } }, "node_modules/ioredis": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.5.0.tgz", - "integrity": "sha512-7CutT89g23FfSa8MDoIFs2GYYa0PaNiW/OrT+nRyjRXHDZd17HmIgy+reOQ/yhh72NznNjGuS8kbCAcA4Ro4mw==", + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.6.0.tgz", + "integrity": "sha512-tBZlIIWbndeWBWCXWZiqtOF/yxf6yZX3tAlTJ7nfo5jhd6dctNxF7QnYlZLZ1a0o0pDoen7CgZqO+zjNaFbJAg==", "license": "MIT", "dependencies": { "@ioredis/commands": "^1.1.1", @@ -1783,6 +1915,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "license": "MIT", "dependencies": { "binary-extensions": "^2.0.0" }, @@ -1809,6 +1942,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -1826,6 +1960,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" }, @@ -1837,6 +1972,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", "engines": { "node": ">=0.12.0" } @@ -1863,9 +1999,9 @@ } }, "node_modules/jiti": { - "version": "1.21.6", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz", - "integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==", + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", "license": "MIT", "bin": { "jiti": "bin/jiti.js" @@ -1922,27 +2058,32 @@ "node_modules/lodash.castarray": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz", - "integrity": "sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==" + "integrity": "sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==", + "license": "MIT" }, "node_modules/lodash.defaults": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", - "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==" + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", + "license": "MIT" }, "node_modules/lodash.isarguments": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", - "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==" + "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==", + "license": "MIT" }, "node_modules/lodash.isplainobject": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "license": "MIT" }, "node_modules/lru-cache": { "version": "10.4.3", @@ -1951,15 +2092,25 @@ "license": "ISC" }, "node_modules/magic-string": { - "version": "0.30.15", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.15.tgz", - "integrity": "sha512-zXeaYRgZ6ldS1RJJUrMrYgNJ4fdwnyI6tVqoiIhyCyv5IVTK9BU8Ic2l253GGETQHxI4HNUwhJ3fjDhKqEoaAw==", + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -1987,6 +2138,7 @@ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -1996,6 +2148,7 @@ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dev": true, + "license": "MIT", "dependencies": { "mime-db": "1.52.0" }, @@ -2007,6 +2160,7 @@ "version": "1.4.4", "resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz", "integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==", + "license": "MIT", "bin": { "mini-svg-data-uri": "cli.js" } @@ -2036,9 +2190,10 @@ } }, "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" }, "node_modules/mz": { "version": "2.7.0", @@ -2052,9 +2207,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.8", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", - "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "funding": [ { "type": "github", @@ -2080,6 +2235,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -2159,6 +2315,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", "engines": { "node": ">=8.6" }, @@ -2176,9 +2333,9 @@ } }, "node_modules/pirates": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", - "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", "license": "MIT", "engines": { "node": ">= 6" @@ -2308,7 +2465,7 @@ "postcss": "^8.2.14" } }, - "node_modules/postcss-selector-parser": { + "node_modules/postcss-nested/node_modules/postcss-selector-parser": { "version": "6.1.2", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", @@ -2321,6 +2478,19 @@ "node": ">=4" } }, + "node_modules/postcss-selector-parser": { + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", + "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/postcss-value-parser": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", @@ -2331,7 +2501,8 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/pusher-js": { "version": "8.4.0", @@ -2376,6 +2547,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "license": "MIT", "dependencies": { "picomatch": "^2.2.1" }, @@ -2387,6 +2559,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", "integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==", + "license": "MIT", "engines": { "node": ">=4" } @@ -2395,6 +2568,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", "integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==", + "license": "MIT", "dependencies": { "redis-errors": "^1.0.0" }, @@ -2423,9 +2597,9 @@ } }, "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", "license": "MIT", "engines": { "iojs": ">=1.0.0", @@ -2433,13 +2607,13 @@ } }, "node_modules/rollup": { - "version": "4.34.8", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.8.tgz", - "integrity": "sha512-489gTVMzAYdiZHFVA/ig/iYFllCcWFHMvUHI1rpFmkoUtRlQxqh6/yiNqnYibjMZ2b/+FUQwldG+aLsEt6bglQ==", + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.38.0.tgz", + "integrity": "sha512-5SsIRtJy9bf1ErAOiFMFzl64Ex9X5V7bnJ+WlFMb+zmP459OSWCEG7b0ERZ+PEU7xPt4OG3RHbrp1LJlXxYTrw==", "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "1.0.6" + "@types/estree": "1.0.7" }, "bin": { "rollup": "dist/bin/rollup" @@ -2449,25 +2623,26 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.34.8", - "@rollup/rollup-android-arm64": "4.34.8", - "@rollup/rollup-darwin-arm64": "4.34.8", - "@rollup/rollup-darwin-x64": "4.34.8", - "@rollup/rollup-freebsd-arm64": "4.34.8", - "@rollup/rollup-freebsd-x64": "4.34.8", - "@rollup/rollup-linux-arm-gnueabihf": "4.34.8", - "@rollup/rollup-linux-arm-musleabihf": "4.34.8", - "@rollup/rollup-linux-arm64-gnu": "4.34.8", - "@rollup/rollup-linux-arm64-musl": "4.34.8", - "@rollup/rollup-linux-loongarch64-gnu": "4.34.8", - "@rollup/rollup-linux-powerpc64le-gnu": "4.34.8", - "@rollup/rollup-linux-riscv64-gnu": "4.34.8", - "@rollup/rollup-linux-s390x-gnu": "4.34.8", - "@rollup/rollup-linux-x64-gnu": "4.34.8", - "@rollup/rollup-linux-x64-musl": "4.34.8", - "@rollup/rollup-win32-arm64-msvc": "4.34.8", - "@rollup/rollup-win32-ia32-msvc": "4.34.8", - "@rollup/rollup-win32-x64-msvc": "4.34.8", + "@rollup/rollup-android-arm-eabi": "4.38.0", + "@rollup/rollup-android-arm64": "4.38.0", + "@rollup/rollup-darwin-arm64": "4.38.0", + "@rollup/rollup-darwin-x64": "4.38.0", + "@rollup/rollup-freebsd-arm64": "4.38.0", + "@rollup/rollup-freebsd-x64": "4.38.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.38.0", + "@rollup/rollup-linux-arm-musleabihf": "4.38.0", + "@rollup/rollup-linux-arm64-gnu": "4.38.0", + "@rollup/rollup-linux-arm64-musl": "4.38.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.38.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.38.0", + "@rollup/rollup-linux-riscv64-gnu": "4.38.0", + "@rollup/rollup-linux-riscv64-musl": "4.38.0", + "@rollup/rollup-linux-s390x-gnu": "4.38.0", + "@rollup/rollup-linux-x64-gnu": "4.38.0", + "@rollup/rollup-linux-x64-musl": "4.38.0", + "@rollup/rollup-win32-arm64-msvc": "4.38.0", + "@rollup/rollup-win32-ia32-msvc": "4.38.0", + "@rollup/rollup-win32-x64-msvc": "4.38.0", "fsevents": "~2.3.2" } }, @@ -2539,7 +2714,8 @@ "node_modules/standard-as-callback": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz", - "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==" + "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==", + "license": "MIT" }, "node_modules/string-width": { "version": "5.1.2", @@ -2721,6 +2897,19 @@ "node": ">=14.0.0" } }, + "node_modules/tailwindcss/node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -2746,6 +2935,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", "dependencies": { "is-number": "^7.0.0" }, @@ -2763,12 +2953,13 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", - "dev": true + "dev": true, + "license": "Unlicense" }, "node_modules/update-browserslist-db": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz", - "integrity": "sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", "dev": true, "funding": [ { @@ -2799,17 +2990,18 @@ "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" }, "node_modules/vite": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.1.1.tgz", - "integrity": "sha512-4GgM54XrwRfrOp297aIYspIti66k56v16ZnqHvrIM7mG+HjDlAwS7p+Srr7J6fGvEdOJ5JcQ/D9T7HhtdXDTzA==", + "version": "6.2.4", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.4.tgz", + "integrity": "sha512-veHMSew8CcRzhL5o8ONjy8gkfmFJAd5Ac16oxBUjlwgX3Gq2Wqr+qNC3TjPIpy7TPV/KporLga5GT9HqdrCizw==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "^0.24.2", - "postcss": "^8.5.2", + "esbuild": "^0.25.0", + "postcss": "^8.5.3", "rollup": "^4.30.1" }, "bin": { @@ -2906,13 +3098,6 @@ } } }, - "node_modules/vue/node_modules/@vue/shared": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz", - "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==", - "dev": true, - "license": "MIT" - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -3020,9 +3205,9 @@ } }, "node_modules/yaml": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.1.tgz", - "integrity": "sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.1.tgz", + "integrity": "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==", "license": "ISC", "bin": { "yaml": "bin.mjs" diff --git a/package.json b/package.json index 0060a1199..43f10e373 100644 --- a/package.json +++ b/package.json @@ -7,16 +7,16 @@ "build": "vite build" }, "devDependencies": { - "@vitejs/plugin-vue": "5.2.1", - "autoprefixer": "10.4.20", - "axios": "1.7.9", + "@vitejs/plugin-vue": "5.2.3", + "autoprefixer": "10.4.21", + "axios": "1.8.4", "laravel-echo": "2.0.2", "laravel-vite-plugin": "^1.2.0", "postcss": "8.5.3", "pusher-js": "8.4.0", "tailwind-scrollbar": "^3.1.0", "tailwindcss": "3.4.17", - "vite": "^6.1.1", + "vite": "^6.2.4", "vue": "3.5.13" }, "dependencies": { @@ -24,6 +24,6 @@ "@tailwindcss/typography": "0.5.16", "@xterm/addon-fit": "^0.10.0", "@xterm/xterm": "^5.5.0", - "ioredis": "5.5.0" + "ioredis": "5.6.0" } } diff --git a/public/svgs/coolify-logo.svg b/public/svgs/coolify-logo.svg new file mode 100644 index 000000000..6f4f641f5 --- /dev/null +++ b/public/svgs/coolify-logo.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/public/svgs/evolution-api.png b/public/svgs/evolution-api.png new file mode 100644 index 000000000..0d26702e9 Binary files /dev/null and b/public/svgs/evolution-api.png differ diff --git a/public/svgs/evolution-api.svg b/public/svgs/evolution-api.svg new file mode 100644 index 000000000..a019d7b4e --- /dev/null +++ b/public/svgs/evolution-api.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/svgs/freescout.png b/public/svgs/freescout.png new file mode 100644 index 000000000..ff282fbc4 Binary files /dev/null and b/public/svgs/freescout.png differ diff --git a/public/svgs/neon.svg b/public/svgs/neon.svg new file mode 100644 index 000000000..ffe172aa9 --- /dev/null +++ b/public/svgs/neon.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/public/vendor/telescope/app.js b/public/vendor/telescope/app.js index 378d6cf43..778774921 100644 --- a/public/vendor/telescope/app.js +++ b/public/vendor/telescope/app.js @@ -1,2 +1,2 @@ /*! For license information please see app.js.LICENSE.txt */ -(()=>{var t,e={2465:(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=Q&&Q.indexOf("edge/")>0;Q&&Q.indexOf("android");var et=Q&&/iphone|ipad|ipod|ios/.test(Q);Q&&/chrome\/\d+/.test(Q),Q&&/phantomjs/.test(Q);var nt,ot=Q&&Q.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 Qt=1,Jt=2;function Zt(t,e,n,o,M,i){return(p(n)||r(n))&&(M=o,o=n,n=void 0),c(i)&&(M=Jt),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===Jt?o=$t(o):M===Qt&&(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 Je="watcher";"".concat(Je," callback"),"".concat(Je," getter"),"".concat(Je," cleanup");var Ze;var tn=function(){function t(t){void 0===t&&(t=!1),this.detached=t,this.active=!0,this.effects=[],this.cleanups=[],this.parent=Ze,!t&&Ze&&(this.index=(Ze.scopes||(Ze.scopes=[])).push(this)-1)}return t.prototype.run=function(t){if(this.active){var e=Ze;try{return Ze=this,t()}finally{Ze=e}}else 0},t.prototype.on=function(){Ze=this},t.prototype.off=function(){Ze=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 Zt(t,e,n,o,p,!1)},t.$createElement=function(e,n,o,p){return Zt(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?Jo(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):Jo(t,e,n)}function Jo(t,e,n){if(vo(n))t.removeAttribute(e);else{if(J&&!Z&&"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 Zo={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=J?"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=Qp(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&&!Z,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;_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)&&Jb(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&&Jb(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),Qc=m((function(t){var e=wo(t);return e&&e.innerHTML})),Jc=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=Qc(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 Jc.call(this,t,e)},no.compile=Yc;var Zc=n(2543),tr=n.n(Zc),er=n(4743),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(4335);const Mr=[{path:"/",redirect:"/requests"},{path:"/mail/:id",name:"mail-preview",component:n(583).A},{path:"/mail",name:"mail",component:n(1574).A},{path:"/exceptions/:id",name:"exception-preview",component:n(3781).A},{path:"/exceptions",name:"exceptions",component:n(2977).A},{path:"/dumps",name:"dumps",component:n(93).A},{path:"/logs/:id",name:"log-preview",component:n(5356).A},{path:"/logs",name:"logs",component:n(8170).A},{path:"/notifications/:id",name:"notification-preview",component:n(5841).A},{path:"/notifications",name:"notifications",component:n(4969).A},{path:"/jobs/:id",name:"job-preview",component:n(8813).A},{path:"/jobs",name:"jobs",component:n(1202).A},{path:"/batches/:id",name:"batch-preview",component:n(9622).A},{path:"/batches",name:"batches",component:n(8888).A},{path:"/events/:id",name:"event-preview",component:n(1119).A},{path:"/events",name:"events",component:n(7380).A},{path:"/cache/:id",name:"cache-preview",component:n(7362).A},{path:"/cache",name:"cache",component:n(8613).A},{path:"/queries/:id",name:"query-preview",component:n(1891).A},{path:"/queries",name:"queries",component:n(5873).A},{path:"/models/:id",name:"model-preview",component:n(5333).A},{path:"/models",name:"models",component:n(9440).A},{path:"/requests/:id",name:"request-preview",component:n(6025).A},{path:"/requests",name:"requests",component:n(2806).A},{path:"/commands/:id",name:"command-preview",component:n(4145).A},{path:"/commands",name:"commands",component:n(346).A},{path:"/schedule/:id",name:"schedule-preview",component:n(9655).A},{path:"/schedule",name:"schedule",component:n(3524).A},{path:"/redis/:id",name:"redis-preview",component:n(6393).A},{path:"/redis",name:"redis",component:n(8872).A},{path:"/monitored-tags",name:"monitored-tags",component:n(5441).A},{path:"/gates/:id",name:"gate-preview",component:n(1905).A},{path:"/gates",name:"gates",component:n(2707).A},{path:"/views/:id",name:"view-preview",component:n(6703).A},{path:"/views",name:"views",component:n(3308).A},{path:"/client-requests/:id",name:"client-request-preview",component:n(6204).A},{path:"/client-requests",name:"client-requests",component:n(5899).A}];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){Qr(t)&&(e.replace?n.replace(M,Vr):n.push(M,Vr))},f={click:Qr};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=Jr(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 Qr(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 Jr(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(Zr){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"),Zr||(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 Jz(this.beforeHooks,t)},Vz.prototype.beforeResolve=function(t){return Jz(this.resolveHooks,t)},Vz.prototype.afterEach=function(t){return Jz(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 Qz=Vz;function Jz(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,Zr&&window.Vue&&window.Vue.use(Vz);var Zz=n(7551),ta=n.n(Zz),ea=n(5072),na=n.n(ea),oa=n(2930),pa={insert:"head",singleton:!1};na()(oa.A,pa);oa.A.locals;n(2754);var Ma=document.head.querySelector('meta[name="csrf-token"]');Ma&&(pr.A.defaults.headers.common["X-CSRF-TOKEN"]=Ma.content),no.use(Qz),window.Popper=n(8851).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 Qz({routes:Mr,mode:"history",base:ba});no.component("vue-json-pretty",ta()),no.component("related-entries",n(4401).A),no.component("index-screen",n(4980).A),no.component("preview-screen",n(9416).A),no.component("alert",n(4445).A),no.component("copy-clipboard",n(1858).A),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.A.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.A.delete(Telescope.basePath+"/telescope-api/entries").then((function(t){return location.reload()}))},keydownListener:function(t){t.metaKey&&"k"===t.key&&this.clearEntries(!1)}}})},8217:(t,e,n)=>{"use strict";n.d(e,{A:()=>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}}}},7526:(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},2754: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",Q=".btn",J=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(Q)[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||J._jQueryInterface.call(p.default(e),"toggle","INPUT"===n.tagName)}})).on(j,H,(function(t){var e=p.default(t.target).closest(Q)[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(Z,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(Jt)?this.hide():this.show()},e.show=function(){var e,n,o=this;if(!(this._isTransitioning||p.default(this._element).hasClass(Jt)||(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(Zt)}))).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(Zt).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(Zt+" "+Jt),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(Jt)){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(Zt+" "+Jt);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(Zn))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(Jn);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(Zn),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(Jn)){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(Zn),"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(Jn)){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(Jn+" "+Zn)},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(Zn)||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!==Qn.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(Jn),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:""},Qo={offset:"number",method:"string",target:"(string|element)"},Jo=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,Qo),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(7526),p=n(251),M=n(4634);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&&(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}},7965:(t,e,n)=>{"use strict";var o=n(6426),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}},251:(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}},4634:t=>{var e={}.toString;t.exports=Array.isArray||function(t){return"[object Array]"==e.call(t)}},4692: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))},Q=function(){rt()},J=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 Z(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))&&Z.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&&J(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",Q),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&&Z.contains(S,t)?-1:e===r||e.ownerDocument==S&&Z.contains(S,e)?1:p?a.call(p,t)-a.call(p,e):0:4&n?-1:1)},r):r}for(t in Z.matches=function(t,e){return Z(t,null,null,e)},Z.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 Z(e,r,null,[t]).length>0},Z.contains=function(t,e){return(t.ownerDocument||t)!=r&&rt(t),g.contains(t,e)},Z.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)},Z.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]||Z.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]&&Z.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=Z.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()]||Z.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 Z(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||"")||Z.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=Z,g.expr[":"]=g.expr.pseudos,g.unique=g.uniqueSort,Z.compile=dt,Z.select=ft,Z.setDocument=rt,Z.tokenize=at,Z.escape=g.escapeSelector,Z.getText=g.text,Z.isXML=g.isXMLDoc,Z.selectors=g.expr,Z.support=g.support,Z.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 Q=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;g.Deferred.exceptionHook=function(t,e){o.console&&o.console.warn&&t&&Q.test(t.name)&&o.console.warn("jQuery.Deferred exception: "+t.message,t.stack,e)},g.readyException=function(t){o.setTimeout((function(){throw t}))};var J=g.Deferred();function Z(){q.removeEventListener("DOMContentLoaded",Z),o.removeEventListener("load",Z),g.ready()}g.fn.ready=function(t){return J.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||J.resolveWith(q,[g]))}}),g.ready.then=J.then,"complete"===q.readyState||"loading"!==q.readyState&&!q.documentElement.doScroll?o.setTimeout(g.ready):(q.addEventListener("DOMContentLoaded",Z),o.addEventListener("load",Z));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=Zt(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=Zt(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=Zt(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):Qt(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(Zt(t,"marginLeft"))||t.getBoundingClientRect().left-Qt(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 Qe(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?Qe(Qe(t,g.ajaxSettings),e):Qe(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 Je={0:200,1223:204},Ze=g.ajaxSettings.xhr();l.cors=!!Ze&&"withCredentials"in Ze,l.ajax=Ze=!!Ze,g.ajaxTransport((function(t){var e,n;if(l.cors||Ze&&!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(Je[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(" diff --git a/resources/views/components/forms/copy-button.blade.php b/resources/views/components/forms/copy-button.blade.php new file mode 100644 index 000000000..12fadc595 --- /dev/null +++ b/resources/views/components/forms/copy-button.blade.php @@ -0,0 +1,18 @@ +@props(['text']) + +
+ + +
diff --git a/resources/views/components/forms/select.blade.php b/resources/views/components/forms/select.blade.php index 4da9eca1b..508a85e0c 100644 --- a/resources/views/components/forms/select.blade.php +++ b/resources/views/components/forms/select.blade.php @@ -1,6 +1,7 @@
@if ($label) -