Merge branch 'next' into feat/disable-default-redirect
This commit is contained in:
@@ -24,3 +24,4 @@ yarn-error.log
|
|||||||
/.ssh
|
/.ssh
|
||||||
.ignition.json
|
.ignition.json
|
||||||
.env.dusk.local
|
.env.dusk.local
|
||||||
|
docker/coolify-realtime/node_modules
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ on:
|
|||||||
- docker/coolify-helper/Dockerfile
|
- docker/coolify-helper/Dockerfile
|
||||||
- docker/coolify-realtime/Dockerfile
|
- docker/coolify-realtime/Dockerfile
|
||||||
- docker/testing-host/Dockerfile
|
- docker/testing-host/Dockerfile
|
||||||
- templates/service-templates.json
|
- templates/**
|
||||||
|
|
||||||
env:
|
env:
|
||||||
GITHUB_REGISTRY: ghcr.io
|
GITHUB_REGISTRY: ghcr.io
|
||||||
@@ -41,7 +41,7 @@ jobs:
|
|||||||
- name: Get Version
|
- name: Get Version
|
||||||
id: version
|
id: version
|
||||||
run: |
|
run: |
|
||||||
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getVersion.php)"|xargs >> $GITHUB_OUTPUT]
|
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getVersion.php)"|xargs >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Build and Push Image
|
- name: Build and Push Image
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
@@ -76,7 +76,7 @@ jobs:
|
|||||||
- name: Get Version
|
- name: Get Version
|
||||||
id: version
|
id: version
|
||||||
run: |
|
run: |
|
||||||
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getVersion.php)"|xargs >> $GITHUB_OUTPUT]
|
echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getVersion.php)"|xargs >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Build and Push Image
|
- name: Build and Push Image
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
|
|||||||
1
.github/workflows/coolify-realtime-next.yml
vendored
1
.github/workflows/coolify-realtime-next.yml
vendored
@@ -8,6 +8,7 @@ on:
|
|||||||
- docker/coolify-realtime/Dockerfile
|
- docker/coolify-realtime/Dockerfile
|
||||||
- docker/coolify-realtime/terminal-server.js
|
- docker/coolify-realtime/terminal-server.js
|
||||||
- docker/coolify-realtime/package.json
|
- docker/coolify-realtime/package.json
|
||||||
|
- docker/coolify-realtime/package-lock.json
|
||||||
- docker/coolify-realtime/soketi-entrypoint.sh
|
- docker/coolify-realtime/soketi-entrypoint.sh
|
||||||
|
|
||||||
env:
|
env:
|
||||||
|
|||||||
1
.github/workflows/coolify-realtime.yml
vendored
1
.github/workflows/coolify-realtime.yml
vendored
@@ -8,6 +8,7 @@ on:
|
|||||||
- docker/coolify-realtime/Dockerfile
|
- docker/coolify-realtime/Dockerfile
|
||||||
- docker/coolify-realtime/terminal-server.js
|
- docker/coolify-realtime/terminal-server.js
|
||||||
- docker/coolify-realtime/package.json
|
- docker/coolify-realtime/package.json
|
||||||
|
- docker/coolify-realtime/package-lock.json
|
||||||
- docker/coolify-realtime/soketi-entrypoint.sh
|
- docker/coolify-realtime/soketi-entrypoint.sh
|
||||||
|
|
||||||
env:
|
env:
|
||||||
|
|||||||
2
.github/workflows/coolify-staging-build.yml
vendored
2
.github/workflows/coolify-staging-build.yml
vendored
@@ -11,7 +11,7 @@ on:
|
|||||||
- docker/coolify-helper/Dockerfile
|
- docker/coolify-helper/Dockerfile
|
||||||
- docker/coolify-realtime/Dockerfile
|
- docker/coolify-realtime/Dockerfile
|
||||||
- docker/testing-host/Dockerfile
|
- docker/testing-host/Dockerfile
|
||||||
- templates/service-templates.json
|
- templates/**
|
||||||
|
|
||||||
env:
|
env:
|
||||||
GITHUB_REGISTRY: ghcr.io
|
GITHUB_REGISTRY: ghcr.io
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -34,3 +34,5 @@ _ide_helper_models.php
|
|||||||
scripts/load-test/*
|
scripts/load-test/*
|
||||||
.ignition.json
|
.ignition.json
|
||||||
.env.dusk.local
|
.env.dusk.local
|
||||||
|
docker/coolify-realtime/node_modules
|
||||||
|
.DS_Store
|
||||||
|
|||||||
65
.gitpod.yml
65
.gitpod.yml
@@ -1,65 +0,0 @@
|
|||||||
tasks:
|
|
||||||
- name: Setup Spin environment and Composer dependencies
|
|
||||||
# Fix because of https://github.com/gitpod-io/gitpod/issues/16614
|
|
||||||
before: sudo curl -o /usr/local/bin/docker-compose -fsSL https://github.com/docker/compose/releases/download/v2.16.0/docker-compose-linux-$(uname -m)
|
|
||||||
init: |
|
|
||||||
cp .env.development.example .env &&
|
|
||||||
sed -i "s#APP_URL=http://localhost#APP_URL=$(gp url 8000)#g" .env
|
|
||||||
sed -i "s#USERID=#USERID=33333#g" .env
|
|
||||||
sed -i "s#GROUPID=#GROUPID=33333#g" .env
|
|
||||||
composer install --ignore-platform-reqs
|
|
||||||
./vendor/bin/spin up -d
|
|
||||||
./vendor/bin/spin exec -u webuser coolify php artisan key:generate
|
|
||||||
./vendor/bin/spin exec -u webuser coolify php artisan storage:link
|
|
||||||
./vendor/bin/spin exec -u webuser coolify php artisan migrate:fresh --seed
|
|
||||||
cat .coolify-logo
|
|
||||||
gp sync-done spin-is-ready
|
|
||||||
|
|
||||||
- name: Install Node dependencies and run Vite
|
|
||||||
command: |
|
|
||||||
echo "Waiting for Sail environment to boot up."
|
|
||||||
gp sync-await spin-is-ready
|
|
||||||
./vendor/bin/spin exec vite npm install
|
|
||||||
./vendor/bin/spin exec vite npm run dev -- --host
|
|
||||||
|
|
||||||
- name: Laravel Queue Worker, listening to code changes
|
|
||||||
command: |
|
|
||||||
echo "Waiting for Sail environment to boot up."
|
|
||||||
gp sync-await spin-is-ready
|
|
||||||
./vendor/bin/spin exec -u webuser coolify php artisan queue:listen
|
|
||||||
|
|
||||||
ports:
|
|
||||||
- port: 5432
|
|
||||||
onOpen: ignore
|
|
||||||
name: PostgreSQL
|
|
||||||
visibility: public
|
|
||||||
- port: 5173
|
|
||||||
onOpen: ignore
|
|
||||||
visibility: public
|
|
||||||
name: Node Server for Vite
|
|
||||||
- port: 8000
|
|
||||||
onOpen: ignore
|
|
||||||
visibility: public
|
|
||||||
name: Coolify
|
|
||||||
|
|
||||||
# Configure vscode
|
|
||||||
vscode:
|
|
||||||
extensions:
|
|
||||||
- bmewburn.vscode-intelephense-client
|
|
||||||
- ikappas.composer
|
|
||||||
- ms-azuretools.vscode-docker
|
|
||||||
- ecmel.vscode-html-css
|
|
||||||
- MehediDracula.php-namespace-resolver
|
|
||||||
- wmaurer.change-case
|
|
||||||
- Equinusocio.vsc-community-material-theme
|
|
||||||
- EditorConfig.EditorConfig
|
|
||||||
- streetsidesoftware.code-spell-checker
|
|
||||||
- rangav.vscode-thunder-client
|
|
||||||
- PKief.material-icon-theme
|
|
||||||
- cierra.livewire-vscode
|
|
||||||
- lennardv.livewire-goto-updated
|
|
||||||
- bradlc.vscode-tailwindcss
|
|
||||||
- heybourn.headwind
|
|
||||||
- adrianwilczynski.alpine-js-intellisense
|
|
||||||
- amiralizadeh9480.laravel-extra-intellisense
|
|
||||||
- shufo.vscode-blade-formatter
|
|
||||||
11
README.md
11
README.md
@@ -22,6 +22,9 @@ curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash
|
|||||||
```
|
```
|
||||||
You can find the installation script source [here](./scripts/install.sh).
|
You can find the installation script source [here](./scripts/install.sh).
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> Please refer to the [docs](https://coolify.io/docs/installation) for more information about the installation.
|
||||||
|
|
||||||
# Support
|
# Support
|
||||||
|
|
||||||
Contact us at [coolify.io/docs/contact](https://coolify.io/docs/contact).
|
Contact us at [coolify.io/docs/contact](https://coolify.io/docs/contact).
|
||||||
@@ -121,7 +124,6 @@ By subscribing to the cloud version, you get the Coolify server for the same pri
|
|||||||
- Better support
|
- Better support
|
||||||
- Less maintenance for you
|
- Less maintenance for you
|
||||||
|
|
||||||
|
|
||||||
# Recognitions
|
# Recognitions
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
@@ -138,6 +140,13 @@ By subscribing to the cloud version, you get the Coolify server for the same pri
|
|||||||
|
|
||||||
<a href="https://trendshift.io/repositories/634" target="_blank"><img src="https://trendshift.io/api/badge/repositories/634" alt="coollabsio%2Fcoolify | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
<a href="https://trendshift.io/repositories/634" target="_blank"><img src="https://trendshift.io/api/badge/repositories/634" alt="coollabsio%2Fcoolify | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
||||||
|
|
||||||
|
# Core Maintainers
|
||||||
|
|
||||||
|
| Andras Bacsai | Peak |
|
||||||
|
|------------|------------|
|
||||||
|
| <img src="https://github.com/andrasbacsai.png" width="200px" alt="Andras Bacsai" /> | <img src="https://github.com/peaklabs-dev.png" width="200px" alt="Peak Labs" /> |
|
||||||
|
| <a href="https://x.com/heyandras"><img src="https://raw.githubusercontent.com/gauravghongde/social-icons/master/SVG/Color/Twitter.svg" width="25px"></a> <a href="https://github.com/andrasbacsai"><img src="https://raw.githubusercontent.com/gauravghongde/social-icons/master/SVG/Color/Github.svg" width="25px"></a> | <a href="https://x.com/peaklabs_dev"><img src="https://raw.githubusercontent.com/gauravghongde/social-icons/master/SVG/Color/Twitter.svg" width="25px"></a> <a href="https://github.com/peaklabs-dev"><img src="https://raw.githubusercontent.com/gauravghongde/social-icons/master/SVG/Color/Github.svg" width="25px"></a> |
|
||||||
|
|
||||||
# Repo Activity
|
# Repo Activity
|
||||||
|
|
||||||

|

|
||||||
|
|||||||
29
RELEASE.md
29
RELEASE.md
@@ -1,6 +1,6 @@
|
|||||||
# Coolify Release Guide
|
# Coolify Release Guide
|
||||||
|
|
||||||
This guide outlines the release process for Coolify, intended for developers and those interested in understanding how releases are managed and deployed.
|
This guide outlines the release process for Coolify, intended for developers and those interested in understanding how Coolify releases are managed and deployed.
|
||||||
|
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
- [Release Process](#release-process)
|
- [Release Process](#release-process)
|
||||||
@@ -19,19 +19,19 @@ This guide outlines the release process for Coolify, intended for developers and
|
|||||||
- Improvements, fixes, and new features are developed on the `next` branch or separate feature branches.
|
- Improvements, fixes, and new features are developed on the `next` branch or separate feature branches.
|
||||||
|
|
||||||
2. **Merging to `main`**
|
2. **Merging to `main`**
|
||||||
- Once ready, changes are merged from the `next` branch into the `main` branch.
|
- Once ready, changes are merged from the `next` branch into the `main` branch (via a pull request).
|
||||||
|
|
||||||
3. **Building the Release**
|
3. **Building the Release**
|
||||||
- After merging to `main`, GitHub Actions automatically builds release images for all architectures and pushes them to the GitHub Container Registry with the version tag and the `latest` tag.
|
- After merging to `main`, GitHub Actions automatically builds release images for all architectures and pushes them to the GitHub Container Registry and Docker Hub with the specific version tag and the `latest` tag.
|
||||||
|
|
||||||
4. **Creating a GitHub Release**
|
4. **Creating a GitHub Release**
|
||||||
- A new GitHub release is manually created with details of the changes made in the version.
|
- A new GitHub release is manually created with details of the changes made in the version.
|
||||||
|
|
||||||
5. **Updating the CDN**
|
5. **Updating the CDN**
|
||||||
- To make a new version publicly available, the version information on the CDN needs to be updated: [https://cdn.coollabs.io/coolify/versions.json](https://cdn.coollabs.io/coolify/versions.json)
|
- To make a new version publicly available, the version information on the CDN needs to be updated manually. After that the new version number will be available at [https://cdn.coollabs.io/coolify/versions.json](https://cdn.coollabs.io/coolify/versions.json).
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> The CDN update may not occur immediately after the GitHub release. It can take hours or even days due to additional testing, stability checks, or potential hotfixes. **The update becomes available only after the CDN is updated.**
|
> The CDN update may not occur immediately after the GitHub release. It can take hours or even days due to additional testing, stability checks, or potential hotfixes. **The update becomes available only after the CDN is updated. After the CDN is updated, a discord announcement will be made in the Production Release channel.**
|
||||||
|
|
||||||
## Version Types
|
## Version Types
|
||||||
|
|
||||||
@@ -39,10 +39,10 @@ This guide outlines the release process for Coolify, intended for developers and
|
|||||||
<summary><strong>Stable (coming soon)</strong></summary>
|
<summary><strong>Stable (coming soon)</strong></summary>
|
||||||
|
|
||||||
- **Stable**
|
- **Stable**
|
||||||
- The production version suitable for stable, production environments (generally recommended).
|
- The production version suitable for stable, production environments (recommended).
|
||||||
- **Update Frequency:** Every 2 to 4 weeks, with more frequent possible hotfixes.
|
- **Update Frequency:** Every 2 to 4 weeks, with more frequent possible fixes.
|
||||||
- **Release Size:** Larger but less frequent releases. Multiple nightly versions are consolidated into a single stable release.
|
- **Release Size:** Larger but less frequent releases. Multiple nightly versions are consolidated into a single stable release.
|
||||||
- **Versioning Scheme:** Follows semantic versioning (e.g., `v4.0.0`).
|
- **Versioning Scheme:** Follows semantic versioning (e.g., `v4.0.0`, `4.1.0`, etc.).
|
||||||
- **Installation Command:**
|
- **Installation Command:**
|
||||||
```bash
|
```bash
|
||||||
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash
|
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash
|
||||||
@@ -57,7 +57,7 @@ This guide outlines the release process for Coolify, intended for developers and
|
|||||||
- The latest development version, suitable for testing the latest changes and experimenting with new features.
|
- The latest development version, suitable for testing the latest changes and experimenting with new features.
|
||||||
- **Update Frequency:** Daily or bi-weekly updates.
|
- **Update Frequency:** Daily or bi-weekly updates.
|
||||||
- **Release Size:** Smaller, more frequent releases.
|
- **Release Size:** Smaller, more frequent releases.
|
||||||
- **Versioning Scheme:** TO BE DETERMINED
|
- **Versioning Scheme:** Follows semantic versioning (e.g., `4.1.0-nightly.1`, `4.1.0-nightly.2`, etc.).
|
||||||
- **Installation Command:**
|
- **Installation Command:**
|
||||||
```bash
|
```bash
|
||||||
curl -fsSL https://cdn.coollabs.io/coolify-nightly/install.sh | bash -s next
|
curl -fsSL https://cdn.coollabs.io/coolify-nightly/install.sh | bash -s next
|
||||||
@@ -73,7 +73,7 @@ This guide outlines the release process for Coolify, intended for developers and
|
|||||||
- **Purpose:** Allows users to test and provide feedback on new features and changes before they become stable.
|
- **Purpose:** Allows users to test and provide feedback on new features and changes before they become stable.
|
||||||
- **Update Frequency:** Available if we think beta testing is necessary.
|
- **Update Frequency:** Available if we think beta testing is necessary.
|
||||||
- **Release Size:** Same size as stable release as it will become the next stabe release after some time.
|
- **Release Size:** Same size as stable release as it will become the next stabe release after some time.
|
||||||
- **Versioning Scheme:** Follows semantic versioning (e.g., `4.1.0-beta.1`).
|
- **Versioning Scheme:** Follows semantic versioning (e.g., `4.1.0-beta.1`, `4.1.0-beta.2`, etc.).
|
||||||
- **Installation Command:**
|
- **Installation Command:**
|
||||||
```bash
|
```bash
|
||||||
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash
|
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash
|
||||||
@@ -117,12 +117,15 @@ When a new version is released and a new GitHub release is created, it doesn't i
|
|||||||
> [!IMPORTANT]
|
> [!IMPORTANT]
|
||||||
> The cloud version of Coolify may be several versions behind the latest GitHub releases even if the CDN is updated. This is intentional to ensure stability and reliability for cloud users and Andras will manully update the cloud version when the update is ready.
|
> The cloud version of Coolify may be several versions behind the latest GitHub releases even if the CDN is updated. This is intentional to ensure stability and reliability for cloud users and Andras will manully update the cloud version when the update is ready.
|
||||||
|
|
||||||
## Manually Update to Specific Versions
|
## Manually Update/ Downgrade to Specific Versions
|
||||||
|
|
||||||
> [!CAUTION]
|
> [!CAUTION]
|
||||||
> Updating to unreleased versions is not recommended and may cause issues. Use at your own risk!
|
> Updating to unreleased versions is not recommended and can cause issues.
|
||||||
|
|
||||||
To update your Coolify instance to a specific (unreleased) version, use the following command:
|
> [!IMPORTANT]
|
||||||
|
> Downgrading is supported but not recommended and can cause issues because of database migrations and other changes.
|
||||||
|
|
||||||
|
To update your Coolify instance to a specific version, use the following command:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash -s <version>
|
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash -s <version>
|
||||||
|
|||||||
23
SECURITY.md
23
SECURITY.md
@@ -2,15 +2,24 @@
|
|||||||
|
|
||||||
## Supported Versions
|
## Supported Versions
|
||||||
|
|
||||||
Use this section to tell people about which versions of your project are
|
Currently supported, maintained and updated versions:
|
||||||
currently being supported with security updates.
|
|
||||||
|
|
||||||
| Version | Supported |
|
| Version | Supported | Support Status |
|
||||||
| ------- | ------------------ |
|
| ------- | ------------------ | -------------- |
|
||||||
| > 4 | :white_check_mark: |
|
| 4.x | :white_check_mark: | Active Development & Security Updates |
|
||||||
| 3 | :x: |
|
| < 4.0 | :x: | End of Life (no security updates) |
|
||||||
|
|
||||||
|
## Security Updates
|
||||||
|
|
||||||
|
We take security seriously. Security updates are released as soon as possible after a vulnerability is discovered and verified.
|
||||||
|
|
||||||
## Reporting a Vulnerability
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
If you have any vulnerability please report at hi@coollabs.io
|
If you discover a security vulnerability, please follow these steps:
|
||||||
|
|
||||||
|
1. **DO NOT** disclose the vulnerability publicly.
|
||||||
|
2. Send a detailed report to: `hi@coollabs.io`.
|
||||||
|
3. Include in your report:
|
||||||
|
- A description of the vulnerability
|
||||||
|
- Steps to reproduce the issue
|
||||||
|
- Potential impact
|
||||||
|
|||||||
37
app/Actions/Application/IsHorizonQueueEmpty.php
Normal file
37
app/Actions/Application/IsHorizonQueueEmpty.php
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Application;
|
||||||
|
|
||||||
|
use Laravel\Horizon\Contracts\JobRepository;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class IsHorizonQueueEmpty
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$hostname = gethostname();
|
||||||
|
$recent = app(JobRepository::class)->getRecent();
|
||||||
|
if ($recent) {
|
||||||
|
$running = $recent->filter(function ($job) use ($hostname) {
|
||||||
|
$payload = json_decode($job->payload);
|
||||||
|
$tags = data_get($payload, 'tags');
|
||||||
|
|
||||||
|
return $job->status != 'completed' &&
|
||||||
|
$job->status != 'failed' &&
|
||||||
|
isset($tags) &&
|
||||||
|
is_array($tags) &&
|
||||||
|
in_array('server:'.$hostname, $tags);
|
||||||
|
});
|
||||||
|
if ($running->count() > 0) {
|
||||||
|
echo 'false';
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
echo 'true';
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ use App\Jobs\ApplicationDeploymentJob;
|
|||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Illuminate\Process\ProcessResult;
|
use Illuminate\Process\ProcessResult;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
use Illuminate\Support\Facades\Process;
|
use Illuminate\Support\Facades\Process;
|
||||||
use Spatie\Activitylog\Models\Activity;
|
use Spatie\Activitylog\Models\Activity;
|
||||||
|
|
||||||
@@ -124,6 +125,7 @@ class RunRemoteProcess
|
|||||||
]));
|
]));
|
||||||
}
|
}
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
|
Log::error('Error calling event: '.$e->getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -99,8 +99,8 @@ class StartClickhouse
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add custom docker run options
|
// Add custom docker run options
|
||||||
$docker_run_options = convert_docker_run_to_compose($this->database->custom_docker_run_options);
|
$docker_run_options = convertDockerRunToCompose($this->database->custom_docker_run_options);
|
||||||
$docker_compose = generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
|
$docker_compose = generateCustomDockerRunOptionsForDatabases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
|
||||||
|
|
||||||
$docker_compose = Yaml::dump($docker_compose, 10);
|
$docker_compose = Yaml::dump($docker_compose, 10);
|
||||||
$docker_compose_base64 = base64_encode($docker_compose);
|
$docker_compose_base64 = base64_encode($docker_compose);
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ class StartDatabase
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if ($database->is_public && $database->public_port) {
|
if ($database->is_public && $database->public_port) {
|
||||||
StartDatabaseProxy::dispatch($database);
|
StartDatabaseProxy::dispatch($database)->onQueue('high');
|
||||||
}
|
}
|
||||||
|
|
||||||
return $activity;
|
return $activity;
|
||||||
|
|||||||
@@ -96,8 +96,8 @@ class StartDragonfly
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add custom docker run options
|
// Add custom docker run options
|
||||||
$docker_run_options = convert_docker_run_to_compose($this->database->custom_docker_run_options);
|
$docker_run_options = convertDockerRunToCompose($this->database->custom_docker_run_options);
|
||||||
$docker_compose = generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
|
$docker_compose = generateCustomDockerRunOptionsForDatabases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
|
||||||
|
|
||||||
$docker_compose = Yaml::dump($docker_compose, 10);
|
$docker_compose = Yaml::dump($docker_compose, 10);
|
||||||
$docker_compose_base64 = base64_encode($docker_compose);
|
$docker_compose_base64 = base64_encode($docker_compose);
|
||||||
|
|||||||
@@ -107,8 +107,8 @@ class StartKeydb
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add custom docker run options
|
// Add custom docker run options
|
||||||
$docker_run_options = convert_docker_run_to_compose($this->database->custom_docker_run_options);
|
$docker_run_options = convertDockerRunToCompose($this->database->custom_docker_run_options);
|
||||||
$docker_compose = generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
|
$docker_compose = generateCustomDockerRunOptionsForDatabases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
|
||||||
$docker_compose = Yaml::dump($docker_compose, 10);
|
$docker_compose = Yaml::dump($docker_compose, 10);
|
||||||
$docker_compose_base64 = base64_encode($docker_compose);
|
$docker_compose_base64 = base64_encode($docker_compose);
|
||||||
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
|
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
|
||||||
|
|||||||
@@ -101,8 +101,8 @@ class StartMariadb
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add custom docker run options
|
// Add custom docker run options
|
||||||
$docker_run_options = convert_docker_run_to_compose($this->database->custom_docker_run_options);
|
$docker_run_options = convertDockerRunToCompose($this->database->custom_docker_run_options);
|
||||||
$docker_compose = generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
|
$docker_compose = generateCustomDockerRunOptionsForDatabases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
|
||||||
|
|
||||||
$docker_compose = Yaml::dump($docker_compose, 10);
|
$docker_compose = Yaml::dump($docker_compose, 10);
|
||||||
$docker_compose_base64 = base64_encode($docker_compose);
|
$docker_compose_base64 = base64_encode($docker_compose);
|
||||||
|
|||||||
@@ -25,6 +25,10 @@ class StartMongodb
|
|||||||
$container_name = $this->database->uuid;
|
$container_name = $this->database->uuid;
|
||||||
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
$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 = [
|
$this->commands = [
|
||||||
"echo 'Starting {$database->name}.'",
|
"echo 'Starting {$database->name}.'",
|
||||||
"mkdir -p $this->configuration_dir",
|
"mkdir -p $this->configuration_dir",
|
||||||
@@ -117,8 +121,8 @@ class StartMongodb
|
|||||||
];
|
];
|
||||||
|
|
||||||
// Add custom docker run options
|
// Add custom docker run options
|
||||||
$docker_run_options = convert_docker_run_to_compose($this->database->custom_docker_run_options);
|
$docker_run_options = convertDockerRunToCompose($this->database->custom_docker_run_options);
|
||||||
$docker_compose = generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
|
$docker_compose = generateCustomDockerRunOptionsForDatabases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
|
||||||
|
|
||||||
$docker_compose = Yaml::dump($docker_compose, 10);
|
$docker_compose = Yaml::dump($docker_compose, 10);
|
||||||
$docker_compose_base64 = base64_encode($docker_compose);
|
$docker_compose_base64 = base64_encode($docker_compose);
|
||||||
|
|||||||
@@ -101,8 +101,8 @@ class StartMysql
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add custom docker run options
|
// Add custom docker run options
|
||||||
$docker_run_options = convert_docker_run_to_compose($this->database->custom_docker_run_options);
|
$docker_run_options = convertDockerRunToCompose($this->database->custom_docker_run_options);
|
||||||
$docker_compose = generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
|
$docker_compose = generateCustomDockerRunOptionsForDatabases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
|
||||||
|
|
||||||
$docker_compose = Yaml::dump($docker_compose, 10);
|
$docker_compose = Yaml::dump($docker_compose, 10);
|
||||||
$docker_compose_base64 = base64_encode($docker_compose);
|
$docker_compose_base64 = base64_encode($docker_compose);
|
||||||
|
|||||||
@@ -122,8 +122,8 @@ class StartPostgresql
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
// Add custom docker run options
|
// Add custom docker run options
|
||||||
$docker_run_options = convert_docker_run_to_compose($this->database->custom_docker_run_options);
|
$docker_run_options = convertDockerRunToCompose($this->database->custom_docker_run_options);
|
||||||
$docker_compose = generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
|
$docker_compose = generateCustomDockerRunOptionsForDatabases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
|
||||||
|
|
||||||
$docker_compose = Yaml::dump($docker_compose, 10);
|
$docker_compose = Yaml::dump($docker_compose, 10);
|
||||||
$docker_compose_base64 = base64_encode($docker_compose);
|
$docker_compose_base64 = base64_encode($docker_compose);
|
||||||
|
|||||||
@@ -110,8 +110,8 @@ class StartRedis
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add custom docker run options
|
// Add custom docker run options
|
||||||
$docker_run_options = convert_docker_run_to_compose($this->database->custom_docker_run_options);
|
$docker_run_options = convertDockerRunToCompose($this->database->custom_docker_run_options);
|
||||||
$docker_compose = generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
|
$docker_compose = generateCustomDockerRunOptionsForDatabases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
|
||||||
|
|
||||||
$docker_compose = Yaml::dump($docker_compose, 10);
|
$docker_compose = Yaml::dump($docker_compose, 10);
|
||||||
$docker_compose_base64 = base64_encode($docker_compose);
|
$docker_compose_base64 = base64_encode($docker_compose);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Actions\Database;
|
namespace App\Actions\Database;
|
||||||
|
|
||||||
use App\Events\DatabaseStatusChanged;
|
use App\Events\DatabaseProxyStopped;
|
||||||
use App\Models\ServiceDatabase;
|
use App\Models\ServiceDatabase;
|
||||||
use App\Models\StandaloneClickhouse;
|
use App\Models\StandaloneClickhouse;
|
||||||
use App\Models\StandaloneDragonfly;
|
use App\Models\StandaloneDragonfly;
|
||||||
@@ -27,7 +27,11 @@ class StopDatabaseProxy
|
|||||||
$server = data_get($database, 'service.server');
|
$server = data_get($database, 'service.server');
|
||||||
}
|
}
|
||||||
instant_remote_process(["docker rm -f {$uuid}-proxy"], $server);
|
instant_remote_process(["docker rm -f {$uuid}-proxy"], $server);
|
||||||
|
|
||||||
|
$database->is_public = false;
|
||||||
$database->save();
|
$database->save();
|
||||||
DatabaseStatusChanged::dispatch();
|
|
||||||
|
DatabaseProxyStopped::dispatch();
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -107,6 +107,8 @@ class GetContainersStatus
|
|||||||
$statusFromDb = $preview->status;
|
$statusFromDb = $preview->status;
|
||||||
if ($statusFromDb !== $containerStatus) {
|
if ($statusFromDb !== $containerStatus) {
|
||||||
$preview->update(['status' => $containerStatus]);
|
$preview->update(['status' => $containerStatus]);
|
||||||
|
} else {
|
||||||
|
$preview->update(['last_online_at' => now()]);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
//Notify user that this container should not be there.
|
//Notify user that this container should not be there.
|
||||||
@@ -118,6 +120,8 @@ class GetContainersStatus
|
|||||||
$statusFromDb = $application->status;
|
$statusFromDb = $application->status;
|
||||||
if ($statusFromDb !== $containerStatus) {
|
if ($statusFromDb !== $containerStatus) {
|
||||||
$application->update(['status' => $containerStatus]);
|
$application->update(['status' => $containerStatus]);
|
||||||
|
} else {
|
||||||
|
$application->update(['last_online_at' => now()]);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
//Notify user that this container should not be there.
|
//Notify user that this container should not be there.
|
||||||
@@ -160,7 +164,10 @@ class GetContainersStatus
|
|||||||
$statusFromDb = $database->status;
|
$statusFromDb = $database->status;
|
||||||
if ($statusFromDb !== $containerStatus) {
|
if ($statusFromDb !== $containerStatus) {
|
||||||
$database->update(['status' => $containerStatus]);
|
$database->update(['status' => $containerStatus]);
|
||||||
|
} else {
|
||||||
|
$database->update(['last_online_at' => now()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($isPublic) {
|
if ($isPublic) {
|
||||||
$foundTcpProxy = $this->containers->filter(function ($value, $key) use ($uuid) {
|
$foundTcpProxy = $this->containers->filter(function ($value, $key) use ($uuid) {
|
||||||
if ($this->server->isSwarm()) {
|
if ($this->server->isSwarm()) {
|
||||||
@@ -171,7 +178,7 @@ class GetContainersStatus
|
|||||||
})->first();
|
})->first();
|
||||||
if (! $foundTcpProxy) {
|
if (! $foundTcpProxy) {
|
||||||
StartDatabaseProxy::run($database);
|
StartDatabaseProxy::run($database);
|
||||||
$this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server));
|
// $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -202,6 +209,8 @@ class GetContainersStatus
|
|||||||
if ($statusFromDb !== $containerStatus) {
|
if ($statusFromDb !== $containerStatus) {
|
||||||
// ray('Updating status: ' . $containerStatus);
|
// ray('Updating status: ' . $containerStatus);
|
||||||
$service->update(['status' => $containerStatus]);
|
$service->update(['status' => $containerStatus]);
|
||||||
|
} else {
|
||||||
|
$service->update(['last_online_at' => now()]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,66 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Actions\License;
|
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Http;
|
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
|
||||||
|
|
||||||
class CheckResaleLicense
|
|
||||||
{
|
|
||||||
use AsAction;
|
|
||||||
|
|
||||||
public function handle()
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$settings = instanceSettings();
|
|
||||||
if (isDev()) {
|
|
||||||
$settings->update([
|
|
||||||
'is_resale_license_active' => true,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// if (!$settings->resale_license) {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
$base_url = config('coolify.license_url');
|
|
||||||
$instance_id = config('app.id');
|
|
||||||
$data = Http::withHeaders([
|
|
||||||
'Accept' => 'application/json',
|
|
||||||
])->get("$base_url/lemon/validate", [
|
|
||||||
'license_key' => $settings->resale_license,
|
|
||||||
'instance_id' => $instance_id,
|
|
||||||
])->json();
|
|
||||||
if (data_get($data, 'valid') === true && data_get($data, 'license_key.status') === 'active') {
|
|
||||||
$settings->update([
|
|
||||||
'is_resale_license_active' => true,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$data = Http::withHeaders([
|
|
||||||
'Accept' => 'application/json',
|
|
||||||
])->get("$base_url/lemon/activate", [
|
|
||||||
'license_key' => $settings->resale_license,
|
|
||||||
'instance_id' => $instance_id,
|
|
||||||
])->json();
|
|
||||||
if (data_get($data, 'activated') === true) {
|
|
||||||
$settings->update([
|
|
||||||
'is_resale_license_active' => true,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (data_get($data, 'license_key.status') === 'active') {
|
|
||||||
throw new \Exception('Invalid license key.');
|
|
||||||
}
|
|
||||||
throw new \Exception('Cannot activate license key.');
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
$settings->update([
|
|
||||||
'resale_license' => null,
|
|
||||||
'is_resale_license_active' => false,
|
|
||||||
]);
|
|
||||||
throw $e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,6 +4,7 @@ namespace App\Actions\Proxy;
|
|||||||
|
|
||||||
use App\Enums\ProxyTypes;
|
use App\Enums\ProxyTypes;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
use Symfony\Component\Yaml\Yaml;
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
@@ -88,6 +89,7 @@ class CheckProxy
|
|||||||
$portsToCheck = [];
|
$portsToCheck = [];
|
||||||
}
|
}
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
|
Log::error('Error checking proxy: '.$e->getMessage());
|
||||||
}
|
}
|
||||||
if (count($portsToCheck) === 0) {
|
if (count($portsToCheck) === 0) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ class CleanupDocker
|
|||||||
{
|
{
|
||||||
$settings = instanceSettings();
|
$settings = instanceSettings();
|
||||||
$helperImageVersion = data_get($settings, 'helper_version');
|
$helperImageVersion = data_get($settings, 'helper_version');
|
||||||
$helperImage = config('coolify.helper_image');
|
$helperImage = config('constants.coolify.helper_image');
|
||||||
$helperImageWithVersion = "$helperImage:$helperImageVersion";
|
$helperImageWithVersion = "$helperImage:$helperImageVersion";
|
||||||
|
|
||||||
$commands = [
|
$commands = [
|
||||||
|
|||||||
@@ -12,11 +12,11 @@ class InstallDocker
|
|||||||
|
|
||||||
public function handle(Server $server)
|
public function handle(Server $server)
|
||||||
{
|
{
|
||||||
|
$dockerVersion = config('constants.docker.minimum_required_version');
|
||||||
$supported_os_type = $server->validateOS();
|
$supported_os_type = $server->validateOS();
|
||||||
if (! $supported_os_type) {
|
if (! $supported_os_type) {
|
||||||
throw new \Exception('Server OS type is not supported for automated installation. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://coolify.io/docs/installation#manually">documentation</a>.');
|
throw new \Exception('Server OS type is not supported for automated installation. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://coolify.io/docs/installation#manually">documentation</a>.');
|
||||||
}
|
}
|
||||||
$dockerVersion = '26.0';
|
|
||||||
$config = base64_encode('{
|
$config = base64_encode('{
|
||||||
"log-driver": "json-file",
|
"log-driver": "json-file",
|
||||||
"log-opts": {
|
"log-opts": {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ use App\Models\Service;
|
|||||||
use App\Models\ServiceApplication;
|
use App\Models\ServiceApplication;
|
||||||
use App\Models\ServiceDatabase;
|
use App\Models\ServiceDatabase;
|
||||||
use App\Notifications\Container\ContainerRestarted;
|
use App\Notifications\Container\ContainerRestarted;
|
||||||
use Arr;
|
use Illuminate\Support\Arr;
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
class ServerCheck
|
class ServerCheck
|
||||||
@@ -130,10 +130,10 @@ class ServerCheck
|
|||||||
if ($foundLogDrainContainer) {
|
if ($foundLogDrainContainer) {
|
||||||
$status = data_get($foundLogDrainContainer, 'State.Status');
|
$status = data_get($foundLogDrainContainer, 'State.Status');
|
||||||
if ($status !== 'running') {
|
if ($status !== 'running') {
|
||||||
StartLogDrain::dispatch($this->server);
|
StartLogDrain::dispatch($this->server)->onQueue('high');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
StartLogDrain::dispatch($this->server);
|
StartLogDrain::dispatch($this->server)->onQueue('high');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,7 +259,7 @@ class ServerCheck
|
|||||||
})->first();
|
})->first();
|
||||||
if (! $foundTcpProxy) {
|
if (! $foundTcpProxy) {
|
||||||
StartDatabaseProxy::run($database);
|
StartDatabaseProxy::run($database);
|
||||||
$this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server));
|
// $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -169,7 +169,7 @@ Files:
|
|||||||
');
|
');
|
||||||
$license_key = $server->settings->logdrain_newrelic_license_key;
|
$license_key = $server->settings->logdrain_newrelic_license_key;
|
||||||
$base_uri = $server->settings->logdrain_newrelic_base_uri;
|
$base_uri = $server->settings->logdrain_newrelic_base_uri;
|
||||||
$base_path = config('coolify.base_config_path');
|
$base_path = config('constants.coolify.base_config_path');
|
||||||
|
|
||||||
$config_path = $base_path.'/log-drains';
|
$config_path = $base_path.'/log-drains';
|
||||||
$fluent_bit_config = $config_path.'/fluent-bit.conf';
|
$fluent_bit_config = $config_path.'/fluent-bit.conf';
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ namespace App\Actions\Server;
|
|||||||
|
|
||||||
use App\Jobs\PullHelperImageJob;
|
use App\Jobs\PullHelperImageJob;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
|
use Illuminate\Support\Sleep;
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
class UpdateCoolify
|
class UpdateCoolify
|
||||||
@@ -18,6 +19,11 @@ class UpdateCoolify
|
|||||||
|
|
||||||
public function handle($manual_update = false)
|
public function handle($manual_update = false)
|
||||||
{
|
{
|
||||||
|
if (isDev()) {
|
||||||
|
Sleep::for(10)->seconds();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
$settings = instanceSettings();
|
$settings = instanceSettings();
|
||||||
$this->server = Server::find(0);
|
$this->server = Server::find(0);
|
||||||
if (! $this->server) {
|
if (! $this->server) {
|
||||||
@@ -44,19 +50,7 @@ class UpdateCoolify
|
|||||||
|
|
||||||
private function update()
|
private function update()
|
||||||
{
|
{
|
||||||
if (isDev()) {
|
PullHelperImageJob::dispatch($this->server);
|
||||||
remote_process([
|
|
||||||
'sleep 10',
|
|
||||||
], $this->server);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$all_servers = Server::all();
|
|
||||||
$servers = $all_servers->where('settings.is_usable', true)->where('settings.is_reachable', true)->where('ip', '!=', '1.2.3.4');
|
|
||||||
foreach ($servers as $server) {
|
|
||||||
PullHelperImageJob::dispatch($server);
|
|
||||||
}
|
|
||||||
|
|
||||||
instant_remote_process(["docker pull -q ghcr.io/coollabsio/coolify:{$this->latestVersion}"], $this->server, false);
|
instant_remote_process(["docker pull -q ghcr.io/coollabsio/coolify:{$this->latestVersion}"], $this->server, false);
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ namespace App\Actions\Service;
|
|||||||
|
|
||||||
use App\Actions\Server\CleanupDocker;
|
use App\Actions\Server\CleanupDocker;
|
||||||
use App\Models\Service;
|
use App\Models\Service;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
class DeleteService
|
class DeleteService
|
||||||
@@ -39,7 +40,8 @@ class DeleteService
|
|||||||
if (! empty($commands)) {
|
if (! empty($commands)) {
|
||||||
foreach ($commands as $command) {
|
foreach ($commands as $command) {
|
||||||
$result = instant_remote_process([$command], $server, false);
|
$result = instant_remote_process([$command], $server, false);
|
||||||
if ($result !== 0) {
|
if ($result !== null && $result !== 0) {
|
||||||
|
Log::error('Error deleting volumes: '.$result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ class CleanupRedis extends Command
|
|||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
echo "Cleanup Redis keys.\n";
|
|
||||||
$prefix = config('database.redis.options.prefix');
|
$prefix = config('database.redis.options.prefix');
|
||||||
|
|
||||||
$keys = Redis::connection()->keys('*:laravel*');
|
$keys = Redis::connection()->keys('*:laravel*');
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ class CleanupStuckedResources extends Command
|
|||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
echo "Running cleanup stucked resources.\n";
|
|
||||||
$this->cleanup_stucked_resources();
|
$this->cleanup_stucked_resources();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
49
app/Console/Commands/CloudCheckSubscription.php
Normal file
49
app/Console/Commands/CloudCheckSubscription.php
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Models\Team;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
|
class CloudCheckSubscription extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The name and signature of the console command.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'cloud:check-subscription';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The console command description.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = 'Check Cloud subscriptions';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$stripe = new \Stripe\StripeClient(config('subscription.stripe_api_key'));
|
||||||
|
$activeSubscribers = Team::whereRelation('subscription', 'stripe_invoice_paid', true)->get();
|
||||||
|
foreach ($activeSubscribers as $team) {
|
||||||
|
$stripeSubscriptionId = $team->subscription->stripe_subscription_id;
|
||||||
|
$stripeInvoicePaid = $team->subscription->stripe_invoice_paid;
|
||||||
|
$stripeCustomerId = $team->subscription->stripe_customer_id;
|
||||||
|
if (! $stripeSubscriptionId) {
|
||||||
|
echo "Team {$team->id} has no subscription, but invoice status is: {$stripeInvoicePaid}\n";
|
||||||
|
echo "Link on Stripe: https://dashboard.stripe.com/customers/{$stripeCustomerId}\n";
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$subscription = $stripe->subscriptions->retrieve($stripeSubscriptionId);
|
||||||
|
if ($subscription->status === 'active') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
echo "Subscription {$stripeSubscriptionId} is not active ({$subscription->status})\n";
|
||||||
|
echo "Link on Stripe: https://dashboard.stripe.com/subscriptions/{$stripeSubscriptionId}\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ use App\Models\InstanceSettings;
|
|||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Illuminate\Support\Facades\Artisan;
|
use Illuminate\Support\Facades\Artisan;
|
||||||
use Illuminate\Support\Facades\Process;
|
use Illuminate\Support\Facades\Process;
|
||||||
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
class Dev extends Command
|
class Dev extends Command
|
||||||
{
|
{
|
||||||
@@ -31,19 +32,32 @@ class Dev extends Command
|
|||||||
{
|
{
|
||||||
// Generate OpenAPI documentation
|
// Generate OpenAPI documentation
|
||||||
echo "Generating OpenAPI documentation.\n";
|
echo "Generating OpenAPI documentation.\n";
|
||||||
$process = Process::run(['/var/www/html/vendor/bin/openapi', 'app', '-o', 'openapi.yaml']);
|
// 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 = $process->errorOutput();
|
||||||
$error = preg_replace('/^.*an object literal,.*$/m', '', $error);
|
$error = preg_replace('/^.*an object literal,.*$/m', '', $error);
|
||||||
$error = preg_replace('/^\h*\v+/m', '', $error);
|
$error = preg_replace('/^\h*\v+/m', '', $error);
|
||||||
echo $error;
|
echo $error;
|
||||||
echo $process->output();
|
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()
|
public function init()
|
||||||
{
|
{
|
||||||
// Generate APP_KEY if not exists
|
// Generate APP_KEY if not exists
|
||||||
|
|
||||||
if (empty(env('APP_KEY'))) {
|
if (empty(config('app.key'))) {
|
||||||
echo "Generating APP_KEY.\n";
|
echo "Generating APP_KEY.\n";
|
||||||
Artisan::call('key:generate');
|
Artisan::call('key:generate');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ class Horizon extends Command
|
|||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
if (config('coolify.is_horizon_enabled')) {
|
if (config('constants.horizon.is_horizon_enabled')) {
|
||||||
$this->info('Horizon is enabled. Starting.');
|
$this->info('[x]: Horizon is enabled. Starting.');
|
||||||
$this->call('horizon');
|
$this->call('horizon');
|
||||||
exit(0);
|
exit(0);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
namespace App\Console\Commands;
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
use App\Actions\Server\StopSentinel;
|
|
||||||
use App\Enums\ActivityTypes;
|
use App\Enums\ActivityTypes;
|
||||||
use App\Enums\ApplicationDeploymentStatus;
|
use App\Enums\ApplicationDeploymentStatus;
|
||||||
|
use App\Jobs\CheckHelperImageJob;
|
||||||
use App\Models\ApplicationDeploymentQueue;
|
use App\Models\ApplicationDeploymentQueue;
|
||||||
use App\Models\Environment;
|
use App\Models\Environment;
|
||||||
use App\Models\ScheduledDatabaseBackup;
|
use App\Models\ScheduledDatabaseBackup;
|
||||||
@@ -12,6 +12,7 @@ use App\Models\Server;
|
|||||||
use App\Models\StandalonePostgresql;
|
use App\Models\StandalonePostgresql;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Support\Facades\Artisan;
|
||||||
use Illuminate\Support\Facades\File;
|
use Illuminate\Support\Facades\File;
|
||||||
use Illuminate\Support\Facades\Http;
|
use Illuminate\Support\Facades\Http;
|
||||||
|
|
||||||
@@ -25,6 +26,8 @@ class Init extends Command
|
|||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
|
$this->optimize();
|
||||||
|
|
||||||
if (isCloud() && ! $this->option('force-cloud')) {
|
if (isCloud() && ! $this->option('force-cloud')) {
|
||||||
echo "Skipping init as we are on cloud and --force-cloud option is not set\n";
|
echo "Skipping init as we are on cloud and --force-cloud option is not set\n";
|
||||||
|
|
||||||
@@ -39,7 +42,6 @@ class Init extends Command
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Backward compatibility
|
// Backward compatibility
|
||||||
$this->disable_metrics();
|
|
||||||
$this->replace_slash_in_environment_name();
|
$this->replace_slash_in_environment_name();
|
||||||
$this->restore_coolify_db_backup();
|
$this->restore_coolify_db_backup();
|
||||||
$this->update_user_emails();
|
$this->update_user_emails();
|
||||||
@@ -53,16 +55,32 @@ class Init extends Command
|
|||||||
} else {
|
} else {
|
||||||
$this->cleanup_in_progress_application_deployments();
|
$this->cleanup_in_progress_application_deployments();
|
||||||
}
|
}
|
||||||
|
echo "[3]: Cleanup Redis keys.\n";
|
||||||
$this->call('cleanup:redis');
|
$this->call('cleanup:redis');
|
||||||
|
|
||||||
|
echo "[4]: Cleanup stucked resources.\n";
|
||||||
$this->call('cleanup:stucked-resources');
|
$this->call('cleanup:stucked-resources');
|
||||||
|
|
||||||
if (isCloud()) {
|
try {
|
||||||
$response = Http::retry(3, 1000)->get(config('constants.services.official'));
|
$this->pullHelperImage();
|
||||||
if ($response->successful()) {
|
} catch (\Throwable $e) {
|
||||||
$services = $response->json();
|
//
|
||||||
File::put(base_path('templates/service-templates.json'), json_encode($services));
|
}
|
||||||
|
|
||||||
|
if (isCloud()) {
|
||||||
|
try {
|
||||||
|
$this->pullTemplatesFromCDN();
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Could not pull templates from CDN: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! isCloud()) {
|
||||||
|
try {
|
||||||
|
$this->pullTemplatesFromCDN();
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Could not pull templates from CDN: {$e->getMessage()}\n";
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
try {
|
try {
|
||||||
$localhost = $this->servers->where('id', 0)->first();
|
$localhost = $this->servers->where('id', 0)->first();
|
||||||
$localhost->setupDynamicProxyConfiguration();
|
$localhost->setupDynamicProxyConfiguration();
|
||||||
@@ -70,8 +88,8 @@ class Init extends Command
|
|||||||
echo "Could not setup dynamic configuration: {$e->getMessage()}\n";
|
echo "Could not setup dynamic configuration: {$e->getMessage()}\n";
|
||||||
}
|
}
|
||||||
$settings = instanceSettings();
|
$settings = instanceSettings();
|
||||||
if (! is_null(env('AUTOUPDATE', null))) {
|
if (! is_null(config('constants.coolify.autoupdate', null))) {
|
||||||
if (env('AUTOUPDATE') == true) {
|
if (config('constants.coolify.autoupdate') == true) {
|
||||||
$settings->update(['is_auto_update_enabled' => true]);
|
$settings->update(['is_auto_update_enabled' => true]);
|
||||||
} else {
|
} else {
|
||||||
$settings->update(['is_auto_update_enabled' => false]);
|
$settings->update(['is_auto_update_enabled' => false]);
|
||||||
@@ -80,18 +98,25 @@ class Init extends Command
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function disable_metrics()
|
private function pullHelperImage()
|
||||||
{
|
{
|
||||||
if (version_compare('4.0.0-beta.312', config('version'), '<=')) {
|
CheckHelperImageJob::dispatch();
|
||||||
foreach ($this->servers as $server) {
|
|
||||||
if ($server->settings->is_metrics_enabled === true) {
|
|
||||||
$server->settings->update(['is_metrics_enabled' => false]);
|
|
||||||
}
|
|
||||||
if ($server->isFunctional()) {
|
|
||||||
StopSentinel::dispatch($server);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function pullTemplatesFromCDN()
|
||||||
|
{
|
||||||
|
$response = Http::retry(3, 1000)->get(config('constants.services.official'));
|
||||||
|
if ($response->successful()) {
|
||||||
|
$services = $response->json();
|
||||||
|
File::put(base_path('templates/service-templates.json'), json_encode($services));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function optimize()
|
||||||
|
{
|
||||||
|
echo "[1]: Optimizing Laravel (caching config, routes, views).\n";
|
||||||
|
Artisan::call('optimize:clear');
|
||||||
|
Artisan::call('optimize');
|
||||||
}
|
}
|
||||||
|
|
||||||
private function update_user_emails()
|
private function update_user_emails()
|
||||||
@@ -207,15 +232,15 @@ class Init extends Command
|
|||||||
$settings = instanceSettings();
|
$settings = instanceSettings();
|
||||||
$do_not_track = data_get($settings, 'do_not_track');
|
$do_not_track = data_get($settings, 'do_not_track');
|
||||||
if ($do_not_track == true) {
|
if ($do_not_track == true) {
|
||||||
echo "Skipping alive as do_not_track is enabled\n";
|
echo "[2]: Skipping sending live signal as do_not_track is enabled\n";
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
Http::get("https://undead.coolify.io/v4/alive?appId=$id&version=$version");
|
Http::get("https://undead.coolify.io/v4/alive?appId=$id&version=$version");
|
||||||
echo "I am alive!\n";
|
echo "[2]: Sending live signal!\n";
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
echo "Error in alive: {$e->getMessage()}\n";
|
echo "[2]: Error in sending live signal: {$e->getMessage()}\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,15 @@ class OpenApi extends Command
|
|||||||
{
|
{
|
||||||
// Generate OpenAPI documentation
|
// Generate OpenAPI documentation
|
||||||
echo "Generating OpenAPI documentation.\n";
|
echo "Generating OpenAPI documentation.\n";
|
||||||
$process = Process::run(['/var/www/html/vendor/bin/openapi', 'app', '-o', 'openapi.yaml']);
|
// 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 = $process->errorOutput();
|
||||||
$error = preg_replace('/^.*an object literal,.*$/m', '', $error);
|
$error = preg_replace('/^.*an object literal,.*$/m', '', $error);
|
||||||
$error = preg_replace('/^\h*\v+/m', '', $error);
|
$error = preg_replace('/^\h*\v+/m', '', $error);
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ class Scheduler extends Command
|
|||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
if (config('coolify.is_scheduler_enabled')) {
|
if (config('constants.horizon.is_scheduler_enabled')) {
|
||||||
$this->info('Scheduler is enabled. Starting.');
|
$this->info('[x]: Scheduler is enabled. Starting.');
|
||||||
$this->call('schedule:work');
|
$this->call('schedule:work');
|
||||||
exit(0);
|
exit(0);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ class ServicesDelete extends Command
|
|||||||
if (! $confirmed) {
|
if (! $confirmed) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
DeleteResourceJob::dispatch($toDelete);
|
DeleteResourceJob::dispatch($toDelete)->onQueue('high');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -122,7 +122,7 @@ class ServicesDelete extends Command
|
|||||||
if (! $confirmed) {
|
if (! $confirmed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
DeleteResourceJob::dispatch($toDelete);
|
DeleteResourceJob::dispatch($toDelete)->onQueue('high');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -148,7 +148,7 @@ class ServicesDelete extends Command
|
|||||||
if (! $confirmed) {
|
if (! $confirmed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
DeleteResourceJob::dispatch($toDelete);
|
DeleteResourceJob::dispatch($toDelete)->onQueue('high');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ class SyncBunny extends Command
|
|||||||
|
|
||||||
PendingRequest::macro('storage', function ($fileName) use ($that) {
|
PendingRequest::macro('storage', function ($fileName) use ($that) {
|
||||||
$headers = [
|
$headers = [
|
||||||
'AccessKey' => env('BUNNY_STORAGE_API_KEY'),
|
'AccessKey' => config('constants.bunny.storage_api_key'),
|
||||||
'Accept' => 'application/json',
|
'Accept' => 'application/json',
|
||||||
'Content-Type' => 'application/octet-stream',
|
'Content-Type' => 'application/octet-stream',
|
||||||
];
|
];
|
||||||
@@ -69,7 +69,7 @@ class SyncBunny extends Command
|
|||||||
});
|
});
|
||||||
PendingRequest::macro('purge', function ($url) use ($that) {
|
PendingRequest::macro('purge', function ($url) use ($that) {
|
||||||
$headers = [
|
$headers = [
|
||||||
'AccessKey' => env('BUNNY_API_KEY'),
|
'AccessKey' => config('constants.bunny.api_key'),
|
||||||
'Accept' => 'application/json',
|
'Accept' => 'application/json',
|
||||||
];
|
];
|
||||||
$that->info('Purging: '.$url);
|
$that->info('Purging: '.$url);
|
||||||
|
|||||||
@@ -28,85 +28,100 @@ class Kernel extends ConsoleKernel
|
|||||||
{
|
{
|
||||||
private $allServers;
|
private $allServers;
|
||||||
|
|
||||||
|
private Schedule $scheduleInstance;
|
||||||
|
|
||||||
private InstanceSettings $settings;
|
private InstanceSettings $settings;
|
||||||
|
|
||||||
|
private string $updateCheckFrequency;
|
||||||
|
|
||||||
|
private string $instanceTimezone;
|
||||||
|
|
||||||
protected function schedule(Schedule $schedule): void
|
protected function schedule(Schedule $schedule): void
|
||||||
{
|
{
|
||||||
|
$this->scheduleInstance = $schedule;
|
||||||
$this->allServers = Server::where('ip', '!=', '1.2.3.4');
|
$this->allServers = Server::where('ip', '!=', '1.2.3.4');
|
||||||
|
|
||||||
$this->settings = instanceSettings();
|
$this->settings = instanceSettings();
|
||||||
|
$this->updateCheckFrequency = $this->settings->update_check_frequency ?: '0 * * * *';
|
||||||
|
|
||||||
$schedule->job(new CleanupStaleMultiplexedConnections)->hourly();
|
$this->instanceTimezone = $this->settings->instance_timezone ?: config('app.timezone');
|
||||||
|
|
||||||
|
if (validate_timezone($this->instanceTimezone) === false) {
|
||||||
|
$this->instanceTimezone = config('app.timezone');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->scheduleInstance->job(new CleanupStaleMultiplexedConnections)->hourly();
|
||||||
|
|
||||||
if (isDev()) {
|
if (isDev()) {
|
||||||
// Instance Jobs
|
// Instance Jobs
|
||||||
$schedule->command('horizon:snapshot')->everyMinute();
|
$this->scheduleInstance->command('horizon:snapshot')->everyMinute();
|
||||||
$schedule->job(new CleanupInstanceStuffsJob)->everyMinute()->onOneServer();
|
$this->scheduleInstance->job(new CleanupInstanceStuffsJob)->everyMinute()->onOneServer();
|
||||||
$schedule->job(new CheckHelperImageJob)->everyFiveMinutes()->onOneServer();
|
$this->scheduleInstance->job(new CheckHelperImageJob)->everyTenMinutes()->onOneServer();
|
||||||
|
|
||||||
// Server Jobs
|
// Server Jobs
|
||||||
$this->checkResources($schedule);
|
$this->checkResources();
|
||||||
|
|
||||||
$this->checkScheduledBackups($schedule);
|
$this->checkScheduledBackups();
|
||||||
$this->checkScheduledTasks($schedule);
|
$this->checkScheduledTasks();
|
||||||
|
|
||||||
$schedule->command('uploads:clear')->everyTwoMinutes();
|
$this->scheduleInstance->command('uploads:clear')->everyTwoMinutes();
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Instance Jobs
|
// Instance Jobs
|
||||||
$schedule->command('horizon:snapshot')->everyFiveMinutes();
|
$this->scheduleInstance->command('horizon:snapshot')->everyFiveMinutes();
|
||||||
$schedule->command('cleanup:unreachable-servers')->daily()->onOneServer();
|
$this->scheduleInstance->command('cleanup:unreachable-servers')->daily()->onOneServer();
|
||||||
$schedule->job(new PullTemplatesFromCDN)->cron($this->settings->update_check_frequency)->timezone($this->settings->instance_timezone)->onOneServer();
|
|
||||||
$schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer();
|
$this->scheduleInstance->job(new PullTemplatesFromCDN)->cron($this->updateCheckFrequency)->timezone($this->instanceTimezone)->onOneServer();
|
||||||
$this->scheduleUpdates($schedule);
|
|
||||||
|
$this->scheduleInstance->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer();
|
||||||
|
$this->scheduleUpdates();
|
||||||
|
|
||||||
// Server Jobs
|
// Server Jobs
|
||||||
$this->checkResources($schedule);
|
$this->checkResources();
|
||||||
|
|
||||||
$this->pullImages($schedule);
|
$this->pullImages();
|
||||||
|
|
||||||
$this->checkScheduledBackups($schedule);
|
$this->checkScheduledBackups();
|
||||||
$this->checkScheduledTasks($schedule);
|
$this->checkScheduledTasks();
|
||||||
|
|
||||||
$schedule->command('cleanup:database --yes')->daily();
|
$this->scheduleInstance->command('cleanup:database --yes')->daily();
|
||||||
$schedule->command('uploads:clear')->everyTwoMinutes();
|
$this->scheduleInstance->command('uploads:clear')->everyTwoMinutes();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function pullImages($schedule): void
|
private function pullImages(): void
|
||||||
{
|
{
|
||||||
$servers = $this->allServers->whereRelation('settings', 'is_usable', true)->whereRelation('settings', 'is_reachable', true)->get();
|
$servers = $this->allServers->whereRelation('settings', 'is_usable', true)->whereRelation('settings', 'is_reachable', true)->get();
|
||||||
foreach ($servers as $server) {
|
foreach ($servers as $server) {
|
||||||
if ($server->isSentinelEnabled()) {
|
if ($server->isSentinelEnabled()) {
|
||||||
$schedule->job(function () use ($server) {
|
$this->scheduleInstance->job(function () use ($server) {
|
||||||
CheckAndStartSentinelJob::dispatch($server);
|
CheckAndStartSentinelJob::dispatch($server);
|
||||||
})->cron($this->settings->update_check_frequency)->timezone($this->settings->instance_timezone)->onOneServer();
|
})->cron($this->updateCheckFrequency)->timezone($this->instanceTimezone)->onOneServer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$schedule->job(new CheckHelperImageJob)
|
$this->scheduleInstance->job(new CheckHelperImageJob)
|
||||||
->cron($this->settings->update_check_frequency)
|
->cron($this->updateCheckFrequency)
|
||||||
->timezone($this->settings->instance_timezone)
|
->timezone($this->instanceTimezone)
|
||||||
->onOneServer();
|
->onOneServer();
|
||||||
}
|
}
|
||||||
|
|
||||||
private function scheduleUpdates($schedule): void
|
private function scheduleUpdates(): void
|
||||||
{
|
{
|
||||||
$updateCheckFrequency = $this->settings->update_check_frequency;
|
$this->scheduleInstance->job(new CheckForUpdatesJob)
|
||||||
$schedule->job(new CheckForUpdatesJob)
|
->cron($this->updateCheckFrequency)
|
||||||
->cron($updateCheckFrequency)
|
->timezone($this->instanceTimezone)
|
||||||
->timezone($this->settings->instance_timezone)
|
|
||||||
->onOneServer();
|
->onOneServer();
|
||||||
|
|
||||||
if ($this->settings->is_auto_update_enabled) {
|
if ($this->settings->is_auto_update_enabled) {
|
||||||
$autoUpdateFrequency = $this->settings->auto_update_frequency;
|
$autoUpdateFrequency = $this->settings->auto_update_frequency;
|
||||||
$schedule->job(new UpdateCoolifyJob)
|
$this->scheduleInstance->job(new UpdateCoolifyJob)
|
||||||
->cron($autoUpdateFrequency)
|
->cron($autoUpdateFrequency)
|
||||||
->timezone($this->settings->instance_timezone)
|
->timezone($this->instanceTimezone)
|
||||||
->onOneServer();
|
->onOneServer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function checkResources($schedule): void
|
private function checkResources(): void
|
||||||
{
|
{
|
||||||
if (isCloud()) {
|
if (isCloud()) {
|
||||||
$servers = $this->allServers->whereHas('team.subscription')->get();
|
$servers = $this->allServers->whereHas('team.subscription')->get();
|
||||||
@@ -115,7 +130,6 @@ class Kernel extends ConsoleKernel
|
|||||||
} else {
|
} else {
|
||||||
$servers = $this->allServers->get();
|
$servers = $this->allServers->get();
|
||||||
}
|
}
|
||||||
// $schedule->job(new \App\Jobs\ResourcesCheck)->everyMinute()->onOneServer();
|
|
||||||
|
|
||||||
foreach ($servers as $server) {
|
foreach ($servers as $server) {
|
||||||
$serverTimezone = $server->settings->server_timezone;
|
$serverTimezone = $server->settings->server_timezone;
|
||||||
@@ -124,31 +138,34 @@ class Kernel extends ConsoleKernel
|
|||||||
$lastSentinelUpdate = $server->sentinel_updated_at;
|
$lastSentinelUpdate = $server->sentinel_updated_at;
|
||||||
if (Carbon::parse($lastSentinelUpdate)->isBefore(now()->subSeconds($server->waitBeforeDoingSshCheck()))) {
|
if (Carbon::parse($lastSentinelUpdate)->isBefore(now()->subSeconds($server->waitBeforeDoingSshCheck()))) {
|
||||||
// Check container status every minute if Sentinel does not activated
|
// Check container status every minute if Sentinel does not activated
|
||||||
$schedule->job(new ServerCheckJob($server))->everyMinute()->onOneServer();
|
if (validate_timezone($serverTimezone) === false) {
|
||||||
// $schedule->job(new \App\Jobs\ServerCheckNewJob($server))->everyMinute()->onOneServer();
|
$serverTimezone = config('app.timezone');
|
||||||
|
}
|
||||||
|
$this->scheduleInstance->job(new ServerCheckJob($server))->timezone($serverTimezone)->everyMinute()->onOneServer();
|
||||||
|
// $this->scheduleInstance->job(new \App\Jobs\ServerCheckNewJob($server))->everyMinute()->onOneServer();
|
||||||
|
|
||||||
// Check storage usage every 10 minutes if Sentinel does not activated
|
// Check storage usage every 10 minutes if Sentinel does not activated
|
||||||
$schedule->job(new ServerStorageCheckJob($server))->everyTenMinutes()->onOneServer();
|
$this->scheduleInstance->job(new ServerStorageCheckJob($server))->everyTenMinutes()->onOneServer();
|
||||||
}
|
}
|
||||||
if ($server->settings->force_docker_cleanup) {
|
if ($server->settings->force_docker_cleanup) {
|
||||||
$schedule->job(new DockerCleanupJob($server))->cron($server->settings->docker_cleanup_frequency)->timezone($serverTimezone)->onOneServer();
|
$this->scheduleInstance->job(new DockerCleanupJob($server))->cron($server->settings->docker_cleanup_frequency)->timezone($serverTimezone)->onOneServer();
|
||||||
} else {
|
} else {
|
||||||
$schedule->job(new DockerCleanupJob($server))->everyTenMinutes()->timezone($serverTimezone)->onOneServer();
|
$this->scheduleInstance->job(new DockerCleanupJob($server))->everyTenMinutes()->timezone($serverTimezone)->onOneServer();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cleanup multiplexed connections every hour
|
// Cleanup multiplexed connections every hour
|
||||||
$schedule->job(new ServerCleanupMux($server))->hourly()->onOneServer();
|
$this->scheduleInstance->job(new ServerCleanupMux($server))->hourly()->onOneServer();
|
||||||
|
|
||||||
// Temporary solution until we have better memory management for Sentinel
|
// Temporary solution until we have better memory management for Sentinel
|
||||||
if ($server->isSentinelEnabled()) {
|
if ($server->isSentinelEnabled()) {
|
||||||
$schedule->job(function () use ($server) {
|
$this->scheduleInstance->job(function () use ($server) {
|
||||||
$server->restartContainer('coolify-sentinel');
|
$server->restartContainer('coolify-sentinel');
|
||||||
})->daily()->onOneServer();
|
})->daily()->onOneServer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function checkScheduledBackups($schedule): void
|
private function checkScheduledBackups(): void
|
||||||
{
|
{
|
||||||
$scheduled_backups = ScheduledDatabaseBackup::where('enabled', true)->get();
|
$scheduled_backups = ScheduledDatabaseBackup::where('enabled', true)->get();
|
||||||
if ($scheduled_backups->isEmpty()) {
|
if ($scheduled_backups->isEmpty()) {
|
||||||
@@ -166,27 +183,23 @@ class Kernel extends ConsoleKernel
|
|||||||
if (is_null($server)) {
|
if (is_null($server)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$serverTimezone = $server->settings->server_timezone;
|
|
||||||
|
|
||||||
if (isset(VALID_CRON_STRINGS[$scheduled_backup->frequency])) {
|
if (isset(VALID_CRON_STRINGS[$scheduled_backup->frequency])) {
|
||||||
$scheduled_backup->frequency = VALID_CRON_STRINGS[$scheduled_backup->frequency];
|
$scheduled_backup->frequency = VALID_CRON_STRINGS[$scheduled_backup->frequency];
|
||||||
}
|
}
|
||||||
$schedule->job(new DatabaseBackupJob(
|
$this->scheduleInstance->job(new DatabaseBackupJob(
|
||||||
backup: $scheduled_backup
|
backup: $scheduled_backup
|
||||||
))->cron($scheduled_backup->frequency)->timezone($serverTimezone)->onOneServer();
|
))->cron($scheduled_backup->frequency)->timezone($this->instanceTimezone)->onOneServer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function checkScheduledTasks($schedule): void
|
private function checkScheduledTasks(): void
|
||||||
{
|
{
|
||||||
$scheduled_tasks = ScheduledTask::all();
|
$scheduled_tasks = ScheduledTask::where('enabled', true)->get();
|
||||||
if ($scheduled_tasks->isEmpty()) {
|
if ($scheduled_tasks->isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
foreach ($scheduled_tasks as $scheduled_task) {
|
foreach ($scheduled_tasks as $scheduled_task) {
|
||||||
if ($scheduled_task->enabled === false) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$service = $scheduled_task->service;
|
$service = $scheduled_task->service;
|
||||||
$application = $scheduled_task->application;
|
$application = $scheduled_task->application;
|
||||||
|
|
||||||
@@ -210,14 +223,13 @@ class Kernel extends ConsoleKernel
|
|||||||
if (! $server) {
|
if (! $server) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$serverTimezone = $server->settings->server_timezone ?: config('app.timezone');
|
|
||||||
|
|
||||||
if (isset(VALID_CRON_STRINGS[$scheduled_task->frequency])) {
|
if (isset(VALID_CRON_STRINGS[$scheduled_task->frequency])) {
|
||||||
$scheduled_task->frequency = VALID_CRON_STRINGS[$scheduled_task->frequency];
|
$scheduled_task->frequency = VALID_CRON_STRINGS[$scheduled_task->frequency];
|
||||||
}
|
}
|
||||||
$schedule->job(new ScheduledTaskJob(
|
$this->scheduleInstance->job(new ScheduledTaskJob(
|
||||||
task: $scheduled_task
|
task: $scheduled_task
|
||||||
))->cron($scheduled_task->frequency)->timezone($serverTimezone)->onOneServer();
|
))->cron($scheduled_task->frequency)->timezone($this->instanceTimezone)->onOneServer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
35
app/Events/DatabaseProxyStopped.php
Normal file
35
app/Events/DatabaseProxyStopped.php
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Events;
|
||||||
|
|
||||||
|
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||||
|
use Illuminate\Broadcasting\PrivateChannel;
|
||||||
|
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||||
|
use Illuminate\Foundation\Events\Dispatchable;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
|
||||||
|
class DatabaseProxyStopped implements ShouldBroadcast
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||||
|
|
||||||
|
public $teamId;
|
||||||
|
|
||||||
|
public function __construct($teamId = null)
|
||||||
|
{
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
$teamId = Auth::user()->currentTeam()->id ?? null;
|
||||||
|
}
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
throw new \Exception('Team id is null');
|
||||||
|
}
|
||||||
|
$this->teamId = $teamId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function broadcastOn(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
new PrivateChannel("team.{$this->teamId}"),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,27 +7,29 @@ use Illuminate\Broadcasting\PrivateChannel;
|
|||||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||||
use Illuminate\Foundation\Events\Dispatchable;
|
use Illuminate\Foundation\Events\Dispatchable;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
|
||||||
class DatabaseStatusChanged implements ShouldBroadcast
|
class DatabaseStatusChanged implements ShouldBroadcast
|
||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||||
|
|
||||||
public ?string $userId = null;
|
public $userId = null;
|
||||||
|
|
||||||
public function __construct($userId = null)
|
public function __construct($userId = null)
|
||||||
{
|
{
|
||||||
if (is_null($userId)) {
|
if (is_null($userId)) {
|
||||||
$userId = auth()->user()->id ?? null;
|
$userId = Auth::id() ?? null;
|
||||||
}
|
}
|
||||||
if (is_null($userId)) {
|
if (is_null($userId)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->userId = $userId;
|
$this->userId = $userId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function broadcastOn(): ?array
|
public function broadcastOn(): ?array
|
||||||
{
|
{
|
||||||
if ($this->userId) {
|
if (! is_null($this->userId)) {
|
||||||
return [
|
return [
|
||||||
new PrivateChannel("user.{$this->userId}"),
|
new PrivateChannel("user.{$this->userId}"),
|
||||||
];
|
];
|
||||||
|
|||||||
34
app/Events/ScheduledTaskDone.php
Normal file
34
app/Events/ScheduledTaskDone.php
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Events;
|
||||||
|
|
||||||
|
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||||
|
use Illuminate\Broadcasting\PrivateChannel;
|
||||||
|
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||||
|
use Illuminate\Foundation\Events\Dispatchable;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
class ScheduledTaskDone implements ShouldBroadcast
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||||
|
|
||||||
|
public $teamId;
|
||||||
|
|
||||||
|
public function __construct($teamId = null)
|
||||||
|
{
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
$teamId = auth()->user()->currentTeam()->id ?? null;
|
||||||
|
}
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
throw new \Exception('Team id is null');
|
||||||
|
}
|
||||||
|
$this->teamId = $teamId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function broadcastOn(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
new PrivateChannel("team.{$this->teamId}"),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ use Illuminate\Broadcasting\PrivateChannel;
|
|||||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||||
use Illuminate\Foundation\Events\Dispatchable;
|
use Illuminate\Foundation\Events\Dispatchable;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
|
||||||
class ServiceStatusChanged implements ShouldBroadcast
|
class ServiceStatusChanged implements ShouldBroadcast
|
||||||
{
|
{
|
||||||
@@ -17,7 +18,7 @@ class ServiceStatusChanged implements ShouldBroadcast
|
|||||||
public function __construct($userId = null)
|
public function __construct($userId = null)
|
||||||
{
|
{
|
||||||
if (is_null($userId)) {
|
if (is_null($userId)) {
|
||||||
$userId = auth()->user()->id ?? null;
|
$userId = Auth::id() ?? null;
|
||||||
}
|
}
|
||||||
if (is_null($userId)) {
|
if (is_null($userId)) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -151,7 +151,7 @@ class SshMultiplexingHelper
|
|||||||
|
|
||||||
private static function isMultiplexingEnabled(): bool
|
private static function isMultiplexingEnabled(): bool
|
||||||
{
|
{
|
||||||
return config('constants.ssh.mux_enabled') && ! config('coolify.is_windows_docker_desktop');
|
return config('constants.ssh.mux_enabled') && ! config('constants.coolify.is_windows_docker_desktop');
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function validateSshKey(string $sshKeyLocation): void
|
private static function validateSshKey(string $sshKeyLocation): void
|
||||||
|
|||||||
@@ -636,7 +636,7 @@ class ApplicationsController extends Controller
|
|||||||
|
|
||||||
private function create_application(Request $request, $type)
|
private function create_application(Request $request, $type)
|
||||||
{
|
{
|
||||||
$allowedFields = ['project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'type', 'name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'private_key_uuid', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'redirect', 'github_app_uuid', 'instant_deploy', 'dockerfile', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'watch_paths', 'use_build_server', 'static_image'];
|
$allowedFields = ['project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'type', 'name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'private_key_uuid', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'redirect', 'github_app_uuid', 'instant_deploy', 'dockerfile', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'watch_paths', 'use_build_server', 'static_image', 'custom_nginx_configuration'];
|
||||||
$teamId = getTeamIdFromToken();
|
$teamId = getTeamIdFromToken();
|
||||||
if (is_null($teamId)) {
|
if (is_null($teamId)) {
|
||||||
return invalidTokenResponse();
|
return invalidTokenResponse();
|
||||||
@@ -676,6 +676,27 @@ class ApplicationsController extends Controller
|
|||||||
$githubAppUuid = $request->github_app_uuid;
|
$githubAppUuid = $request->github_app_uuid;
|
||||||
$useBuildServer = $request->use_build_server;
|
$useBuildServer = $request->use_build_server;
|
||||||
$isStatic = $request->is_static;
|
$isStatic = $request->is_static;
|
||||||
|
$customNginxConfiguration = $request->custom_nginx_configuration;
|
||||||
|
|
||||||
|
if (! is_null($customNginxConfiguration)) {
|
||||||
|
if (! isBase64Encoded($customNginxConfiguration)) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Validation failed.',
|
||||||
|
'errors' => [
|
||||||
|
'custom_nginx_configuration' => 'The custom_nginx_configuration should be base64 encoded.',
|
||||||
|
],
|
||||||
|
], 422);
|
||||||
|
}
|
||||||
|
$customNginxConfiguration = base64_decode($customNginxConfiguration);
|
||||||
|
if (mb_detect_encoding($customNginxConfiguration, 'ASCII', true) === false) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Validation failed.',
|
||||||
|
'errors' => [
|
||||||
|
'custom_nginx_configuration' => 'The custom_nginx_configuration should be base64 encoded.',
|
||||||
|
],
|
||||||
|
], 422);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$project = Project::whereTeamId($teamId)->whereUuid($request->project_uuid)->first();
|
$project = Project::whereTeamId($teamId)->whereUuid($request->project_uuid)->first();
|
||||||
if (! $project) {
|
if (! $project) {
|
||||||
@@ -1203,7 +1224,7 @@ class ApplicationsController extends Controller
|
|||||||
$service->name = "service-$service->uuid";
|
$service->name = "service-$service->uuid";
|
||||||
$service->parse(isNew: true);
|
$service->parse(isNew: true);
|
||||||
if ($instantDeploy) {
|
if ($instantDeploy) {
|
||||||
StartService::dispatch($service);
|
StartService::dispatch($service)->onQueue('high');
|
||||||
}
|
}
|
||||||
|
|
||||||
return response()->json(serializeApiResponse([
|
return response()->json(serializeApiResponse([
|
||||||
@@ -1358,7 +1379,7 @@ class ApplicationsController extends Controller
|
|||||||
deleteVolumes: $request->query->get('delete_volumes', true),
|
deleteVolumes: $request->query->get('delete_volumes', true),
|
||||||
dockerCleanup: $request->query->get('docker_cleanup', true),
|
dockerCleanup: $request->query->get('docker_cleanup', true),
|
||||||
deleteConnectedNetworks: $request->query->get('delete_connected_networks', true)
|
deleteConnectedNetworks: $request->query->get('delete_connected_networks', true)
|
||||||
);
|
)->onQueue('high');
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'message' => 'Application deletion request queued.',
|
'message' => 'Application deletion request queued.',
|
||||||
@@ -1500,7 +1521,7 @@ class ApplicationsController extends Controller
|
|||||||
], 404);
|
], 404);
|
||||||
}
|
}
|
||||||
$server = $application->destination->server;
|
$server = $application->destination->server;
|
||||||
$allowedFields = ['name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'static_image', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'watch_paths', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'redirect', 'instant_deploy', 'use_build_server'];
|
$allowedFields = ['name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'static_image', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'watch_paths', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'redirect', 'instant_deploy', 'use_build_server', 'custom_nginx_configuration'];
|
||||||
|
|
||||||
$validationRules = [
|
$validationRules = [
|
||||||
'name' => 'string|max:255',
|
'name' => 'string|max:255',
|
||||||
@@ -1512,6 +1533,7 @@ class ApplicationsController extends Controller
|
|||||||
'docker_compose_domains' => 'array|nullable',
|
'docker_compose_domains' => 'array|nullable',
|
||||||
'docker_compose_custom_start_command' => 'string|nullable',
|
'docker_compose_custom_start_command' => 'string|nullable',
|
||||||
'docker_compose_custom_build_command' => 'string|nullable',
|
'docker_compose_custom_build_command' => 'string|nullable',
|
||||||
|
'custom_nginx_configuration' => 'string|nullable',
|
||||||
];
|
];
|
||||||
$validationRules = array_merge($validationRules, sharedDataApplications());
|
$validationRules = array_merge($validationRules, sharedDataApplications());
|
||||||
$validator = customApiValidator($request->all(), $validationRules);
|
$validator = customApiValidator($request->all(), $validationRules);
|
||||||
@@ -1530,6 +1552,25 @@ class ApplicationsController extends Controller
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if ($request->has('custom_nginx_configuration')) {
|
||||||
|
if (! isBase64Encoded($request->custom_nginx_configuration)) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Validation failed.',
|
||||||
|
'errors' => [
|
||||||
|
'custom_nginx_configuration' => 'The custom_nginx_configuration should be base64 encoded.',
|
||||||
|
],
|
||||||
|
], 422);
|
||||||
|
}
|
||||||
|
$customNginxConfiguration = base64_decode($request->custom_nginx_configuration);
|
||||||
|
if (mb_detect_encoding($customNginxConfiguration, 'ASCII', true) === false) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Validation failed.',
|
||||||
|
'errors' => [
|
||||||
|
'custom_nginx_configuration' => 'The custom_nginx_configuration should be base64 encoded.',
|
||||||
|
],
|
||||||
|
], 422);
|
||||||
|
}
|
||||||
|
}
|
||||||
$return = $this->validateDataApplications($request, $server);
|
$return = $this->validateDataApplications($request, $server);
|
||||||
if ($return instanceof \Illuminate\Http\JsonResponse) {
|
if ($return instanceof \Illuminate\Http\JsonResponse) {
|
||||||
return $return;
|
return $return;
|
||||||
@@ -2482,7 +2523,7 @@ class ApplicationsController extends Controller
|
|||||||
if (! $application) {
|
if (! $application) {
|
||||||
return response()->json(['message' => 'Application not found.'], 404);
|
return response()->json(['message' => 'Application not found.'], 404);
|
||||||
}
|
}
|
||||||
StopApplication::dispatch($application);
|
StopApplication::dispatch($application)->onQueue('high');
|
||||||
|
|
||||||
return response()->json(
|
return response()->json(
|
||||||
[
|
[
|
||||||
|
|||||||
@@ -497,9 +497,9 @@ class DatabasesController extends Controller
|
|||||||
$database->update($request->all());
|
$database->update($request->all());
|
||||||
|
|
||||||
if ($whatToDoWithDatabaseProxy === 'start') {
|
if ($whatToDoWithDatabaseProxy === 'start') {
|
||||||
StartDatabaseProxy::dispatch($database);
|
StartDatabaseProxy::dispatch($database)->onQueue('high');
|
||||||
} elseif ($whatToDoWithDatabaseProxy === 'stop') {
|
} elseif ($whatToDoWithDatabaseProxy === 'stop') {
|
||||||
StopDatabaseProxy::dispatch($database);
|
StopDatabaseProxy::dispatch($database)->onQueue('high');
|
||||||
}
|
}
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
@@ -1151,7 +1151,7 @@ class DatabasesController extends Controller
|
|||||||
}
|
}
|
||||||
$database = create_standalone_postgresql($environment->id, $destination->uuid, $request->all());
|
$database = create_standalone_postgresql($environment->id, $destination->uuid, $request->all());
|
||||||
if ($instantDeploy) {
|
if ($instantDeploy) {
|
||||||
StartDatabase::dispatch($database);
|
StartDatabase::dispatch($database)->onQueue('high');
|
||||||
}
|
}
|
||||||
$database->refresh();
|
$database->refresh();
|
||||||
$payload = [
|
$payload = [
|
||||||
@@ -1206,7 +1206,7 @@ class DatabasesController extends Controller
|
|||||||
}
|
}
|
||||||
$database = create_standalone_mariadb($environment->id, $destination->uuid, $request->all());
|
$database = create_standalone_mariadb($environment->id, $destination->uuid, $request->all());
|
||||||
if ($instantDeploy) {
|
if ($instantDeploy) {
|
||||||
StartDatabase::dispatch($database);
|
StartDatabase::dispatch($database)->onQueue('high');
|
||||||
}
|
}
|
||||||
|
|
||||||
$database->refresh();
|
$database->refresh();
|
||||||
@@ -1264,7 +1264,7 @@ class DatabasesController extends Controller
|
|||||||
}
|
}
|
||||||
$database = create_standalone_mysql($environment->id, $destination->uuid, $request->all());
|
$database = create_standalone_mysql($environment->id, $destination->uuid, $request->all());
|
||||||
if ($instantDeploy) {
|
if ($instantDeploy) {
|
||||||
StartDatabase::dispatch($database);
|
StartDatabase::dispatch($database)->onQueue('high');
|
||||||
}
|
}
|
||||||
|
|
||||||
$database->refresh();
|
$database->refresh();
|
||||||
@@ -1320,7 +1320,7 @@ class DatabasesController extends Controller
|
|||||||
}
|
}
|
||||||
$database = create_standalone_redis($environment->id, $destination->uuid, $request->all());
|
$database = create_standalone_redis($environment->id, $destination->uuid, $request->all());
|
||||||
if ($instantDeploy) {
|
if ($instantDeploy) {
|
||||||
StartDatabase::dispatch($database);
|
StartDatabase::dispatch($database)->onQueue('high');
|
||||||
}
|
}
|
||||||
|
|
||||||
$database->refresh();
|
$database->refresh();
|
||||||
@@ -1357,7 +1357,7 @@ class DatabasesController extends Controller
|
|||||||
removeUnnecessaryFieldsFromRequest($request);
|
removeUnnecessaryFieldsFromRequest($request);
|
||||||
$database = create_standalone_dragonfly($environment->id, $destination->uuid, $request->all());
|
$database = create_standalone_dragonfly($environment->id, $destination->uuid, $request->all());
|
||||||
if ($instantDeploy) {
|
if ($instantDeploy) {
|
||||||
StartDatabase::dispatch($database);
|
StartDatabase::dispatch($database)->onQueue('high');
|
||||||
}
|
}
|
||||||
|
|
||||||
return response()->json(serializeApiResponse([
|
return response()->json(serializeApiResponse([
|
||||||
@@ -1406,7 +1406,7 @@ class DatabasesController extends Controller
|
|||||||
}
|
}
|
||||||
$database = create_standalone_keydb($environment->id, $destination->uuid, $request->all());
|
$database = create_standalone_keydb($environment->id, $destination->uuid, $request->all());
|
||||||
if ($instantDeploy) {
|
if ($instantDeploy) {
|
||||||
StartDatabase::dispatch($database);
|
StartDatabase::dispatch($database)->onQueue('high');
|
||||||
}
|
}
|
||||||
|
|
||||||
$database->refresh();
|
$database->refresh();
|
||||||
@@ -1442,7 +1442,7 @@ class DatabasesController extends Controller
|
|||||||
removeUnnecessaryFieldsFromRequest($request);
|
removeUnnecessaryFieldsFromRequest($request);
|
||||||
$database = create_standalone_clickhouse($environment->id, $destination->uuid, $request->all());
|
$database = create_standalone_clickhouse($environment->id, $destination->uuid, $request->all());
|
||||||
if ($instantDeploy) {
|
if ($instantDeploy) {
|
||||||
StartDatabase::dispatch($database);
|
StartDatabase::dispatch($database)->onQueue('high');
|
||||||
}
|
}
|
||||||
|
|
||||||
$database->refresh();
|
$database->refresh();
|
||||||
@@ -1500,7 +1500,7 @@ class DatabasesController extends Controller
|
|||||||
}
|
}
|
||||||
$database = create_standalone_mongodb($environment->id, $destination->uuid, $request->all());
|
$database = create_standalone_mongodb($environment->id, $destination->uuid, $request->all());
|
||||||
if ($instantDeploy) {
|
if ($instantDeploy) {
|
||||||
StartDatabase::dispatch($database);
|
StartDatabase::dispatch($database)->onQueue('high');
|
||||||
}
|
}
|
||||||
|
|
||||||
$database->refresh();
|
$database->refresh();
|
||||||
@@ -1593,7 +1593,7 @@ class DatabasesController extends Controller
|
|||||||
deleteVolumes: $request->query->get('delete_volumes', true),
|
deleteVolumes: $request->query->get('delete_volumes', true),
|
||||||
dockerCleanup: $request->query->get('docker_cleanup', true),
|
dockerCleanup: $request->query->get('docker_cleanup', true),
|
||||||
deleteConnectedNetworks: $request->query->get('delete_connected_networks', true)
|
deleteConnectedNetworks: $request->query->get('delete_connected_networks', true)
|
||||||
);
|
)->onQueue('high');
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'message' => 'Database deletion request queued.',
|
'message' => 'Database deletion request queued.',
|
||||||
@@ -1666,7 +1666,7 @@ class DatabasesController extends Controller
|
|||||||
if (str($database->status)->contains('running')) {
|
if (str($database->status)->contains('running')) {
|
||||||
return response()->json(['message' => 'Database is already running.'], 400);
|
return response()->json(['message' => 'Database is already running.'], 400);
|
||||||
}
|
}
|
||||||
StartDatabase::dispatch($database);
|
StartDatabase::dispatch($database)->onQueue('high');
|
||||||
|
|
||||||
return response()->json(
|
return response()->json(
|
||||||
[
|
[
|
||||||
@@ -1742,7 +1742,7 @@ class DatabasesController extends Controller
|
|||||||
if (str($database->status)->contains('stopped') || str($database->status)->contains('exited')) {
|
if (str($database->status)->contains('stopped') || str($database->status)->contains('exited')) {
|
||||||
return response()->json(['message' => 'Database is already stopped.'], 400);
|
return response()->json(['message' => 'Database is already stopped.'], 400);
|
||||||
}
|
}
|
||||||
StopDatabase::dispatch($database);
|
StopDatabase::dispatch($database)->onQueue('high');
|
||||||
|
|
||||||
return response()->json(
|
return response()->json(
|
||||||
[
|
[
|
||||||
@@ -1815,7 +1815,7 @@ class DatabasesController extends Controller
|
|||||||
if (! $database) {
|
if (! $database) {
|
||||||
return response()->json(['message' => 'Database not found.'], 404);
|
return response()->json(['message' => 'Database not found.'], 404);
|
||||||
}
|
}
|
||||||
RestartDatabase::dispatch($database);
|
RestartDatabase::dispatch($database)->onQueue('high');
|
||||||
|
|
||||||
return response()->json(
|
return response()->json(
|
||||||
[
|
[
|
||||||
|
|||||||
@@ -307,7 +307,7 @@ class DeployController extends Controller
|
|||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
// Database resource
|
// Database resource
|
||||||
StartDatabase::dispatch($resource);
|
StartDatabase::dispatch($resource)->onQueue('high');
|
||||||
$resource->update([
|
$resource->update([
|
||||||
'started_at' => now(),
|
'started_at' => now(),
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ class OtherController extends Controller
|
|||||||
public function feedback(Request $request)
|
public function feedback(Request $request)
|
||||||
{
|
{
|
||||||
$content = $request->input('content');
|
$content = $request->input('content');
|
||||||
$webhook_url = config('coolify.feedback_discord_webhook');
|
$webhook_url = config('constants.webhooks.feedback_discord_webhook');
|
||||||
if ($webhook_url) {
|
if ($webhook_url) {
|
||||||
Http::post($webhook_url, [
|
Http::post($webhook_url, [
|
||||||
'content' => $content,
|
'content' => $content,
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ class ProjectController extends Controller
|
|||||||
responses: [
|
responses: [
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
response: 200,
|
response: 200,
|
||||||
description: 'Project details',
|
description: 'Environment details',
|
||||||
content: new OA\JsonContent(ref: '#/components/schemas/Environment')),
|
content: new OA\JsonContent(ref: '#/components/schemas/Environment')),
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
response: 401,
|
response: 401,
|
||||||
@@ -422,7 +422,7 @@ class ProjectController extends Controller
|
|||||||
if (! $project) {
|
if (! $project) {
|
||||||
return response()->json(['message' => 'Project not found.'], 404);
|
return response()->json(['message' => 'Project not found.'], 404);
|
||||||
}
|
}
|
||||||
if ($project->resource_count() > 0) {
|
if (! $project->isEmpty()) {
|
||||||
return response()->json(['message' => 'Project has resources, so it cannot be deleted.'], 400);
|
return response()->json(['message' => 'Project has resources, so it cannot be deleted.'], 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -81,15 +81,8 @@ class SecurityController extends Controller
|
|||||||
new OA\Response(
|
new OA\Response(
|
||||||
response: 200,
|
response: 200,
|
||||||
description: 'Get all private keys.',
|
description: 'Get all private keys.',
|
||||||
content: [
|
content: new OA\JsonContent(ref: '#/components/schemas/PrivateKey')
|
||||||
new OA\MediaType(
|
|
||||||
mediaType: 'application/json',
|
|
||||||
schema: new OA\Schema(
|
|
||||||
type: 'array',
|
|
||||||
items: new OA\Items(ref: '#/components/schemas/PrivateKey')
|
|
||||||
)
|
|
||||||
),
|
),
|
||||||
]),
|
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
response: 401,
|
response: 401,
|
||||||
ref: '#/components/responses/401',
|
ref: '#/components/responses/401',
|
||||||
|
|||||||
@@ -426,6 +426,7 @@ class ServersController extends Controller
|
|||||||
'private_key_uuid' => ['type' => 'string', 'example' => 'og888os', 'description' => 'The UUID of the private key.'],
|
'private_key_uuid' => ['type' => 'string', 'example' => 'og888os', 'description' => 'The UUID of the private key.'],
|
||||||
'is_build_server' => ['type' => 'boolean', 'example' => false, 'description' => 'Is build server.'],
|
'is_build_server' => ['type' => 'boolean', 'example' => false, 'description' => 'Is build server.'],
|
||||||
'instant_validate' => ['type' => 'boolean', 'example' => false, 'description' => 'Instant validate.'],
|
'instant_validate' => ['type' => 'boolean', 'example' => false, 'description' => 'Instant validate.'],
|
||||||
|
'proxy_type' => ['type' => 'string', 'enum' => ['traefik', 'caddy', 'none'], 'example' => 'traefik', 'description' => 'The proxy type.'],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -461,7 +462,7 @@ class ServersController extends Controller
|
|||||||
)]
|
)]
|
||||||
public function create_server(Request $request)
|
public function create_server(Request $request)
|
||||||
{
|
{
|
||||||
$allowedFields = ['name', 'description', 'ip', 'port', 'user', 'private_key_uuid', 'is_build_server', 'instant_validate'];
|
$allowedFields = ['name', 'description', 'ip', 'port', 'user', 'private_key_uuid', 'is_build_server', 'instant_validate', 'proxy_type'];
|
||||||
|
|
||||||
$teamId = getTeamIdFromToken();
|
$teamId = getTeamIdFromToken();
|
||||||
if (is_null($teamId)) {
|
if (is_null($teamId)) {
|
||||||
@@ -481,6 +482,7 @@ class ServersController extends Controller
|
|||||||
'user' => 'string|nullable',
|
'user' => 'string|nullable',
|
||||||
'is_build_server' => 'boolean|nullable',
|
'is_build_server' => 'boolean|nullable',
|
||||||
'instant_validate' => 'boolean|nullable',
|
'instant_validate' => 'boolean|nullable',
|
||||||
|
'proxy_type' => 'string|nullable',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
|
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
|
||||||
@@ -512,6 +514,14 @@ class ServersController extends Controller
|
|||||||
if (is_null($request->instant_validate)) {
|
if (is_null($request->instant_validate)) {
|
||||||
$request->offsetSet('instant_validate', false);
|
$request->offsetSet('instant_validate', false);
|
||||||
}
|
}
|
||||||
|
if ($request->proxy_type) {
|
||||||
|
$validProxyTypes = collect(ProxyTypes::cases())->map(function ($proxyType) {
|
||||||
|
return str($proxyType->value)->lower();
|
||||||
|
});
|
||||||
|
if (! $validProxyTypes->contains(str($request->proxy_type)->lower())) {
|
||||||
|
return response()->json(['message' => 'Invalid proxy type.'], 422);
|
||||||
|
}
|
||||||
|
}
|
||||||
$privateKey = PrivateKey::whereTeamId($teamId)->whereUuid($request->private_key_uuid)->first();
|
$privateKey = PrivateKey::whereTeamId($teamId)->whereUuid($request->private_key_uuid)->first();
|
||||||
if (! $privateKey) {
|
if (! $privateKey) {
|
||||||
return response()->json(['message' => 'Private key not found.'], 404);
|
return response()->json(['message' => 'Private key not found.'], 404);
|
||||||
@@ -521,6 +531,8 @@ class ServersController extends Controller
|
|||||||
return response()->json(['message' => 'Server with this IP already exists.'], 400);
|
return response()->json(['message' => 'Server with this IP already exists.'], 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$proxyType = $request->proxy_type ? str($request->proxy_type)->upper() : ProxyTypes::TRAEFIK->value;
|
||||||
|
|
||||||
$server = ModelsServer::create([
|
$server = ModelsServer::create([
|
||||||
'name' => $request->name,
|
'name' => $request->name,
|
||||||
'description' => $request->description,
|
'description' => $request->description,
|
||||||
@@ -530,7 +542,7 @@ class ServersController extends Controller
|
|||||||
'private_key_id' => $privateKey->id,
|
'private_key_id' => $privateKey->id,
|
||||||
'team_id' => $teamId,
|
'team_id' => $teamId,
|
||||||
'proxy' => [
|
'proxy' => [
|
||||||
'type' => ProxyTypes::TRAEFIK->value,
|
'type' => $proxyType,
|
||||||
'status' => ProxyStatus::EXITED->value,
|
'status' => ProxyStatus::EXITED->value,
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
@@ -538,7 +550,7 @@ class ServersController extends Controller
|
|||||||
'is_build_server' => $request->is_build_server,
|
'is_build_server' => $request->is_build_server,
|
||||||
]);
|
]);
|
||||||
if ($request->instant_validate) {
|
if ($request->instant_validate) {
|
||||||
ValidateServer::dispatch($server);
|
ValidateServer::dispatch($server)->onQueue('high');
|
||||||
}
|
}
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
@@ -571,6 +583,7 @@ class ServersController extends Controller
|
|||||||
'private_key_uuid' => ['type' => 'string', 'description' => 'The UUID of the private key.'],
|
'private_key_uuid' => ['type' => 'string', 'description' => 'The UUID of the private key.'],
|
||||||
'is_build_server' => ['type' => 'boolean', 'description' => 'Is build server.'],
|
'is_build_server' => ['type' => 'boolean', 'description' => 'Is build server.'],
|
||||||
'instant_validate' => ['type' => 'boolean', 'description' => 'Instant validate.'],
|
'instant_validate' => ['type' => 'boolean', 'description' => 'Instant validate.'],
|
||||||
|
'proxy_type' => ['type' => 'string', 'enum' => ['traefik', 'caddy', 'none'], 'description' => 'The proxy type.'],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -604,7 +617,7 @@ class ServersController extends Controller
|
|||||||
)]
|
)]
|
||||||
public function update_server(Request $request)
|
public function update_server(Request $request)
|
||||||
{
|
{
|
||||||
$allowedFields = ['name', 'description', 'ip', 'port', 'user', 'private_key_uuid', 'is_build_server', 'instant_validate'];
|
$allowedFields = ['name', 'description', 'ip', 'port', 'user', 'private_key_uuid', 'is_build_server', 'instant_validate', 'proxy_type'];
|
||||||
|
|
||||||
$teamId = getTeamIdFromToken();
|
$teamId = getTeamIdFromToken();
|
||||||
if (is_null($teamId)) {
|
if (is_null($teamId)) {
|
||||||
@@ -624,6 +637,7 @@ class ServersController extends Controller
|
|||||||
'user' => 'string|nullable',
|
'user' => 'string|nullable',
|
||||||
'is_build_server' => 'boolean|nullable',
|
'is_build_server' => 'boolean|nullable',
|
||||||
'instant_validate' => 'boolean|nullable',
|
'instant_validate' => 'boolean|nullable',
|
||||||
|
'proxy_type' => 'string|nullable',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
|
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
|
||||||
@@ -644,6 +658,16 @@ class ServersController extends Controller
|
|||||||
if (! $server) {
|
if (! $server) {
|
||||||
return response()->json(['message' => 'Server not found.'], 404);
|
return response()->json(['message' => 'Server not found.'], 404);
|
||||||
}
|
}
|
||||||
|
if ($request->proxy_type) {
|
||||||
|
$validProxyTypes = collect(ProxyTypes::cases())->map(function ($proxyType) {
|
||||||
|
return str($proxyType->value)->lower();
|
||||||
|
});
|
||||||
|
if ($validProxyTypes->contains(str($request->proxy_type)->lower())) {
|
||||||
|
$server->changeProxy($request->proxy_type, async: true);
|
||||||
|
} else {
|
||||||
|
return response()->json(['message' => 'Invalid proxy type.'], 422);
|
||||||
|
}
|
||||||
|
}
|
||||||
$server->update($request->only(['name', 'description', 'ip', 'port', 'user']));
|
$server->update($request->only(['name', 'description', 'ip', 'port', 'user']));
|
||||||
if ($request->is_build_server) {
|
if ($request->is_build_server) {
|
||||||
$server->settings()->update([
|
$server->settings()->update([
|
||||||
@@ -651,10 +675,12 @@ class ServersController extends Controller
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
if ($request->instant_validate) {
|
if ($request->instant_validate) {
|
||||||
ValidateServer::dispatch($server);
|
ValidateServer::dispatch($server)->onQueue('high');
|
||||||
}
|
}
|
||||||
|
|
||||||
return response()->json(serializeApiResponse($server))->setStatusCode(201);
|
return response()->json([
|
||||||
|
|
||||||
|
])->setStatusCode(201);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[OA\Delete(
|
#[OA\Delete(
|
||||||
@@ -787,7 +813,7 @@ class ServersController extends Controller
|
|||||||
if (! $server) {
|
if (! $server) {
|
||||||
return response()->json(['message' => 'Server not found.'], 404);
|
return response()->json(['message' => 'Server not found.'], 404);
|
||||||
}
|
}
|
||||||
ValidateServer::dispatch($server);
|
ValidateServer::dispatch($server)->onQueue('high');
|
||||||
|
|
||||||
return response()->json(['message' => 'Validation started.']);
|
return response()->json(['message' => 'Validation started.']);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -342,7 +342,7 @@ class ServicesController extends Controller
|
|||||||
}
|
}
|
||||||
$service->parse(isNew: true);
|
$service->parse(isNew: true);
|
||||||
if ($instantDeploy) {
|
if ($instantDeploy) {
|
||||||
StartService::dispatch($service);
|
StartService::dispatch($service)->onQueue('high');
|
||||||
}
|
}
|
||||||
$domains = $service->applications()->get()->pluck('fqdn')->sort();
|
$domains = $service->applications()->get()->pluck('fqdn')->sort();
|
||||||
$domains = $domains->map(function ($domain) {
|
$domains = $domains->map(function ($domain) {
|
||||||
@@ -487,7 +487,7 @@ class ServicesController extends Controller
|
|||||||
deleteVolumes: $request->query->get('delete_volumes', true),
|
deleteVolumes: $request->query->get('delete_volumes', true),
|
||||||
dockerCleanup: $request->query->get('docker_cleanup', true),
|
dockerCleanup: $request->query->get('docker_cleanup', true),
|
||||||
deleteConnectedNetworks: $request->query->get('delete_connected_networks', true)
|
deleteConnectedNetworks: $request->query->get('delete_connected_networks', true)
|
||||||
);
|
)->onQueue('high');
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'message' => 'Service deletion request queued.',
|
'message' => 'Service deletion request queued.',
|
||||||
@@ -1076,7 +1076,7 @@ class ServicesController extends Controller
|
|||||||
if (str($service->status())->contains('running')) {
|
if (str($service->status())->contains('running')) {
|
||||||
return response()->json(['message' => 'Service is already running.'], 400);
|
return response()->json(['message' => 'Service is already running.'], 400);
|
||||||
}
|
}
|
||||||
StartService::dispatch($service);
|
StartService::dispatch($service)->onQueue('high');
|
||||||
|
|
||||||
return response()->json(
|
return response()->json(
|
||||||
[
|
[
|
||||||
@@ -1154,7 +1154,7 @@ class ServicesController extends Controller
|
|||||||
if (str($service->status())->contains('stopped') || str($service->status())->contains('exited')) {
|
if (str($service->status())->contains('stopped') || str($service->status())->contains('exited')) {
|
||||||
return response()->json(['message' => 'Service is already stopped.'], 400);
|
return response()->json(['message' => 'Service is already stopped.'], 400);
|
||||||
}
|
}
|
||||||
StopService::dispatch($service);
|
StopService::dispatch($service)->onQueue('high');
|
||||||
|
|
||||||
return response()->json(
|
return response()->json(
|
||||||
[
|
[
|
||||||
@@ -1229,7 +1229,7 @@ class ServicesController extends Controller
|
|||||||
if (! $service) {
|
if (! $service) {
|
||||||
return response()->json(['message' => 'Service not found.'], 404);
|
return response()->json(['message' => 'Service not found.'], 404);
|
||||||
}
|
}
|
||||||
RestartService::dispatch($service);
|
RestartService::dispatch($service)->onQueue('high');
|
||||||
|
|
||||||
return response()->json(
|
return response()->json(
|
||||||
[
|
[
|
||||||
|
|||||||
@@ -110,13 +110,19 @@ class Controller extends BaseController
|
|||||||
return redirect()->route('login')->with('error', 'Invalid credentials.');
|
return redirect()->route('login')->with('error', 'Invalid credentials.');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function accept_invitation()
|
public function acceptInvitation()
|
||||||
{
|
{
|
||||||
$resetPassword = request()->query('reset-password');
|
$resetPassword = request()->query('reset-password');
|
||||||
$invitationUuid = request()->route('uuid');
|
$invitationUuid = request()->route('uuid');
|
||||||
|
|
||||||
$invitation = TeamInvitation::whereUuid($invitationUuid)->firstOrFail();
|
$invitation = TeamInvitation::whereUuid($invitationUuid)->firstOrFail();
|
||||||
$user = User::whereEmail($invitation->email)->firstOrFail();
|
$user = User::whereEmail($invitation->email)->firstOrFail();
|
||||||
|
|
||||||
|
if (Auth::id() !== $user->id) {
|
||||||
|
abort(400, 'You are not allowed to accept this invitation.');
|
||||||
|
}
|
||||||
$invitationValid = $invitation->isValid();
|
$invitationValid = $invitation->isValid();
|
||||||
|
|
||||||
if ($invitationValid) {
|
if ($invitationValid) {
|
||||||
if ($resetPassword) {
|
if ($resetPassword) {
|
||||||
$user->update([
|
$user->update([
|
||||||
@@ -131,14 +137,12 @@ class Controller extends BaseController
|
|||||||
}
|
}
|
||||||
$user->teams()->attach($invitation->team->id, ['role' => $invitation->role]);
|
$user->teams()->attach($invitation->team->id, ['role' => $invitation->role]);
|
||||||
$invitation->delete();
|
$invitation->delete();
|
||||||
if (auth()->user()?->id !== $user->id) {
|
|
||||||
return redirect()->route('login');
|
|
||||||
}
|
|
||||||
refreshSession($invitation->team);
|
refreshSession($invitation->team);
|
||||||
|
|
||||||
return redirect()->route('team.index');
|
return redirect()->route('team.index');
|
||||||
} else {
|
} else {
|
||||||
abort(401);
|
abort(400, 'Invitation expired.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,10 +150,10 @@ class Controller extends BaseController
|
|||||||
{
|
{
|
||||||
$invitation = TeamInvitation::whereUuid(request()->route('uuid'))->firstOrFail();
|
$invitation = TeamInvitation::whereUuid(request()->route('uuid'))->firstOrFail();
|
||||||
$user = User::whereEmail($invitation->email)->firstOrFail();
|
$user = User::whereEmail($invitation->email)->firstOrFail();
|
||||||
if (is_null(auth()->user())) {
|
if (is_null(Auth::user())) {
|
||||||
return redirect()->route('login');
|
return redirect()->route('login');
|
||||||
}
|
}
|
||||||
if (auth()->user()->id !== $user->id) {
|
if (Auth::id() !== $user->id) {
|
||||||
abort(401);
|
abort(401);
|
||||||
}
|
}
|
||||||
$invitation->delete();
|
$invitation->delete();
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ class Gitlab extends Controller
|
|||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$return_payloads = collect([]);
|
$return_payloads = collect([]);
|
||||||
$payload = $request->collect();
|
$payload = $request->collect();
|
||||||
$headers = $request->headers->all();
|
$headers = $request->headers->all();
|
||||||
@@ -48,6 +49,15 @@ class Gitlab extends Controller
|
|||||||
return response($return_payloads);
|
return response($return_payloads);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (empty($x_gitlab_token)) {
|
||||||
|
$return_payloads->push([
|
||||||
|
'status' => 'failed',
|
||||||
|
'message' => 'Invalid signature.',
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response($return_payloads);
|
||||||
|
}
|
||||||
|
|
||||||
if ($x_gitlab_event === 'push') {
|
if ($x_gitlab_event === 'push') {
|
||||||
$branch = data_get($payload, 'ref');
|
$branch = data_get($payload, 'ref');
|
||||||
$full_name = data_get($payload, 'project.path_with_namespace');
|
$full_name = data_get($payload, 'project.path_with_namespace');
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ namespace App\Http\Controllers\Webhook;
|
|||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Jobs\ServerLimitCheckJob;
|
use App\Jobs\ServerLimitCheckJob;
|
||||||
use App\Jobs\SubscriptionInvoiceFailedJob;
|
use App\Jobs\SubscriptionInvoiceFailedJob;
|
||||||
use App\Jobs\SubscriptionTrialEndedJob;
|
|
||||||
use App\Jobs\SubscriptionTrialEndsSoonJob;
|
|
||||||
use App\Models\Subscription;
|
use App\Models\Subscription;
|
||||||
use App\Models\Team;
|
use App\Models\Team;
|
||||||
use App\Models\Webhook;
|
use App\Models\Webhook;
|
||||||
@@ -260,42 +258,7 @@ class Stripe extends Controller
|
|||||||
$customerId = data_get($data, 'customer');
|
$customerId = data_get($data, 'customer');
|
||||||
$subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail();
|
$subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail();
|
||||||
$team = data_get($subscription, 'team');
|
$team = data_get($subscription, 'team');
|
||||||
if ($team) {
|
$team?->subscriptionEnded();
|
||||||
$team->trialEnded();
|
|
||||||
}
|
|
||||||
$subscription->update([
|
|
||||||
'stripe_subscription_id' => null,
|
|
||||||
'stripe_plan_id' => null,
|
|
||||||
'stripe_cancel_at_period_end' => false,
|
|
||||||
'stripe_invoice_paid' => false,
|
|
||||||
'stripe_trial_already_ended' => false,
|
|
||||||
]);
|
|
||||||
// send_internal_notification('customer.subscription.deleted for customer: '.$customerId);
|
|
||||||
break;
|
|
||||||
case 'customer.subscription.trial_will_end':
|
|
||||||
// Not used for now
|
|
||||||
$customerId = data_get($data, 'customer');
|
|
||||||
$subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail();
|
|
||||||
$team = data_get($subscription, 'team');
|
|
||||||
if (! $team) {
|
|
||||||
return response('No team found for subscription: '.$subscription->id, 400);
|
|
||||||
}
|
|
||||||
SubscriptionTrialEndsSoonJob::dispatch($team);
|
|
||||||
break;
|
|
||||||
case 'customer.subscription.paused':
|
|
||||||
$customerId = data_get($data, 'customer');
|
|
||||||
$subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail();
|
|
||||||
$team = data_get($subscription, 'team');
|
|
||||||
if (! $team) {
|
|
||||||
return response('No team found for subscription: '.$subscription->id, 400);
|
|
||||||
}
|
|
||||||
$team->trialEnded();
|
|
||||||
$subscription->update([
|
|
||||||
'stripe_trial_already_ended' => true,
|
|
||||||
'stripe_invoice_paid' => false,
|
|
||||||
]);
|
|
||||||
SubscriptionTrialEndedJob::dispatch($team);
|
|
||||||
// send_internal_notification('Subscription paused for customer: '.$customerId);
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
// Unhandled event type
|
// Unhandled event type
|
||||||
|
|||||||
@@ -225,6 +225,11 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function tags(): array
|
||||||
|
{
|
||||||
|
return ['server:'.gethostname()];
|
||||||
|
}
|
||||||
|
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
$this->application_deployment_queue->update([
|
$this->application_deployment_queue->update([
|
||||||
@@ -1318,7 +1323,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
private function prepare_builder_image()
|
private function prepare_builder_image()
|
||||||
{
|
{
|
||||||
$settings = instanceSettings();
|
$settings = instanceSettings();
|
||||||
$helperImage = config('coolify.helper_image');
|
$helperImage = config('constants.coolify.helper_image');
|
||||||
$helperImage = "{$helperImage}:{$settings->helper_version}";
|
$helperImage = "{$helperImage}:{$settings->helper_version}";
|
||||||
// Get user home directory
|
// Get user home directory
|
||||||
$this->serverUserHomeDir = instant_remote_process(['echo $HOME'], $this->server);
|
$this->serverUserHomeDir = instant_remote_process(['echo $HOME'], $this->server);
|
||||||
@@ -1836,7 +1841,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($this->pull_request_id === 0) {
|
if ($this->pull_request_id === 0) {
|
||||||
$custom_compose = convert_docker_run_to_compose($this->application->custom_docker_run_options);
|
$custom_compose = convertDockerRunToCompose($this->application->custom_docker_run_options);
|
||||||
if ((bool) $this->application->settings->is_consistent_container_name_enabled) {
|
if ((bool) $this->application->settings->is_consistent_container_name_enabled) {
|
||||||
if (! $this->application->settings->custom_internal_name) {
|
if (! $this->application->settings->custom_internal_name) {
|
||||||
$docker_compose['services'][$this->application->uuid] = $docker_compose['services'][$this->container_name];
|
$docker_compose['services'][$this->application->uuid] = $docker_compose['services'][$this->container_name];
|
||||||
@@ -1990,22 +1995,11 @@ COPY . .
|
|||||||
RUN rm -f /usr/share/nginx/html/nginx.conf
|
RUN rm -f /usr/share/nginx/html/nginx.conf
|
||||||
RUN rm -f /usr/share/nginx/html/Dockerfile
|
RUN rm -f /usr/share/nginx/html/Dockerfile
|
||||||
COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||||
$nginx_config = base64_encode('server {
|
if (str($this->application->custom_nginx_configuration)->isNotEmpty()) {
|
||||||
listen 80;
|
$nginx_config = base64_encode($this->application->custom_nginx_configuration);
|
||||||
listen [::]:80;
|
} else {
|
||||||
server_name localhost;
|
$nginx_config = base64_encode(defaultNginxConfiguration());
|
||||||
|
|
||||||
location / {
|
|
||||||
root /usr/share/nginx/html;
|
|
||||||
index index.html;
|
|
||||||
try_files $uri $uri.html $uri/index.html $uri/ /index.html =404;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
error_page 500 502 503 504 /50x.html;
|
|
||||||
location = /50x.html {
|
|
||||||
root /usr/share/nginx/html;
|
|
||||||
}
|
|
||||||
}');
|
|
||||||
} else {
|
} else {
|
||||||
if ($this->application->build_pack === 'nixpacks') {
|
if ($this->application->build_pack === 'nixpacks') {
|
||||||
$this->nixpacks_plan = base64_encode($this->nixpacks_plan);
|
$this->nixpacks_plan = base64_encode($this->nixpacks_plan);
|
||||||
@@ -2068,23 +2062,11 @@ WORKDIR /usr/share/nginx/html/
|
|||||||
LABEL coolify.deploymentId={$this->deployment_uuid}
|
LABEL coolify.deploymentId={$this->deployment_uuid}
|
||||||
COPY --from=$this->build_image_name /app/{$this->application->publish_directory} .
|
COPY --from=$this->build_image_name /app/{$this->application->publish_directory} .
|
||||||
COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||||
|
if (str($this->application->custom_nginx_configuration)->isNotEmpty()) {
|
||||||
$nginx_config = base64_encode('server {
|
$nginx_config = base64_encode($this->application->custom_nginx_configuration);
|
||||||
listen 80;
|
} else {
|
||||||
listen [::]:80;
|
$nginx_config = base64_encode(defaultNginxConfiguration());
|
||||||
server_name localhost;
|
|
||||||
|
|
||||||
location / {
|
|
||||||
root /usr/share/nginx/html;
|
|
||||||
index index.html;
|
|
||||||
try_files $uri $uri.html $uri/index.html $uri/ /index.html =404;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
error_page 500 502 503 504 /50x.html;
|
|
||||||
location = /50x.html {
|
|
||||||
root /usr/share/nginx/html;
|
|
||||||
}
|
|
||||||
}');
|
|
||||||
}
|
}
|
||||||
$build_command = "docker build {$this->addHosts} --network host -f {$this->workdir}/Dockerfile {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}";
|
$build_command = "docker build {$this->addHosts} --network host -f {$this->workdir}/Dockerfile {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}";
|
||||||
$base64_build_command = base64_encode($build_command);
|
$base64_build_command = base64_encode($build_command);
|
||||||
|
|||||||
@@ -3,14 +3,15 @@
|
|||||||
namespace App\Jobs;
|
namespace App\Jobs;
|
||||||
|
|
||||||
use App\Models\TeamInvitation;
|
use App\Models\TeamInvitation;
|
||||||
use App\Models\Waitlist;
|
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||||
use Illuminate\Contracts\Queue\ShouldBeUnique;
|
use Illuminate\Contracts\Queue\ShouldBeUnique;
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
class CleanupInstanceStuffsJob implements ShouldBeEncrypted, ShouldBeUnique, ShouldQueue
|
class CleanupInstanceStuffsJob implements ShouldBeEncrypted, ShouldBeUnique, ShouldQueue
|
||||||
{
|
{
|
||||||
@@ -18,34 +19,21 @@ class CleanupInstanceStuffsJob implements ShouldBeEncrypted, ShouldBeUnique, Sho
|
|||||||
|
|
||||||
public function __construct() {}
|
public function __construct() {}
|
||||||
|
|
||||||
// public function uniqueId(): string
|
public function middleware(): array
|
||||||
// {
|
{
|
||||||
// return $this->container_name;
|
return [(new WithoutOverlapping('cleanup-instance-stuffs'))->dontRelease()];
|
||||||
// }
|
}
|
||||||
|
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
// $this->cleanup_waitlist();
|
$this->cleanupInvitationLink();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
send_internal_notification('CleanupInstanceStuffsJob failed with error: '.$e->getMessage());
|
Log::error('CleanupInstanceStuffsJob failed with error: '.$e->getMessage());
|
||||||
}
|
|
||||||
try {
|
|
||||||
$this->cleanup_invitation_link();
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
send_internal_notification('CleanupInstanceStuffsJob failed with error: '.$e->getMessage());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function cleanup_waitlist()
|
private function cleanupInvitationLink()
|
||||||
{
|
|
||||||
$waitlist = Waitlist::whereVerified(false)->where('created_at', '<', now()->subMinutes(config('constants.waitlist.expiration')))->get();
|
|
||||||
foreach ($waitlist as $item) {
|
|
||||||
$item->delete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function cleanup_invitation_link()
|
|
||||||
{
|
{
|
||||||
$invitation = TeamInvitation::all();
|
$invitation = TeamInvitation::all();
|
||||||
foreach ($invitation as $item) {
|
foreach ($invitation as $item) {
|
||||||
|
|||||||
@@ -524,7 +524,7 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
private function getFullImageName(): string
|
private function getFullImageName(): string
|
||||||
{
|
{
|
||||||
$settings = instanceSettings();
|
$settings = instanceSettings();
|
||||||
$helperImage = config('coolify.helper_image');
|
$helperImage = config('constants.coolify.helper_image');
|
||||||
$latestVersion = $settings->helper_version;
|
$latestVersion = $settings->helper_version;
|
||||||
|
|
||||||
return "{$helperImage}:{$latestVersion}";
|
return "{$helperImage}:{$latestVersion}";
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
|||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
@@ -23,6 +24,11 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
public ?string $usageBefore = null;
|
public ?string $usageBefore = null;
|
||||||
|
|
||||||
|
public function middleware(): array
|
||||||
|
{
|
||||||
|
return [(new WithoutOverlapping($this->server->uuid))->dontRelease()];
|
||||||
|
}
|
||||||
|
|
||||||
public function __construct(public Server $server, public bool $manualCleanup = false) {}
|
public function __construct(public Server $server, public bool $manualCleanup = false) {}
|
||||||
|
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ class PullHelperImageJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
$helperImage = config('coolify.helper_image');
|
$helperImage = config('constants.coolify.helper_image');
|
||||||
$latest_version = instanceSettings()->helper_version;
|
$latest_version = instanceSettings()->helper_version;
|
||||||
instant_remote_process(["docker pull -q {$helperImage}:{$latest_version}"], $this->server, false);
|
instant_remote_process(["docker pull -q {$helperImage}:{$latest_version}"], $this->server, false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -360,7 +360,7 @@ class PushServerUpdateJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
private function checkLogDrainContainer()
|
private function checkLogDrainContainer()
|
||||||
{
|
{
|
||||||
if ($this->server->isLogDrainEnabled() && $this->foundLogDrainContainer === false) {
|
if ($this->server->isLogDrainEnabled() && $this->foundLogDrainContainer === false) {
|
||||||
StartLogDrain::dispatch($this->server);
|
StartLogDrain::dispatch($this->server)->onQueue('high');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Jobs;
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use App\Events\ScheduledTaskDone;
|
||||||
use App\Models\Application;
|
use App\Models\Application;
|
||||||
use App\Models\ScheduledTask;
|
use App\Models\ScheduledTask;
|
||||||
use App\Models\ScheduledTaskExecution;
|
use App\Models\ScheduledTaskExecution;
|
||||||
@@ -19,7 +20,7 @@ class ScheduledTaskJob implements ShouldQueue
|
|||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
public ?Team $team = null;
|
public Team $team;
|
||||||
|
|
||||||
public Server $server;
|
public Server $server;
|
||||||
|
|
||||||
@@ -47,7 +48,7 @@ class ScheduledTaskJob implements ShouldQueue
|
|||||||
} else {
|
} else {
|
||||||
throw new \RuntimeException('ScheduledTaskJob failed: No resource found.');
|
throw new \RuntimeException('ScheduledTaskJob failed: No resource found.');
|
||||||
}
|
}
|
||||||
$this->team = Team::find($task->team_id);
|
$this->team = Team::findOrFail($task->team_id);
|
||||||
$this->server_timezone = $this->getServerTimezone();
|
$this->server_timezone = $this->getServerTimezone();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,6 +126,7 @@ class ScheduledTaskJob implements ShouldQueue
|
|||||||
// send_internal_notification('ScheduledTaskJob failed with: ' . $e->getMessage());
|
// send_internal_notification('ScheduledTaskJob failed with: ' . $e->getMessage());
|
||||||
throw $e;
|
throw $e;
|
||||||
} finally {
|
} finally {
|
||||||
|
ScheduledTaskDone::dispatch($this->team->id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
|||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
class ServerCheckJob implements ShouldBeEncrypted, ShouldQueue
|
class ServerCheckJob implements ShouldBeEncrypted, ShouldQueue
|
||||||
@@ -25,6 +26,11 @@ class ServerCheckJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
public $containers;
|
public $containers;
|
||||||
|
|
||||||
|
public function middleware(): array
|
||||||
|
{
|
||||||
|
return [(new WithoutOverlapping($this->server->uuid))->dontRelease()];
|
||||||
|
}
|
||||||
|
|
||||||
public function __construct(public Server $server) {}
|
public function __construct(public Server $server) {}
|
||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
@@ -88,10 +94,10 @@ class ServerCheckJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
if ($foundLogDrainContainer) {
|
if ($foundLogDrainContainer) {
|
||||||
$status = data_get($foundLogDrainContainer, 'State.Status');
|
$status = data_get($foundLogDrainContainer, 'State.Status');
|
||||||
if ($status !== 'running') {
|
if ($status !== 'running') {
|
||||||
StartLogDrain::dispatch($this->server);
|
StartLogDrain::dispatch($this->server)->onQueue('high');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
StartLogDrain::dispatch($this->server);
|
StartLogDrain::dispatch($this->server)->onQueue('high');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Jobs;
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use App\Actions\Server\ResourcesCheck;
|
||||||
use App\Actions\Server\ServerCheck;
|
use App\Actions\Server\ServerCheck;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
@@ -25,6 +26,7 @@ class ServerCheckNewJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
ServerCheck::run($this->server);
|
ServerCheck::run($this->server);
|
||||||
|
ResourcesCheck::dispatch($this->server);
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e);
|
return handleError($e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,8 +30,7 @@ class ServerLimitCheckJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
try {
|
try {
|
||||||
$servers = $this->team->servers;
|
$servers = $this->team->servers;
|
||||||
$servers_count = $servers->count();
|
$servers_count = $servers->count();
|
||||||
$limit = data_get($this->team->limits, 'serverLimit', 2);
|
$number_of_servers_to_disable = $servers_count - $this->team->limits;
|
||||||
$number_of_servers_to_disable = $servers_count - $limit;
|
|
||||||
if ($number_of_servers_to_disable > 0) {
|
if ($number_of_servers_to_disable > 0) {
|
||||||
$servers = $servers->sortbyDesc('created_at');
|
$servers = $servers->sortbyDesc('created_at');
|
||||||
$servers_to_disable = $servers->take($number_of_servers_to_disable);
|
$servers_to_disable = $servers->take($number_of_servers_to_disable);
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ class ServerStorageCheckJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
return isDev() ? 1 : 3;
|
return isDev() ? 1 : 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function __construct(public Server $server, public ?int $percentage = null) {}
|
public function __construct(public Server $server, public int|string|null $percentage = null) {}
|
||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Jobs;
|
|
||||||
|
|
||||||
use App\Models\Team;
|
|
||||||
use Illuminate\Bus\Queueable;
|
|
||||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
|
||||||
use Illuminate\Notifications\Messages\MailMessage;
|
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
|
||||||
use Illuminate\Queue\SerializesModels;
|
|
||||||
|
|
||||||
class SubscriptionTrialEndedJob implements ShouldBeEncrypted, ShouldQueue
|
|
||||||
{
|
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
|
||||||
|
|
||||||
public function __construct(
|
|
||||||
public Team $team
|
|
||||||
) {}
|
|
||||||
|
|
||||||
public function handle(): void
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$session = getStripeCustomerPortalSession($this->team);
|
|
||||||
$mail = new MailMessage;
|
|
||||||
$mail->subject('Action required: You trial in Coolify Cloud ended.');
|
|
||||||
$mail->view('emails.trial-ended', [
|
|
||||||
'stripeCustomerPortal' => $session->url,
|
|
||||||
]);
|
|
||||||
$this->team->members()->each(function ($member) use ($mail) {
|
|
||||||
if ($member->isAdmin()) {
|
|
||||||
send_user_an_email($mail, $member->email);
|
|
||||||
send_internal_notification('Trial reminder email sent to '.$member->email);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
send_internal_notification('SubscriptionTrialEndsSoonJob failed with: '.$e->getMessage());
|
|
||||||
throw $e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Jobs;
|
|
||||||
|
|
||||||
use App\Models\Team;
|
|
||||||
use Illuminate\Bus\Queueable;
|
|
||||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
|
||||||
use Illuminate\Notifications\Messages\MailMessage;
|
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
|
||||||
use Illuminate\Queue\SerializesModels;
|
|
||||||
|
|
||||||
class SubscriptionTrialEndsSoonJob implements ShouldBeEncrypted, ShouldQueue
|
|
||||||
{
|
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
|
||||||
|
|
||||||
public function __construct(
|
|
||||||
public Team $team
|
|
||||||
) {}
|
|
||||||
|
|
||||||
public function handle(): void
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$session = getStripeCustomerPortalSession($this->team);
|
|
||||||
$mail = new MailMessage;
|
|
||||||
$mail->subject('You trial in Coolify Cloud ends soon.');
|
|
||||||
$mail->view('emails.trial-ends-soon', [
|
|
||||||
'stripeCustomerPortal' => $session->url,
|
|
||||||
]);
|
|
||||||
$this->team->members()->each(function ($member) use ($mail) {
|
|
||||||
if ($member->isAdmin()) {
|
|
||||||
send_user_an_email($mail, $member->email);
|
|
||||||
send_internal_notification('Trial reminder email sent to '.$member->email);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
send_internal_notification('SubscriptionTrialEndsSoonJob failed with: '.$e->getMessage());
|
|
||||||
throw $e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,8 +2,10 @@
|
|||||||
|
|
||||||
namespace App\Livewire\Admin;
|
namespace App\Livewire\Admin;
|
||||||
|
|
||||||
|
use App\Models\Team;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Support\Facades\Cache;
|
use Illuminate\Support\Facades\Cache;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
@@ -23,7 +25,7 @@ class Index extends Component
|
|||||||
return redirect()->route('dashboard');
|
return redirect()->route('dashboard');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auth()->user()->id !== 0) {
|
if (Auth::id() !== 0) {
|
||||||
return redirect()->route('dashboard');
|
return redirect()->route('dashboard');
|
||||||
}
|
}
|
||||||
$this->getSubscribers();
|
$this->getSubscribers();
|
||||||
@@ -41,23 +43,19 @@ class Index extends Component
|
|||||||
|
|
||||||
public function getSubscribers()
|
public function getSubscribers()
|
||||||
{
|
{
|
||||||
$this->inactiveSubscribers = User::whereDoesntHave('teams', function ($query) {
|
$this->inactiveSubscribers = Team::whereRelation('subscription', 'stripe_invoice_paid', false)->count();
|
||||||
$query->whereRelation('subscription', 'stripe_subscription_id', '!=', null);
|
$this->activeSubscribers = Team::whereRelation('subscription', 'stripe_invoice_paid', true)->count();
|
||||||
})->count();
|
|
||||||
$this->activeSubscribers = User::whereHas('teams', function ($query) {
|
|
||||||
$query->whereRelation('subscription', 'stripe_subscription_id', '!=', null);
|
|
||||||
})->count();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function switchUser(int $user_id)
|
public function switchUser(int $user_id)
|
||||||
{
|
{
|
||||||
if (auth()->user()->id !== 0) {
|
if (Auth::id() !== 0) {
|
||||||
return redirect()->route('dashboard');
|
return redirect()->route('dashboard');
|
||||||
}
|
}
|
||||||
$user = User::find($user_id);
|
$user = User::find($user_id);
|
||||||
$team_to_switch_to = $user->teams->first();
|
$team_to_switch_to = $user->teams->first();
|
||||||
Cache::forget("team:{$user->id}");
|
Cache::forget("team:{$user->id}");
|
||||||
auth()->login($user);
|
Auth::login($user);
|
||||||
refreshSession($team_to_switch_to);
|
refreshSession($team_to_switch_to);
|
||||||
|
|
||||||
return redirect(request()->header('Referer'));
|
return redirect(request()->header('Referer'));
|
||||||
|
|||||||
@@ -66,11 +66,15 @@ class Index extends Component
|
|||||||
|
|
||||||
public bool $serverReachable = true;
|
public bool $serverReachable = true;
|
||||||
|
|
||||||
|
public ?string $minDockerVersion = null;
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
if (auth()->user()?->isMember() && auth()->user()->currentTeam()->show_boarding === true) {
|
if (auth()->user()?->isMember() && auth()->user()->currentTeam()->show_boarding === true) {
|
||||||
return redirect()->route('dashboard');
|
return redirect()->route('dashboard');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->minDockerVersion = str(config('constants.docker.minimum_required_version'))->before('.');
|
||||||
$this->privateKeyName = generate_random_name();
|
$this->privateKeyName = generate_random_name();
|
||||||
$this->remoteServerName = generate_random_name();
|
$this->remoteServerName = generate_random_name();
|
||||||
if (isDev()) {
|
if (isDev()) {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use App\Models\Server;
|
|||||||
use App\Models\StandaloneDocker;
|
use App\Models\StandaloneDocker;
|
||||||
use App\Models\SwarmDocker;
|
use App\Models\SwarmDocker;
|
||||||
use Livewire\Attributes\Locked;
|
use Livewire\Attributes\Locked;
|
||||||
use Livewire\Attributes\Rule;
|
use Livewire\Attributes\Validate;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use Visus\Cuid2\Cuid2;
|
use Visus\Cuid2\Cuid2;
|
||||||
|
|
||||||
@@ -18,16 +18,16 @@ class Docker extends Component
|
|||||||
#[Locked]
|
#[Locked]
|
||||||
public Server $selectedServer;
|
public Server $selectedServer;
|
||||||
|
|
||||||
#[Rule(['required', 'string'])]
|
#[Validate(['required', 'string'])]
|
||||||
public string $name;
|
public string $name;
|
||||||
|
|
||||||
#[Rule(['required', 'string'])]
|
#[Validate(['required', 'string'])]
|
||||||
public string $network;
|
public string $network;
|
||||||
|
|
||||||
#[Rule(['required', 'string'])]
|
#[Validate(['required', 'string'])]
|
||||||
public string $serverId;
|
public string $serverId;
|
||||||
|
|
||||||
#[Rule(['required', 'boolean'])]
|
#[Validate(['required', 'boolean'])]
|
||||||
public bool $isSwarm = false;
|
public bool $isSwarm = false;
|
||||||
|
|
||||||
public function mount(?string $server_id = null)
|
public function mount(?string $server_id = null)
|
||||||
@@ -36,8 +36,10 @@ class Docker extends Component
|
|||||||
$this->servers = Server::isUsable()->get();
|
$this->servers = Server::isUsable()->get();
|
||||||
if ($server_id) {
|
if ($server_id) {
|
||||||
$this->selectedServer = $this->servers->find($server_id);
|
$this->selectedServer = $this->servers->find($server_id);
|
||||||
|
$this->serverId = $this->selectedServer->id;
|
||||||
} else {
|
} else {
|
||||||
$this->selectedServer = $this->servers->first();
|
$this->selectedServer = $this->servers->first();
|
||||||
|
$this->serverId = $this->selectedServer->id;
|
||||||
}
|
}
|
||||||
$this->generateName();
|
$this->generateName();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use App\Models\Server;
|
|||||||
use App\Models\StandaloneDocker;
|
use App\Models\StandaloneDocker;
|
||||||
use App\Models\SwarmDocker;
|
use App\Models\SwarmDocker;
|
||||||
use Livewire\Attributes\Locked;
|
use Livewire\Attributes\Locked;
|
||||||
use Livewire\Attributes\Rule;
|
use Livewire\Attributes\Validate;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class Show extends Component
|
class Show extends Component
|
||||||
@@ -14,13 +14,13 @@ class Show extends Component
|
|||||||
#[Locked]
|
#[Locked]
|
||||||
public $destination;
|
public $destination;
|
||||||
|
|
||||||
#[Rule(['string', 'required'])]
|
#[Validate(['string', 'required'])]
|
||||||
public string $name;
|
public string $name;
|
||||||
|
|
||||||
#[Rule(['string', 'required'])]
|
#[Validate(['string', 'required'])]
|
||||||
public string $network;
|
public string $network;
|
||||||
|
|
||||||
#[Rule(['string', 'required'])]
|
#[Validate(['string', 'required'])]
|
||||||
public string $serverIp;
|
public string $serverIp;
|
||||||
|
|
||||||
public function mount(string $destination_uuid)
|
public function mount(string $destination_uuid)
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Livewire\Dev;
|
|
||||||
|
|
||||||
use Livewire\Component;
|
|
||||||
|
|
||||||
class Compose extends Component
|
|
||||||
{
|
|
||||||
public string $compose = '';
|
|
||||||
|
|
||||||
public string $base64 = '';
|
|
||||||
|
|
||||||
public $services;
|
|
||||||
|
|
||||||
public function mount()
|
|
||||||
{
|
|
||||||
$this->services = get_service_templates();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setService(string $selected)
|
|
||||||
{
|
|
||||||
$this->base64 = data_get($this->services, $selected.'.compose');
|
|
||||||
if ($this->base64) {
|
|
||||||
$this->compose = base64_decode($this->base64);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function updatedCompose($value)
|
|
||||||
{
|
|
||||||
$this->base64 = base64_encode($value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function render()
|
|
||||||
{
|
|
||||||
return view('livewire.dev.compose');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -5,17 +5,17 @@ namespace App\Livewire;
|
|||||||
use DanHarrin\LivewireRateLimiting\WithRateLimiting;
|
use DanHarrin\LivewireRateLimiting\WithRateLimiting;
|
||||||
use Illuminate\Notifications\Messages\MailMessage;
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
use Illuminate\Support\Facades\Http;
|
use Illuminate\Support\Facades\Http;
|
||||||
use Livewire\Attributes\Rule;
|
use Livewire\Attributes\Validate;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class Help extends Component
|
class Help extends Component
|
||||||
{
|
{
|
||||||
use WithRateLimiting;
|
use WithRateLimiting;
|
||||||
|
|
||||||
#[Rule(['required', 'min:10', 'max:1000'])]
|
#[Validate(['required', 'min:10', 'max:1000'])]
|
||||||
public string $description;
|
public string $description;
|
||||||
|
|
||||||
#[Rule(['required', 'min:3'])]
|
#[Validate(['required', 'min:3'])]
|
||||||
public string $subject;
|
public string $subject;
|
||||||
|
|
||||||
public function submit()
|
public function submit()
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
namespace App\Livewire;
|
namespace App\Livewire;
|
||||||
|
|
||||||
use App\Models\InstanceSettings;
|
use App\Models\InstanceSettings;
|
||||||
|
use Illuminate\Container\Attributes\Auth as AttributesAuth;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
use Illuminate\Support\Facades\Hash;
|
use Illuminate\Support\Facades\Hash;
|
||||||
@@ -31,7 +32,7 @@ class NavbarDeleteTeam extends Component
|
|||||||
$currentTeam->delete();
|
$currentTeam->delete();
|
||||||
|
|
||||||
$currentTeam->members->each(function ($user) use ($currentTeam) {
|
$currentTeam->members->each(function ($user) use ($currentTeam) {
|
||||||
if ($user->id === auth()->user()->id) {
|
if ($user->id === AttributesAuth::id()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$user->teams()->detach($currentTeam);
|
$user->teams()->detach($currentTeam);
|
||||||
|
|||||||
@@ -4,35 +4,35 @@ namespace App\Livewire\Notifications;
|
|||||||
|
|
||||||
use App\Models\Team;
|
use App\Models\Team;
|
||||||
use App\Notifications\Test;
|
use App\Notifications\Test;
|
||||||
use Livewire\Attributes\Rule;
|
use Livewire\Attributes\Validate;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class Discord extends Component
|
class Discord extends Component
|
||||||
{
|
{
|
||||||
public Team $team;
|
public Team $team;
|
||||||
|
|
||||||
#[Rule(['boolean'])]
|
#[Validate(['boolean'])]
|
||||||
public bool $discordEnabled = false;
|
public bool $discordEnabled = false;
|
||||||
|
|
||||||
#[Rule(['url', 'nullable'])]
|
#[Validate(['url', 'nullable'])]
|
||||||
public ?string $discordWebhookUrl = null;
|
public ?string $discordWebhookUrl = null;
|
||||||
|
|
||||||
#[Rule(['boolean'])]
|
#[Validate(['boolean'])]
|
||||||
public bool $discordNotificationsTest = false;
|
public bool $discordNotificationsTest = false;
|
||||||
|
|
||||||
#[Rule(['boolean'])]
|
#[Validate(['boolean'])]
|
||||||
public bool $discordNotificationsDeployments = false;
|
public bool $discordNotificationsDeployments = false;
|
||||||
|
|
||||||
#[Rule(['boolean'])]
|
#[Validate(['boolean'])]
|
||||||
public bool $discordNotificationsStatusChanges = false;
|
public bool $discordNotificationsStatusChanges = false;
|
||||||
|
|
||||||
#[Rule(['boolean'])]
|
#[Validate(['boolean'])]
|
||||||
public bool $discordNotificationsDatabaseBackups = false;
|
public bool $discordNotificationsDatabaseBackups = false;
|
||||||
|
|
||||||
#[Rule(['boolean'])]
|
#[Validate(['boolean'])]
|
||||||
public bool $discordNotificationsScheduledTasks = false;
|
public bool $discordNotificationsScheduledTasks = false;
|
||||||
|
|
||||||
#[Rule(['boolean'])]
|
#[Validate(['boolean'])]
|
||||||
public bool $discordNotificationsServerDiskUsage = false;
|
public bool $discordNotificationsServerDiskUsage = false;
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
@@ -41,7 +41,7 @@ class Discord extends Component
|
|||||||
$this->team = auth()->user()->currentTeam();
|
$this->team = auth()->user()->currentTeam();
|
||||||
$this->syncData();
|
$this->syncData();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,11 +57,8 @@ class Discord extends Component
|
|||||||
$this->team->discord_notifications_database_backups = $this->discordNotificationsDatabaseBackups;
|
$this->team->discord_notifications_database_backups = $this->discordNotificationsDatabaseBackups;
|
||||||
$this->team->discord_notifications_scheduled_tasks = $this->discordNotificationsScheduledTasks;
|
$this->team->discord_notifications_scheduled_tasks = $this->discordNotificationsScheduledTasks;
|
||||||
$this->team->discord_notifications_server_disk_usage = $this->discordNotificationsServerDiskUsage;
|
$this->team->discord_notifications_server_disk_usage = $this->discordNotificationsServerDiskUsage;
|
||||||
try {
|
$this->team->save();
|
||||||
$this->saveModel();
|
refreshSession();
|
||||||
} catch (\Throwable $e) {
|
|
||||||
return handleError($e, $this);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
$this->discordEnabled = $this->team->discord_enabled;
|
$this->discordEnabled = $this->team->discord_enabled;
|
||||||
$this->discordWebhookUrl = $this->team->discord_webhook_url;
|
$this->discordWebhookUrl = $this->team->discord_webhook_url;
|
||||||
@@ -74,6 +71,22 @@ class Discord extends Component
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function instantSaveDiscordEnabled()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->validate([
|
||||||
|
'discordWebhookUrl' => 'required',
|
||||||
|
], [
|
||||||
|
'discordWebhookUrl.required' => 'Discord Webhook URL is required.',
|
||||||
|
]);
|
||||||
|
$this->saveModel();
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$this->discordEnabled = false;
|
||||||
|
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function instantSave()
|
public function instantSave()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
@@ -96,7 +109,7 @@ class Discord extends Component
|
|||||||
|
|
||||||
public function saveModel()
|
public function saveModel()
|
||||||
{
|
{
|
||||||
$this->team->save();
|
$this->syncData(true);
|
||||||
refreshSession();
|
refreshSession();
|
||||||
$this->dispatch('success', 'Settings saved.');
|
$this->dispatch('success', 'Settings saved.');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,75 +5,133 @@ namespace App\Livewire\Notifications;
|
|||||||
use App\Models\Team;
|
use App\Models\Team;
|
||||||
use App\Notifications\Test;
|
use App\Notifications\Test;
|
||||||
use Illuminate\Support\Facades\RateLimiter;
|
use Illuminate\Support\Facades\RateLimiter;
|
||||||
|
use Livewire\Attributes\Locked;
|
||||||
|
use Livewire\Attributes\Validate;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class Email extends Component
|
class Email extends Component
|
||||||
{
|
{
|
||||||
public Team $team;
|
public Team $team;
|
||||||
|
|
||||||
|
#[Locked]
|
||||||
public string $emails;
|
public string $emails;
|
||||||
|
|
||||||
public bool $sharedEmailEnabled = false;
|
#[Validate(['boolean'])]
|
||||||
|
public bool $smtpEnabled = false;
|
||||||
|
|
||||||
protected $rules = [
|
#[Validate(['boolean'])]
|
||||||
'team.smtp_enabled' => 'nullable|boolean',
|
public bool $useInstanceEmailSettings = false;
|
||||||
'team.smtp_from_address' => 'required|email',
|
|
||||||
'team.smtp_from_name' => 'required',
|
|
||||||
'team.smtp_recipients' => 'nullable',
|
|
||||||
'team.smtp_host' => 'required',
|
|
||||||
'team.smtp_port' => 'required',
|
|
||||||
'team.smtp_encryption' => 'nullable',
|
|
||||||
'team.smtp_username' => 'nullable',
|
|
||||||
'team.smtp_password' => 'nullable',
|
|
||||||
'team.smtp_timeout' => 'nullable',
|
|
||||||
'team.smtp_notifications_test' => 'nullable|boolean',
|
|
||||||
'team.smtp_notifications_deployments' => 'nullable|boolean',
|
|
||||||
'team.smtp_notifications_status_changes' => 'nullable|boolean',
|
|
||||||
'team.smtp_notifications_database_backups' => 'nullable|boolean',
|
|
||||||
'team.smtp_notifications_scheduled_tasks' => 'nullable|boolean',
|
|
||||||
'team.smtp_notifications_server_disk_usage' => 'nullable|boolean',
|
|
||||||
'team.use_instance_email_settings' => 'boolean',
|
|
||||||
'team.resend_enabled' => 'nullable|boolean',
|
|
||||||
'team.resend_api_key' => 'nullable',
|
|
||||||
];
|
|
||||||
|
|
||||||
protected $validationAttributes = [
|
#[Validate(['nullable', 'email'])]
|
||||||
'team.smtp_from_address' => 'From Address',
|
public ?string $smtpFromAddress = null;
|
||||||
'team.smtp_from_name' => 'From Name',
|
|
||||||
'team.smtp_recipients' => 'Recipients',
|
#[Validate(['nullable', 'string'])]
|
||||||
'team.smtp_host' => 'Host',
|
public ?string $smtpFromName = null;
|
||||||
'team.smtp_port' => 'Port',
|
|
||||||
'team.smtp_encryption' => 'Encryption',
|
#[Validate(['nullable', 'string'])]
|
||||||
'team.smtp_username' => 'Username',
|
public ?string $smtpRecipients = null;
|
||||||
'team.smtp_password' => 'Password',
|
|
||||||
'team.smtp_timeout' => 'Timeout',
|
#[Validate(['nullable', 'string'])]
|
||||||
'team.resend_enabled' => 'Resend Enabled',
|
public ?string $smtpHost = null;
|
||||||
'team.resend_api_key' => 'Resend API Key',
|
|
||||||
];
|
#[Validate(['nullable', 'numeric'])]
|
||||||
|
public ?int $smtpPort = null;
|
||||||
|
|
||||||
|
#[Validate(['nullable', 'string'])]
|
||||||
|
public ?string $smtpEncryption = null;
|
||||||
|
|
||||||
|
#[Validate(['nullable', 'string'])]
|
||||||
|
public ?string $smtpUsername = null;
|
||||||
|
|
||||||
|
#[Validate(['nullable', 'string'])]
|
||||||
|
public ?string $smtpPassword = null;
|
||||||
|
|
||||||
|
#[Validate(['nullable', 'numeric'])]
|
||||||
|
public ?int $smtpTimeout = null;
|
||||||
|
|
||||||
|
#[Validate(['boolean'])]
|
||||||
|
public bool $smtpNotificationsTest = false;
|
||||||
|
|
||||||
|
#[Validate(['boolean'])]
|
||||||
|
public bool $smtpNotificationsDeployments = false;
|
||||||
|
|
||||||
|
#[Validate(['boolean'])]
|
||||||
|
public bool $smtpNotificationsStatusChanges = false;
|
||||||
|
|
||||||
|
#[Validate(['boolean'])]
|
||||||
|
public bool $smtpNotificationsDatabaseBackups = false;
|
||||||
|
|
||||||
|
#[Validate(['boolean'])]
|
||||||
|
public bool $smtpNotificationsScheduledTasks = false;
|
||||||
|
|
||||||
|
#[Validate(['boolean'])]
|
||||||
|
public bool $smtpNotificationsServerDiskUsage = false;
|
||||||
|
|
||||||
|
#[Validate(['boolean'])]
|
||||||
|
public bool $resendEnabled;
|
||||||
|
|
||||||
|
#[Validate(['nullable', 'string'])]
|
||||||
|
public ?string $resendApiKey = null;
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
|
||||||
$this->team = auth()->user()->currentTeam();
|
|
||||||
['sharedEmailEnabled' => $this->sharedEmailEnabled] = $this->team->limits;
|
|
||||||
$this->emails = auth()->user()->email;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function submitFromFields()
|
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->resetErrorBag();
|
$this->team = auth()->user()->currentTeam();
|
||||||
$this->validate([
|
$this->emails = auth()->user()->email;
|
||||||
'team.smtp_from_address' => 'required|email',
|
$this->syncData();
|
||||||
'team.smtp_from_name' => 'required',
|
|
||||||
]);
|
|
||||||
$this->team->save();
|
|
||||||
refreshSession();
|
|
||||||
$this->dispatch('success', 'Settings saved.');
|
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function syncData(bool $toModel = false)
|
||||||
|
{
|
||||||
|
if ($toModel) {
|
||||||
|
$this->validate();
|
||||||
|
$this->team->smtp_enabled = $this->smtpEnabled;
|
||||||
|
$this->team->smtp_from_address = $this->smtpFromAddress;
|
||||||
|
$this->team->smtp_from_name = $this->smtpFromName;
|
||||||
|
$this->team->smtp_host = $this->smtpHost;
|
||||||
|
$this->team->smtp_port = $this->smtpPort;
|
||||||
|
$this->team->smtp_encryption = $this->smtpEncryption;
|
||||||
|
$this->team->smtp_username = $this->smtpUsername;
|
||||||
|
$this->team->smtp_password = $this->smtpPassword;
|
||||||
|
$this->team->smtp_timeout = $this->smtpTimeout;
|
||||||
|
$this->team->smtp_recipients = $this->smtpRecipients;
|
||||||
|
$this->team->smtp_notifications_test = $this->smtpNotificationsTest;
|
||||||
|
$this->team->smtp_notifications_deployments = $this->smtpNotificationsDeployments;
|
||||||
|
$this->team->smtp_notifications_status_changes = $this->smtpNotificationsStatusChanges;
|
||||||
|
$this->team->smtp_notifications_database_backups = $this->smtpNotificationsDatabaseBackups;
|
||||||
|
$this->team->smtp_notifications_scheduled_tasks = $this->smtpNotificationsScheduledTasks;
|
||||||
|
$this->team->smtp_notifications_server_disk_usage = $this->smtpNotificationsServerDiskUsage;
|
||||||
|
$this->team->use_instance_email_settings = $this->useInstanceEmailSettings;
|
||||||
|
$this->team->resend_enabled = $this->resendEnabled;
|
||||||
|
$this->team->resend_api_key = $this->resendApiKey;
|
||||||
|
$this->team->save();
|
||||||
|
refreshSession();
|
||||||
|
} else {
|
||||||
|
$this->smtpEnabled = $this->team->smtp_enabled;
|
||||||
|
$this->smtpFromAddress = $this->team->smtp_from_address;
|
||||||
|
$this->smtpFromName = $this->team->smtp_from_name;
|
||||||
|
$this->smtpHost = $this->team->smtp_host;
|
||||||
|
$this->smtpPort = $this->team->smtp_port;
|
||||||
|
$this->smtpEncryption = $this->team->smtp_encryption;
|
||||||
|
$this->smtpUsername = $this->team->smtp_username;
|
||||||
|
$this->smtpPassword = $this->team->smtp_password;
|
||||||
|
$this->smtpTimeout = $this->team->smtp_timeout;
|
||||||
|
$this->smtpRecipients = $this->team->smtp_recipients;
|
||||||
|
$this->smtpNotificationsTest = $this->team->smtp_notifications_test;
|
||||||
|
$this->smtpNotificationsDeployments = $this->team->smtp_notifications_deployments;
|
||||||
|
$this->smtpNotificationsStatusChanges = $this->team->smtp_notifications_status_changes;
|
||||||
|
$this->smtpNotificationsDatabaseBackups = $this->team->smtp_notifications_database_backups;
|
||||||
|
$this->smtpNotificationsScheduledTasks = $this->team->smtp_notifications_scheduled_tasks;
|
||||||
|
$this->smtpNotificationsServerDiskUsage = $this->team->smtp_notifications_server_disk_usage;
|
||||||
|
$this->useInstanceEmailSettings = $this->team->use_instance_email_settings;
|
||||||
|
$this->resendEnabled = $this->team->resend_enabled;
|
||||||
|
$this->resendApiKey = $this->team->resend_api_key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function sendTestNotification()
|
public function sendTestNotification()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
@@ -98,38 +156,45 @@ class Email extends Component
|
|||||||
public function instantSaveInstance()
|
public function instantSaveInstance()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if (! $this->sharedEmailEnabled) {
|
$this->smtpEnabled = false;
|
||||||
throw new \Exception('Not allowed to change settings. Please upgrade your subscription.');
|
$this->resendEnabled = false;
|
||||||
}
|
$this->saveModel();
|
||||||
$this->team->smtp_enabled = false;
|
|
||||||
$this->team->resend_enabled = false;
|
|
||||||
$this->team->save();
|
|
||||||
refreshSession();
|
|
||||||
$this->dispatch('success', 'Settings saved.');
|
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function instantSaveSmtpEnabled()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->validate([
|
||||||
|
'smtpHost' => 'required',
|
||||||
|
'smtpPort' => 'required|numeric',
|
||||||
|
], [
|
||||||
|
'smtpHost.required' => 'SMTP Host is required.',
|
||||||
|
'smtpPort.required' => 'SMTP Port is required.',
|
||||||
|
]);
|
||||||
|
$this->resendEnabled = false;
|
||||||
|
$this->saveModel();
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$this->smtpEnabled = false;
|
||||||
|
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function instantSaveResend()
|
public function instantSaveResend()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->team->smtp_enabled = false;
|
$this->validate([
|
||||||
$this->submitResend();
|
'resendApiKey' => 'required',
|
||||||
|
], [
|
||||||
|
'resendApiKey.required' => 'Resend API Key is required.',
|
||||||
|
]);
|
||||||
|
$this->smtpEnabled = false;
|
||||||
|
$this->saveModel();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->team->smtp_enabled = false;
|
$this->resendEnabled = false;
|
||||||
|
|
||||||
return handleError($e, $this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function instantSave()
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$this->team->resend_enabled = false;
|
|
||||||
$this->submit();
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
$this->team->smtp_enabled = false;
|
|
||||||
|
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
@@ -137,7 +202,7 @@ class Email extends Component
|
|||||||
|
|
||||||
public function saveModel()
|
public function saveModel()
|
||||||
{
|
{
|
||||||
$this->team->save();
|
$this->syncData(true);
|
||||||
refreshSession();
|
refreshSession();
|
||||||
$this->dispatch('success', 'Settings saved.');
|
$this->dispatch('success', 'Settings saved.');
|
||||||
}
|
}
|
||||||
@@ -146,43 +211,8 @@ class Email extends Component
|
|||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->resetErrorBag();
|
$this->resetErrorBag();
|
||||||
if (! $this->team->use_instance_email_settings) {
|
$this->saveModel();
|
||||||
$this->validate([
|
|
||||||
'team.smtp_from_address' => 'required|email',
|
|
||||||
'team.smtp_from_name' => 'required',
|
|
||||||
'team.smtp_host' => 'required',
|
|
||||||
'team.smtp_port' => 'required|numeric',
|
|
||||||
'team.smtp_encryption' => 'nullable',
|
|
||||||
'team.smtp_username' => 'nullable',
|
|
||||||
'team.smtp_password' => 'nullable',
|
|
||||||
'team.smtp_timeout' => 'nullable',
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
$this->team->save();
|
|
||||||
refreshSession();
|
|
||||||
$this->dispatch('success', 'Settings saved.');
|
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->team->smtp_enabled = false;
|
|
||||||
|
|
||||||
return handleError($e, $this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function submitResend()
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$this->resetErrorBag();
|
|
||||||
$this->validate([
|
|
||||||
'team.smtp_from_address' => 'required|email',
|
|
||||||
'team.smtp_from_name' => 'required',
|
|
||||||
'team.resend_api_key' => 'required',
|
|
||||||
]);
|
|
||||||
$this->team->save();
|
|
||||||
refreshSession();
|
|
||||||
$this->dispatch('success', 'Settings saved.');
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
$this->team->resend_enabled = false;
|
|
||||||
|
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -190,35 +220,28 @@ class Email extends Component
|
|||||||
public function copyFromInstanceSettings()
|
public function copyFromInstanceSettings()
|
||||||
{
|
{
|
||||||
$settings = instanceSettings();
|
$settings = instanceSettings();
|
||||||
|
|
||||||
if ($settings->smtp_enabled) {
|
if ($settings->smtp_enabled) {
|
||||||
$team = currentTeam();
|
$this->smtpEnabled = true;
|
||||||
$team->update([
|
$this->smtpFromAddress = $settings->smtp_from_address;
|
||||||
'smtp_enabled' => $settings->smtp_enabled,
|
$this->smtpFromName = $settings->smtp_from_name;
|
||||||
'smtp_from_address' => $settings->smtp_from_address,
|
$this->smtpRecipients = $settings->smtp_recipients;
|
||||||
'smtp_from_name' => $settings->smtp_from_name,
|
$this->smtpHost = $settings->smtp_host;
|
||||||
'smtp_recipients' => $settings->smtp_recipients,
|
$this->smtpPort = $settings->smtp_port;
|
||||||
'smtp_host' => $settings->smtp_host,
|
$this->smtpEncryption = $settings->smtp_encryption;
|
||||||
'smtp_port' => $settings->smtp_port,
|
$this->smtpUsername = $settings->smtp_username;
|
||||||
'smtp_encryption' => $settings->smtp_encryption,
|
$this->smtpPassword = $settings->smtp_password;
|
||||||
'smtp_username' => $settings->smtp_username,
|
$this->smtpTimeout = $settings->smtp_timeout;
|
||||||
'smtp_password' => $settings->smtp_password,
|
$this->resendEnabled = false;
|
||||||
'smtp_timeout' => $settings->smtp_timeout,
|
$this->saveModel();
|
||||||
]);
|
|
||||||
refreshSession();
|
|
||||||
$this->team = $team;
|
|
||||||
$this->dispatch('success', 'Settings saved.');
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ($settings->resend_enabled) {
|
if ($settings->resend_enabled) {
|
||||||
$team = currentTeam();
|
$this->resendEnabled = true;
|
||||||
$team->update([
|
$this->resendApiKey = $settings->resend_api_key;
|
||||||
'resend_enabled' => $settings->resend_enabled,
|
$this->smtpEnabled = false;
|
||||||
'resend_api_key' => $settings->resend_api_key,
|
$this->saveModel();
|
||||||
]);
|
|
||||||
refreshSession();
|
|
||||||
$this->team = $team;
|
|
||||||
$this->dispatch('success', 'Settings saved.');
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,67 +4,157 @@ namespace App\Livewire\Notifications;
|
|||||||
|
|
||||||
use App\Models\Team;
|
use App\Models\Team;
|
||||||
use App\Notifications\Test;
|
use App\Notifications\Test;
|
||||||
|
use Livewire\Attributes\Validate;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class Telegram extends Component
|
class Telegram extends Component
|
||||||
{
|
{
|
||||||
public Team $team;
|
public Team $team;
|
||||||
|
|
||||||
protected $rules = [
|
#[Validate(['boolean'])]
|
||||||
'team.telegram_enabled' => 'nullable|boolean',
|
public bool $telegramEnabled = false;
|
||||||
'team.telegram_token' => 'required|string',
|
|
||||||
'team.telegram_chat_id' => 'required|string',
|
|
||||||
'team.telegram_notifications_test' => 'nullable|boolean',
|
|
||||||
'team.telegram_notifications_deployments' => 'nullable|boolean',
|
|
||||||
'team.telegram_notifications_status_changes' => 'nullable|boolean',
|
|
||||||
'team.telegram_notifications_database_backups' => 'nullable|boolean',
|
|
||||||
'team.telegram_notifications_scheduled_tasks' => 'nullable|boolean',
|
|
||||||
'team.telegram_notifications_test_message_thread_id' => 'nullable|string',
|
|
||||||
'team.telegram_notifications_deployments_message_thread_id' => 'nullable|string',
|
|
||||||
'team.telegram_notifications_status_changes_message_thread_id' => 'nullable|string',
|
|
||||||
'team.telegram_notifications_database_backups_message_thread_id' => 'nullable|string',
|
|
||||||
'team.telegram_notifications_scheduled_tasks_thread_id' => 'nullable|string',
|
|
||||||
'team.telegram_notifications_server_disk_usage' => 'nullable|boolean',
|
|
||||||
];
|
|
||||||
|
|
||||||
protected $validationAttributes = [
|
#[Validate(['nullable', 'string'])]
|
||||||
'team.telegram_token' => 'Token',
|
public ?string $telegramToken = null;
|
||||||
'team.telegram_chat_id' => 'Chat ID',
|
|
||||||
];
|
#[Validate(['nullable', 'string'])]
|
||||||
|
public ?string $telegramChatId = null;
|
||||||
|
|
||||||
|
#[Validate(['boolean'])]
|
||||||
|
public bool $telegramNotificationsTest = false;
|
||||||
|
|
||||||
|
#[Validate(['boolean'])]
|
||||||
|
public bool $telegramNotificationsDeployments = false;
|
||||||
|
|
||||||
|
#[Validate(['boolean'])]
|
||||||
|
public bool $telegramNotificationsStatusChanges = false;
|
||||||
|
|
||||||
|
#[Validate(['boolean'])]
|
||||||
|
public bool $telegramNotificationsDatabaseBackups = false;
|
||||||
|
|
||||||
|
#[Validate(['boolean'])]
|
||||||
|
public bool $telegramNotificationsScheduledTasks = false;
|
||||||
|
|
||||||
|
#[Validate(['nullable', 'string'])]
|
||||||
|
public ?string $telegramNotificationsTestMessageThreadId = null;
|
||||||
|
|
||||||
|
#[Validate(['nullable', 'string'])]
|
||||||
|
public ?string $telegramNotificationsDeploymentsMessageThreadId = null;
|
||||||
|
|
||||||
|
#[Validate(['nullable', 'string'])]
|
||||||
|
public ?string $telegramNotificationsStatusChangesMessageThreadId = null;
|
||||||
|
|
||||||
|
#[Validate(['nullable', 'string'])]
|
||||||
|
public ?string $telegramNotificationsDatabaseBackupsMessageThreadId = null;
|
||||||
|
|
||||||
|
#[Validate(['nullable', 'string'])]
|
||||||
|
public ?string $telegramNotificationsScheduledTasksThreadId = null;
|
||||||
|
|
||||||
|
#[Validate(['boolean'])]
|
||||||
|
public bool $telegramNotificationsServerDiskUsage = false;
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
|
try {
|
||||||
$this->team = auth()->user()->currentTeam();
|
$this->team = auth()->user()->currentTeam();
|
||||||
|
$this->syncData();
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function syncData(bool $toModel = false)
|
||||||
|
{
|
||||||
|
if ($toModel) {
|
||||||
|
$this->validate();
|
||||||
|
$this->team->telegram_enabled = $this->telegramEnabled;
|
||||||
|
$this->team->telegram_token = $this->telegramToken;
|
||||||
|
$this->team->telegram_chat_id = $this->telegramChatId;
|
||||||
|
$this->team->telegram_notifications_test = $this->telegramNotificationsTest;
|
||||||
|
$this->team->telegram_notifications_deployments = $this->telegramNotificationsDeployments;
|
||||||
|
$this->team->telegram_notifications_status_changes = $this->telegramNotificationsStatusChanges;
|
||||||
|
$this->team->telegram_notifications_database_backups = $this->telegramNotificationsDatabaseBackups;
|
||||||
|
$this->team->telegram_notifications_scheduled_tasks = $this->telegramNotificationsScheduledTasks;
|
||||||
|
$this->team->telegram_notifications_test_message_thread_id = $this->telegramNotificationsTestMessageThreadId;
|
||||||
|
$this->team->telegram_notifications_deployments_message_thread_id = $this->telegramNotificationsDeploymentsMessageThreadId;
|
||||||
|
$this->team->telegram_notifications_status_changes_message_thread_id = $this->telegramNotificationsStatusChangesMessageThreadId;
|
||||||
|
$this->team->telegram_notifications_database_backups_message_thread_id = $this->telegramNotificationsDatabaseBackupsMessageThreadId;
|
||||||
|
$this->team->telegram_notifications_scheduled_tasks_thread_id = $this->telegramNotificationsScheduledTasksThreadId;
|
||||||
|
$this->team->telegram_notifications_server_disk_usage = $this->telegramNotificationsServerDiskUsage;
|
||||||
|
$this->team->save();
|
||||||
|
refreshSession();
|
||||||
|
} else {
|
||||||
|
$this->telegramEnabled = $this->team->telegram_enabled;
|
||||||
|
$this->telegramToken = $this->team->telegram_token;
|
||||||
|
$this->telegramChatId = $this->team->telegram_chat_id;
|
||||||
|
$this->telegramNotificationsTest = $this->team->telegram_notifications_test;
|
||||||
|
$this->telegramNotificationsDeployments = $this->team->telegram_notifications_deployments;
|
||||||
|
$this->telegramNotificationsStatusChanges = $this->team->telegram_notifications_status_changes;
|
||||||
|
$this->telegramNotificationsDatabaseBackups = $this->team->telegram_notifications_database_backups;
|
||||||
|
$this->telegramNotificationsScheduledTasks = $this->team->telegram_notifications_scheduled_tasks;
|
||||||
|
$this->telegramNotificationsTestMessageThreadId = $this->team->telegram_notifications_test_message_thread_id;
|
||||||
|
$this->telegramNotificationsDeploymentsMessageThreadId = $this->team->telegram_notifications_deployments_message_thread_id;
|
||||||
|
$this->telegramNotificationsStatusChangesMessageThreadId = $this->team->telegram_notifications_status_changes_message_thread_id;
|
||||||
|
$this->telegramNotificationsDatabaseBackupsMessageThreadId = $this->team->telegram_notifications_database_backups_message_thread_id;
|
||||||
|
$this->telegramNotificationsScheduledTasksThreadId = $this->team->telegram_notifications_scheduled_tasks_thread_id;
|
||||||
|
$this->telegramNotificationsServerDiskUsage = $this->team->telegram_notifications_server_disk_usage;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function instantSave()
|
public function instantSave()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->submit();
|
$this->syncData(true);
|
||||||
} catch (\Throwable) {
|
} catch (\Throwable $e) {
|
||||||
$this->team->telegram_enabled = false;
|
return handleError($e, $this);
|
||||||
$this->validate();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function submit()
|
public function submit()
|
||||||
{
|
{
|
||||||
|
try {
|
||||||
$this->resetErrorBag();
|
$this->resetErrorBag();
|
||||||
$this->validate();
|
$this->syncData(true);
|
||||||
$this->saveModel();
|
$this->saveModel();
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function instantSaveTelegramEnabled()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->validate([
|
||||||
|
'telegramToken' => 'required',
|
||||||
|
'telegramChatId' => 'required',
|
||||||
|
], [
|
||||||
|
'telegramToken.required' => 'Telegram Token is required.',
|
||||||
|
'telegramChatId.required' => 'Telegram Chat ID is required.',
|
||||||
|
]);
|
||||||
|
$this->saveModel();
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$this->telegramEnabled = false;
|
||||||
|
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function saveModel()
|
public function saveModel()
|
||||||
{
|
{
|
||||||
$this->team->save();
|
$this->syncData(true);
|
||||||
refreshSession();
|
refreshSession();
|
||||||
$this->dispatch('success', 'Settings saved.');
|
$this->dispatch('success', 'Settings saved.');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function sendTestNotification()
|
public function sendTestNotification()
|
||||||
{
|
{
|
||||||
$this->team?->notify(new Test);
|
try {
|
||||||
|
$this->team->notify(new Test);
|
||||||
$this->dispatch('success', 'Test notification sent.');
|
$this->dispatch('success', 'Test notification sent.');
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function render()
|
public function render()
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Livewire\Profile;
|
namespace App\Livewire\Profile;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Support\Facades\Hash;
|
use Illuminate\Support\Facades\Hash;
|
||||||
use Illuminate\Validation\Rules\Password;
|
use Illuminate\Validation\Rules\Password;
|
||||||
use Livewire\Attributes\Validate;
|
use Livewire\Attributes\Validate;
|
||||||
@@ -24,9 +25,9 @@ class Index extends Component
|
|||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->userId = auth()->user()->id;
|
$this->userId = Auth::id();
|
||||||
$this->name = auth()->user()->name;
|
$this->name = Auth::user()->name;
|
||||||
$this->email = auth()->user()->email;
|
$this->email = Auth::user()->email;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function submit()
|
public function submit()
|
||||||
@@ -35,7 +36,7 @@ class Index extends Component
|
|||||||
$this->validate([
|
$this->validate([
|
||||||
'name' => 'required',
|
'name' => 'required',
|
||||||
]);
|
]);
|
||||||
auth()->user()->update([
|
Auth::user()->update([
|
||||||
'name' => $this->name,
|
'name' => $this->name,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -3,15 +3,15 @@
|
|||||||
namespace App\Livewire\Project;
|
namespace App\Livewire\Project;
|
||||||
|
|
||||||
use App\Models\Project;
|
use App\Models\Project;
|
||||||
use Livewire\Attributes\Rule;
|
use Livewire\Attributes\Validate;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class AddEmpty extends Component
|
class AddEmpty extends Component
|
||||||
{
|
{
|
||||||
#[Rule(['required', 'string', 'min:3'])]
|
#[Validate(['required', 'string', 'min:3'])]
|
||||||
public string $name;
|
public string $name;
|
||||||
|
|
||||||
#[Rule(['nullable', 'string'])]
|
#[Validate(['nullable', 'string'])]
|
||||||
public string $description = '';
|
public string $description = '';
|
||||||
|
|
||||||
public function submit()
|
public function submit()
|
||||||
|
|||||||
@@ -3,120 +3,200 @@
|
|||||||
namespace App\Livewire\Project\Application;
|
namespace App\Livewire\Project\Application;
|
||||||
|
|
||||||
use App\Models\Application;
|
use App\Models\Application;
|
||||||
|
use Livewire\Attributes\Validate;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class Advanced extends Component
|
class Advanced extends Component
|
||||||
{
|
{
|
||||||
public Application $application;
|
public Application $application;
|
||||||
|
|
||||||
public bool $is_force_https_enabled;
|
#[Validate(['boolean'])]
|
||||||
|
public bool $isForceHttpsEnabled = false;
|
||||||
|
|
||||||
public bool $is_gzip_enabled;
|
#[Validate(['boolean'])]
|
||||||
|
public bool $isGitSubmodulesEnabled = false;
|
||||||
|
|
||||||
public bool $is_stripprefix_enabled;
|
#[Validate(['boolean'])]
|
||||||
|
public bool $isGitLfsEnabled = false;
|
||||||
|
|
||||||
protected $rules = [
|
#[Validate(['boolean'])]
|
||||||
'application.settings.is_git_submodules_enabled' => 'boolean|required',
|
public bool $isPreviewDeploymentsEnabled = false;
|
||||||
'application.settings.is_git_lfs_enabled' => 'boolean|required',
|
|
||||||
'application.settings.is_preview_deployments_enabled' => 'boolean|required',
|
#[Validate(['boolean'])]
|
||||||
'application.settings.is_auto_deploy_enabled' => 'boolean|required',
|
public bool $isAutoDeployEnabled = true;
|
||||||
'is_force_https_enabled' => 'boolean|required',
|
|
||||||
'application.settings.is_log_drain_enabled' => 'boolean|required',
|
#[Validate(['boolean'])]
|
||||||
'application.settings.is_gpu_enabled' => 'boolean|required',
|
public bool $isLogDrainEnabled = false;
|
||||||
'application.settings.is_build_server_enabled' => 'boolean|required',
|
|
||||||
'application.settings.is_consistent_container_name_enabled' => 'boolean|required',
|
#[Validate(['boolean'])]
|
||||||
'application.settings.custom_internal_name' => 'string|nullable',
|
public bool $isGpuEnabled = false;
|
||||||
'application.settings.is_gzip_enabled' => 'boolean|required',
|
|
||||||
'application.settings.is_stripprefix_enabled' => 'boolean|required',
|
#[Validate(['string'])]
|
||||||
'application.settings.gpu_driver' => 'string|required',
|
public string $gpuDriver = '';
|
||||||
'application.settings.gpu_count' => 'string|required',
|
|
||||||
'application.settings.gpu_device_ids' => 'string|required',
|
#[Validate(['string', 'nullable'])]
|
||||||
'application.settings.gpu_options' => 'string|required',
|
public ?string $gpuCount = null;
|
||||||
'application.settings.is_raw_compose_deployment_enabled' => 'boolean|required',
|
|
||||||
'application.settings.connect_to_docker_network' => 'boolean|required',
|
#[Validate(['string', 'nullable'])]
|
||||||
];
|
public ?string $gpuDeviceIds = null;
|
||||||
|
|
||||||
|
#[Validate(['string', 'nullable'])]
|
||||||
|
public ?string $gpuOptions = null;
|
||||||
|
|
||||||
|
#[Validate(['boolean'])]
|
||||||
|
public bool $isBuildServerEnabled = false;
|
||||||
|
|
||||||
|
#[Validate(['boolean'])]
|
||||||
|
public bool $isConsistentContainerNameEnabled = false;
|
||||||
|
|
||||||
|
#[Validate(['string', 'nullable'])]
|
||||||
|
public ?string $customInternalName = null;
|
||||||
|
|
||||||
|
#[Validate(['boolean'])]
|
||||||
|
public bool $isGzipEnabled = true;
|
||||||
|
|
||||||
|
#[Validate(['boolean'])]
|
||||||
|
public bool $isStripprefixEnabled = true;
|
||||||
|
|
||||||
|
#[Validate(['boolean'])]
|
||||||
|
public bool $isRawComposeDeploymentEnabled = false;
|
||||||
|
|
||||||
|
#[Validate(['boolean'])]
|
||||||
|
public bool $isConnectToDockerNetworkEnabled = false;
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->is_force_https_enabled = $this->application->isForceHttpsEnabled();
|
try {
|
||||||
$this->is_gzip_enabled = $this->application->isGzipEnabled();
|
$this->syncData();
|
||||||
$this->is_stripprefix_enabled = $this->application->isStripprefixEnabled();
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function syncData(bool $toModel = false)
|
||||||
|
{
|
||||||
|
if ($toModel) {
|
||||||
|
$this->validate();
|
||||||
|
$this->application->settings->is_force_https_enabled = $this->isForceHttpsEnabled;
|
||||||
|
$this->application->settings->is_git_submodules_enabled = $this->isGitSubmodulesEnabled;
|
||||||
|
$this->application->settings->is_git_lfs_enabled = $this->isGitLfsEnabled;
|
||||||
|
$this->application->settings->is_preview_deployments_enabled = $this->isPreviewDeploymentsEnabled;
|
||||||
|
$this->application->settings->is_auto_deploy_enabled = $this->isAutoDeployEnabled;
|
||||||
|
$this->application->settings->is_log_drain_enabled = $this->isLogDrainEnabled;
|
||||||
|
$this->application->settings->is_gpu_enabled = $this->isGpuEnabled;
|
||||||
|
$this->application->settings->gpu_driver = $this->gpuDriver;
|
||||||
|
$this->application->settings->gpu_count = $this->gpuCount;
|
||||||
|
$this->application->settings->gpu_device_ids = $this->gpuDeviceIds;
|
||||||
|
$this->application->settings->gpu_options = $this->gpuOptions;
|
||||||
|
$this->application->settings->is_build_server_enabled = $this->isBuildServerEnabled;
|
||||||
|
$this->application->settings->is_consistent_container_name_enabled = $this->isConsistentContainerNameEnabled;
|
||||||
|
$this->application->settings->custom_internal_name = $this->customInternalName;
|
||||||
|
$this->application->settings->is_gzip_enabled = $this->isGzipEnabled;
|
||||||
|
$this->application->settings->is_stripprefix_enabled = $this->isStripprefixEnabled;
|
||||||
|
$this->application->settings->is_raw_compose_deployment_enabled = $this->isRawComposeDeploymentEnabled;
|
||||||
|
$this->application->settings->connect_to_docker_network = $this->isConnectToDockerNetworkEnabled;
|
||||||
|
$this->application->settings->save();
|
||||||
|
} else {
|
||||||
|
$this->isForceHttpsEnabled = $this->application->isForceHttpsEnabled();
|
||||||
|
$this->isGzipEnabled = $this->application->isGzipEnabled();
|
||||||
|
$this->isStripprefixEnabled = $this->application->isStripprefixEnabled();
|
||||||
|
$this->isLogDrainEnabled = $this->application->isLogDrainEnabled();
|
||||||
|
|
||||||
|
$this->isGitSubmodulesEnabled = $this->application->settings->is_git_submodules_enabled;
|
||||||
|
$this->isGitLfsEnabled = $this->application->settings->is_git_lfs_enabled;
|
||||||
|
$this->isPreviewDeploymentsEnabled = $this->application->settings->is_preview_deployments_enabled;
|
||||||
|
$this->isAutoDeployEnabled = $this->application->settings->is_auto_deploy_enabled;
|
||||||
|
$this->isGpuEnabled = $this->application->settings->is_gpu_enabled;
|
||||||
|
$this->gpuDriver = $this->application->settings->gpu_driver;
|
||||||
|
$this->gpuCount = $this->application->settings->gpu_count;
|
||||||
|
$this->gpuDeviceIds = $this->application->settings->gpu_device_ids;
|
||||||
|
$this->gpuOptions = $this->application->settings->gpu_options;
|
||||||
|
$this->isBuildServerEnabled = $this->application->settings->is_build_server_enabled;
|
||||||
|
$this->isConsistentContainerNameEnabled = $this->application->settings->is_consistent_container_name_enabled;
|
||||||
|
$this->customInternalName = $this->application->settings->custom_internal_name;
|
||||||
|
$this->isRawComposeDeploymentEnabled = $this->application->settings->is_raw_compose_deployment_enabled;
|
||||||
|
$this->isConnectToDockerNetworkEnabled = $this->application->settings->connect_to_docker_network;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function instantSave()
|
public function instantSave()
|
||||||
{
|
{
|
||||||
if ($this->application->isLogDrainEnabled()) {
|
try {
|
||||||
|
if ($this->isLogDrainEnabled) {
|
||||||
if (! $this->application->destination->server->isLogDrainEnabled()) {
|
if (! $this->application->destination->server->isLogDrainEnabled()) {
|
||||||
$this->application->settings->is_log_drain_enabled = false;
|
$this->isLogDrainEnabled = false;
|
||||||
|
$this->syncData(true);
|
||||||
$this->dispatch('error', 'Log drain is not enabled on this server.');
|
$this->dispatch('error', 'Log drain is not enabled on this server.');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($this->application->settings->is_force_https_enabled !== $this->is_force_https_enabled) {
|
if ($this->application->isForceHttpsEnabled() !== $this->isForceHttpsEnabled ||
|
||||||
$this->application->settings->is_force_https_enabled = $this->is_force_https_enabled;
|
$this->application->isGzipEnabled() !== $this->isGzipEnabled ||
|
||||||
$this->dispatch('resetDefaultLabels', false);
|
$this->application->isStripprefixEnabled() !== $this->isStripprefixEnabled
|
||||||
}
|
) {
|
||||||
if ($this->application->settings->is_gzip_enabled !== $this->is_gzip_enabled) {
|
|
||||||
$this->application->settings->is_gzip_enabled = $this->is_gzip_enabled;
|
|
||||||
$this->dispatch('resetDefaultLabels', false);
|
|
||||||
}
|
|
||||||
if ($this->application->settings->is_stripprefix_enabled !== $this->is_stripprefix_enabled) {
|
|
||||||
$this->application->settings->is_stripprefix_enabled = $this->is_stripprefix_enabled;
|
|
||||||
$this->dispatch('resetDefaultLabels', false);
|
$this->dispatch('resetDefaultLabels', false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->application->settings->is_raw_compose_deployment_enabled) {
|
if ($this->application->settings->is_raw_compose_deployment_enabled) {
|
||||||
$this->application->oldRawParser();
|
$this->application->oldRawParser();
|
||||||
} else {
|
} else {
|
||||||
$this->application->parse();
|
$this->application->parse();
|
||||||
}
|
}
|
||||||
$this->application->settings->save();
|
$this->syncData(true);
|
||||||
$this->dispatch('success', 'Settings saved.');
|
$this->dispatch('success', 'Settings saved.');
|
||||||
$this->dispatch('configurationChanged');
|
$this->dispatch('configurationChanged');
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function submit()
|
public function submit()
|
||||||
{
|
{
|
||||||
if ($this->application->settings->gpu_count && $this->application->settings->gpu_device_ids) {
|
try {
|
||||||
|
if ($this->gpuCount && $this->gpuDeviceIds) {
|
||||||
$this->dispatch('error', 'You cannot set both GPU count and GPU device IDs.');
|
$this->dispatch('error', 'You cannot set both GPU count and GPU device IDs.');
|
||||||
$this->application->settings->gpu_count = null;
|
$this->gpuCount = null;
|
||||||
$this->application->settings->gpu_device_ids = null;
|
$this->gpuDeviceIds = null;
|
||||||
$this->application->settings->save();
|
$this->syncData(true);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$this->application->settings->save();
|
$this->syncData(true);
|
||||||
$this->dispatch('success', 'Settings saved.');
|
$this->dispatch('success', 'Settings saved.');
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function saveCustomName()
|
public function saveCustomName()
|
||||||
{
|
{
|
||||||
if (str($this->application->settings->custom_internal_name)->isNotEmpty()) {
|
if (str($this->customInternalName)->isNotEmpty()) {
|
||||||
$this->application->settings->custom_internal_name = str($this->application->settings->custom_internal_name)->slug()->value();
|
$this->customInternalName = str($this->customInternalName)->slug()->value();
|
||||||
} else {
|
} else {
|
||||||
$this->application->settings->custom_internal_name = null;
|
$this->customInternalName = null;
|
||||||
}
|
}
|
||||||
if (is_null($this->application->settings->custom_internal_name)) {
|
if (is_null($this->customInternalName)) {
|
||||||
$this->application->settings->save();
|
$this->syncData(true);
|
||||||
$this->dispatch('success', 'Custom name saved.');
|
$this->dispatch('success', 'Custom name saved.');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$customInternalName = $this->application->settings->custom_internal_name;
|
$customInternalName = $this->customInternalName;
|
||||||
$server = $this->application->destination->server;
|
$server = $this->application->destination->server;
|
||||||
$allApplications = $server->applications();
|
$allApplications = $server->applications();
|
||||||
|
|
||||||
$foundSameInternalName = $allApplications->filter(function ($application) {
|
$foundSameInternalName = $allApplications->filter(function ($application) {
|
||||||
return $application->id !== $this->application->id && $application->settings->custom_internal_name === $this->application->settings->custom_internal_name;
|
return $application->id !== $this->application->id && $application->settings->custom_internal_name === $this->customInternalName;
|
||||||
});
|
});
|
||||||
if ($foundSameInternalName->isNotEmpty()) {
|
if ($foundSameInternalName->isNotEmpty()) {
|
||||||
$this->dispatch('error', 'This custom container name is already in use by another application on this server.');
|
$this->dispatch('error', 'This custom container name is already in use by another application on this server.');
|
||||||
$this->application->settings->custom_internal_name = $customInternalName;
|
$this->customInternalName = $customInternalName;
|
||||||
$this->application->settings->refresh();
|
$this->syncData(true);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$this->application->settings->save();
|
$this->syncData(true);
|
||||||
$this->dispatch('success', 'Custom name saved.');
|
$this->dispatch('success', 'Custom name saved.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ class General extends Component
|
|||||||
'application.pre_deployment_command_container' => 'nullable',
|
'application.pre_deployment_command_container' => 'nullable',
|
||||||
'application.post_deployment_command' => 'nullable',
|
'application.post_deployment_command' => 'nullable',
|
||||||
'application.post_deployment_command_container' => 'nullable',
|
'application.post_deployment_command_container' => 'nullable',
|
||||||
|
'application.custom_nginx_configuration' => 'nullable',
|
||||||
'application.settings.is_static' => 'boolean|required',
|
'application.settings.is_static' => 'boolean|required',
|
||||||
'application.settings.is_build_server_enabled' => 'boolean|required',
|
'application.settings.is_build_server_enabled' => 'boolean|required',
|
||||||
'application.settings.is_container_label_escape_enabled' => 'boolean|required',
|
'application.settings.is_container_label_escape_enabled' => 'boolean|required',
|
||||||
@@ -121,6 +122,7 @@ class General extends Component
|
|||||||
'application.custom_docker_run_options' => 'Custom docker run commands',
|
'application.custom_docker_run_options' => 'Custom docker run commands',
|
||||||
'application.docker_compose_custom_start_command' => 'Docker compose custom start command',
|
'application.docker_compose_custom_start_command' => 'Docker compose custom start command',
|
||||||
'application.docker_compose_custom_build_command' => 'Docker compose custom build command',
|
'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_static' => 'Is static',
|
||||||
'application.settings.is_build_server_enabled' => 'Is build server enabled',
|
'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_escape_enabled' => 'Is container label escape enabled',
|
||||||
@@ -241,6 +243,13 @@ class General extends Component
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function updatedApplicationSettingsIsStatic($value)
|
||||||
|
{
|
||||||
|
if ($value) {
|
||||||
|
$this->generateNginxConfiguration();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function updatedApplicationBuildPack()
|
public function updatedApplicationBuildPack()
|
||||||
{
|
{
|
||||||
if ($this->application->build_pack !== 'nixpacks') {
|
if ($this->application->build_pack !== 'nixpacks') {
|
||||||
@@ -257,6 +266,7 @@ class General extends Component
|
|||||||
if ($this->application->build_pack === 'static') {
|
if ($this->application->build_pack === 'static') {
|
||||||
$this->application->ports_exposes = $this->ports_exposes = 80;
|
$this->application->ports_exposes = $this->ports_exposes = 80;
|
||||||
$this->resetDefaultLabels(false);
|
$this->resetDefaultLabels(false);
|
||||||
|
$this->generateNginxConfiguration();
|
||||||
}
|
}
|
||||||
$this->submit();
|
$this->submit();
|
||||||
$this->dispatch('buildPackUpdated');
|
$this->dispatch('buildPackUpdated');
|
||||||
@@ -274,6 +284,13 @@ class General extends Component
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function generateNginxConfiguration()
|
||||||
|
{
|
||||||
|
$this->application->custom_nginx_configuration = defaultNginxConfiguration();
|
||||||
|
$this->application->save();
|
||||||
|
$this->dispatch('success', 'Nginx configuration generated.');
|
||||||
|
}
|
||||||
|
|
||||||
public function resetDefaultLabels($manualReset = false)
|
public function resetDefaultLabels($manualReset = false)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
namespace App\Livewire\Project\Application\Preview;
|
namespace App\Livewire\Project\Application\Preview;
|
||||||
|
|
||||||
use App\Models\Application;
|
use App\Models\Application;
|
||||||
|
use Livewire\Attributes\Validate;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use Spatie\Url\Url;
|
use Spatie\Url\Url;
|
||||||
|
|
||||||
@@ -10,49 +11,53 @@ class Form extends Component
|
|||||||
{
|
{
|
||||||
public Application $application;
|
public Application $application;
|
||||||
|
|
||||||
public string $preview_url_template;
|
#[Validate('required')]
|
||||||
|
public string $previewUrlTemplate;
|
||||||
protected $rules = [
|
|
||||||
'application.preview_url_template' => 'required',
|
|
||||||
];
|
|
||||||
|
|
||||||
protected $validationAttributes = [
|
|
||||||
'application.preview_url_template' => 'preview url template',
|
|
||||||
];
|
|
||||||
|
|
||||||
public function resetToDefault()
|
|
||||||
{
|
|
||||||
$this->application->preview_url_template = '{{pr_id}}.{{domain}}';
|
|
||||||
$this->preview_url_template = $this->application->preview_url_template;
|
|
||||||
$this->application->save();
|
|
||||||
$this->generate_real_url();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function generate_real_url()
|
|
||||||
{
|
|
||||||
if (data_get($this->application, 'fqdn')) {
|
|
||||||
try {
|
|
||||||
$firstFqdn = str($this->application->fqdn)->before(',');
|
|
||||||
$url = Url::fromString($firstFqdn);
|
|
||||||
$host = $url->getHost();
|
|
||||||
$this->preview_url_template = str($this->application->preview_url_template)->replace('{{domain}}', $host);
|
|
||||||
} catch (\Exception) {
|
|
||||||
$this->dispatch('error', 'Invalid FQDN.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->generate_real_url();
|
try {
|
||||||
|
$this->previewUrlTemplate = $this->application->preview_url_template;
|
||||||
|
$this->generateRealUrl();
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function submit()
|
public function submit()
|
||||||
{
|
{
|
||||||
|
try {
|
||||||
|
$this->resetErrorBag();
|
||||||
$this->validate();
|
$this->validate();
|
||||||
$this->application->preview_url_template = str_replace(' ', '', $this->application->preview_url_template);
|
$this->application->preview_url_template = str_replace(' ', '', $this->previewUrlTemplate);
|
||||||
$this->application->save();
|
$this->application->save();
|
||||||
$this->dispatch('success', 'Preview url template updated.');
|
$this->dispatch('success', 'Preview url template updated.');
|
||||||
$this->generate_real_url();
|
$this->generateRealUrl();
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function resetToDefault()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->application->preview_url_template = '{{pr_id}}.{{domain}}';
|
||||||
|
$this->previewUrlTemplate = $this->application->preview_url_template;
|
||||||
|
$this->application->save();
|
||||||
|
$this->generateRealUrl();
|
||||||
|
$this->dispatch('success', 'Preview url template updated.');
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function generateRealUrl()
|
||||||
|
{
|
||||||
|
if (data_get($this->application, 'fqdn')) {
|
||||||
|
$firstFqdn = str($this->application->fqdn)->before(',');
|
||||||
|
$url = Url::fromString($firstFqdn);
|
||||||
|
$host = $url->getHost();
|
||||||
|
$this->previewUrlTemplate = str($this->application->preview_url_template)->replace('{{domain}}', $host);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ namespace App\Livewire\Project\Application;
|
|||||||
use App\Models\Application;
|
use App\Models\Application;
|
||||||
use App\Models\PrivateKey;
|
use App\Models\PrivateKey;
|
||||||
use Livewire\Attributes\Locked;
|
use Livewire\Attributes\Locked;
|
||||||
use Livewire\Attributes\Rule;
|
use Livewire\Attributes\Validate;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class Source extends Component
|
class Source extends Component
|
||||||
@@ -15,19 +15,19 @@ class Source extends Component
|
|||||||
#[Locked]
|
#[Locked]
|
||||||
public $privateKeys;
|
public $privateKeys;
|
||||||
|
|
||||||
#[Rule(['nullable', 'string'])]
|
#[Validate(['nullable', 'string'])]
|
||||||
public ?string $privateKeyName = null;
|
public ?string $privateKeyName = null;
|
||||||
|
|
||||||
#[Rule(['nullable', 'integer'])]
|
#[Validate(['nullable', 'integer'])]
|
||||||
public ?int $privateKeyId = null;
|
public ?int $privateKeyId = null;
|
||||||
|
|
||||||
#[Rule(['required', 'string'])]
|
#[Validate(['required', 'string'])]
|
||||||
public string $gitRepository;
|
public string $gitRepository;
|
||||||
|
|
||||||
#[Rule(['required', 'string'])]
|
#[Validate(['required', 'string'])]
|
||||||
public string $gitBranch;
|
public string $gitBranch;
|
||||||
|
|
||||||
#[Rule(['nullable', 'string'])]
|
#[Validate(['nullable', 'string'])]
|
||||||
public ?string $gitCommitSha = null;
|
public ?string $gitCommitSha = null;
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
|
|||||||
@@ -3,32 +3,55 @@
|
|||||||
namespace App\Livewire\Project\Application;
|
namespace App\Livewire\Project\Application;
|
||||||
|
|
||||||
use App\Models\Application;
|
use App\Models\Application;
|
||||||
|
use Livewire\Attributes\Validate;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class Swarm extends Component
|
class Swarm extends Component
|
||||||
{
|
{
|
||||||
public Application $application;
|
public Application $application;
|
||||||
|
|
||||||
public string $swarm_placement_constraints = '';
|
#[Validate('required')]
|
||||||
|
public int $swarmReplicas;
|
||||||
|
|
||||||
protected $rules = [
|
#[Validate(['nullable'])]
|
||||||
'application.swarm_replicas' => 'required',
|
public ?string $swarmPlacementConstraints = null;
|
||||||
'application.swarm_placement_constraints' => 'nullable',
|
|
||||||
'application.settings.is_swarm_only_worker_nodes' => 'required',
|
#[Validate('required')]
|
||||||
];
|
public bool $isSwarmOnlyWorkerNodes;
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
|
try {
|
||||||
|
$this->syncData();
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function syncData(bool $toModel = false)
|
||||||
|
{
|
||||||
|
if ($toModel) {
|
||||||
|
$this->validate();
|
||||||
|
$this->application->swarm_replicas = $this->swarmReplicas;
|
||||||
|
$this->application->swarm_placement_constraints = $this->swarmPlacementConstraints ? base64_encode($this->swarmPlacementConstraints) : null;
|
||||||
|
$this->application->settings->is_swarm_only_worker_nodes = $this->isSwarmOnlyWorkerNodes;
|
||||||
|
$this->application->save();
|
||||||
|
$this->application->settings->save();
|
||||||
|
} else {
|
||||||
|
$this->swarmReplicas = $this->application->swarm_replicas;
|
||||||
if ($this->application->swarm_placement_constraints) {
|
if ($this->application->swarm_placement_constraints) {
|
||||||
$this->swarm_placement_constraints = base64_decode($this->application->swarm_placement_constraints);
|
$this->swarmPlacementConstraints = base64_decode($this->application->swarm_placement_constraints);
|
||||||
|
} else {
|
||||||
|
$this->swarmPlacementConstraints = null;
|
||||||
|
}
|
||||||
|
$this->isSwarmOnlyWorkerNodes = $this->application->settings->is_swarm_only_worker_nodes;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function instantSave()
|
public function instantSave()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->validate();
|
$this->syncData(true);
|
||||||
$this->application->settings->save();
|
|
||||||
$this->dispatch('success', 'Swarm settings updated.');
|
$this->dispatch('success', 'Swarm settings updated.');
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
@@ -38,14 +61,7 @@ class Swarm extends Component
|
|||||||
public function submit()
|
public function submit()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->validate();
|
$this->syncData(true);
|
||||||
if ($this->swarm_placement_constraints) {
|
|
||||||
$this->application->swarm_placement_constraints = base64_encode($this->swarm_placement_constraints);
|
|
||||||
} else {
|
|
||||||
$this->application->swarm_placement_constraints = null;
|
|
||||||
}
|
|
||||||
$this->application->save();
|
|
||||||
|
|
||||||
$this->dispatch('success', 'Swarm settings updated.');
|
$this->dispatch('success', 'Swarm settings updated.');
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
|
|||||||
@@ -4,56 +4,87 @@ namespace App\Livewire\Project\Database;
|
|||||||
|
|
||||||
use App\Models\InstanceSettings;
|
use App\Models\InstanceSettings;
|
||||||
use App\Models\ScheduledDatabaseBackup;
|
use App\Models\ScheduledDatabaseBackup;
|
||||||
|
use Exception;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Support\Facades\Hash;
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
use Livewire\Attributes\Locked;
|
||||||
|
use Livewire\Attributes\Validate;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use Spatie\Url\Url;
|
use Spatie\Url\Url;
|
||||||
|
|
||||||
class BackupEdit extends Component
|
class BackupEdit extends Component
|
||||||
{
|
{
|
||||||
public ?ScheduledDatabaseBackup $backup;
|
public ScheduledDatabaseBackup $backup;
|
||||||
|
|
||||||
|
#[Locked]
|
||||||
public $s3s;
|
public $s3s;
|
||||||
|
|
||||||
|
#[Locked]
|
||||||
|
public $parameters;
|
||||||
|
|
||||||
|
#[Validate(['required', 'boolean'])]
|
||||||
public bool $delete_associated_backups_locally = false;
|
public bool $delete_associated_backups_locally = false;
|
||||||
|
|
||||||
|
#[Validate(['required', 'boolean'])]
|
||||||
public bool $delete_associated_backups_s3 = false;
|
public bool $delete_associated_backups_s3 = false;
|
||||||
|
|
||||||
|
#[Validate(['required', 'boolean'])]
|
||||||
public bool $delete_associated_backups_sftp = false;
|
public bool $delete_associated_backups_sftp = false;
|
||||||
|
|
||||||
|
#[Validate(['nullable', 'string'])]
|
||||||
public ?string $status = null;
|
public ?string $status = null;
|
||||||
|
|
||||||
public array $parameters;
|
#[Validate(['required', 'boolean'])]
|
||||||
|
public bool $backupEnabled = false;
|
||||||
|
|
||||||
protected $rules = [
|
#[Validate(['required', 'string'])]
|
||||||
'backup.enabled' => 'required|boolean',
|
public string $frequency = '';
|
||||||
'backup.frequency' => 'required|string',
|
|
||||||
'backup.number_of_backups_locally' => 'required|integer|min:1',
|
|
||||||
'backup.save_s3' => 'required|boolean',
|
|
||||||
'backup.s3_storage_id' => 'nullable|integer',
|
|
||||||
'backup.databases_to_backup' => 'nullable',
|
|
||||||
'backup.dump_all' => 'required|boolean',
|
|
||||||
];
|
|
||||||
|
|
||||||
protected $validationAttributes = [
|
#[Validate(['required', 'integer', 'min:1'])]
|
||||||
'backup.enabled' => 'Enabled',
|
public int $numberOfBackupsLocally = 1;
|
||||||
'backup.frequency' => 'Frequency',
|
|
||||||
'backup.number_of_backups_locally' => 'Number of Backups Locally',
|
|
||||||
'backup.save_s3' => 'Save to S3',
|
|
||||||
'backup.s3_storage_id' => 'S3 Storage',
|
|
||||||
'backup.databases_to_backup' => 'Databases to Backup',
|
|
||||||
'backup.dump_all' => 'Backup All Databases',
|
|
||||||
];
|
|
||||||
|
|
||||||
protected $messages = [
|
#[Validate(['required', 'boolean'])]
|
||||||
'backup.s3_storage_id' => 'Select a S3 Storage',
|
public bool $saveS3 = false;
|
||||||
];
|
|
||||||
|
#[Validate(['nullable', 'integer'])]
|
||||||
|
public ?int $s3StorageId = 1;
|
||||||
|
|
||||||
|
#[Validate(['nullable', 'string'])]
|
||||||
|
public ?string $databasesToBackup = null;
|
||||||
|
|
||||||
|
#[Validate(['required', 'boolean'])]
|
||||||
|
public bool $dumpAll = false;
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
|
try {
|
||||||
$this->parameters = get_route_parameters();
|
$this->parameters = get_route_parameters();
|
||||||
if (is_null(data_get($this->backup, 's3_storage_id'))) {
|
$this->syncData();
|
||||||
data_set($this->backup, 's3_storage_id', 'default');
|
} catch (Exception $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function syncData(bool $toModel = false)
|
||||||
|
{
|
||||||
|
if ($toModel) {
|
||||||
|
$this->customValidate();
|
||||||
|
$this->backup->enabled = $this->backupEnabled;
|
||||||
|
$this->backup->frequency = $this->frequency;
|
||||||
|
$this->backup->number_of_backups_locally = $this->numberOfBackupsLocally;
|
||||||
|
$this->backup->save_s3 = $this->saveS3;
|
||||||
|
$this->backup->s3_storage_id = $this->s3StorageId;
|
||||||
|
$this->backup->databases_to_backup = $this->databasesToBackup;
|
||||||
|
$this->backup->dump_all = $this->dumpAll;
|
||||||
|
$this->backup->save();
|
||||||
|
} else {
|
||||||
|
$this->backupEnabled = $this->backup->enabled;
|
||||||
|
$this->frequency = $this->backup->frequency;
|
||||||
|
$this->numberOfBackupsLocally = $this->backup->number_of_backups_locally;
|
||||||
|
$this->saveS3 = $this->backup->save_s3;
|
||||||
|
$this->s3StorageId = $this->backup->s3_storage_id;
|
||||||
|
$this->databasesToBackup = $this->backup->databases_to_backup;
|
||||||
|
$this->dumpAll = $this->backup->dump_all;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,16 +127,14 @@ class BackupEdit extends Component
|
|||||||
public function instantSave()
|
public function instantSave()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->custom_validate();
|
$this->syncData(true);
|
||||||
$this->backup->save();
|
|
||||||
$this->backup->refresh();
|
|
||||||
$this->dispatch('success', 'Backup updated successfully.');
|
$this->dispatch('success', 'Backup updated successfully.');
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->dispatch('error', $e->getMessage());
|
$this->dispatch('error', $e->getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function custom_validate()
|
private function customValidate()
|
||||||
{
|
{
|
||||||
if (! is_numeric($this->backup->s3_storage_id)) {
|
if (! is_numeric($this->backup->s3_storage_id)) {
|
||||||
$this->backup->s3_storage_id = null;
|
$this->backup->s3_storage_id = null;
|
||||||
@@ -120,19 +149,14 @@ class BackupEdit extends Component
|
|||||||
public function submit()
|
public function submit()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->custom_validate();
|
$this->syncData(true);
|
||||||
if ($this->backup->databases_to_backup === '' || $this->backup->databases_to_backup === null) {
|
$this->dispatch('success', 'Backup updated successfully.');
|
||||||
$this->backup->databases_to_backup = null;
|
|
||||||
}
|
|
||||||
$this->backup->save();
|
|
||||||
$this->backup->refresh();
|
|
||||||
$this->dispatch('success', 'Backup updated successfully');
|
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->dispatch('error', $e->getMessage());
|
$this->dispatch('error', $e->getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function deleteAssociatedBackupsLocally()
|
private function deleteAssociatedBackupsLocally()
|
||||||
{
|
{
|
||||||
$executions = $this->backup->executions;
|
$executions = $this->backup->executions;
|
||||||
$backupFolder = null;
|
$backupFolder = null;
|
||||||
@@ -152,17 +176,17 @@ class BackupEdit extends Component
|
|||||||
$execution->delete();
|
$execution->delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($backupFolder) {
|
if (str($backupFolder)->isNotEmpty()) {
|
||||||
$this->deleteEmptyBackupFolder($backupFolder, $server);
|
$this->deleteEmptyBackupFolder($backupFolder, $server);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function deleteAssociatedBackupsS3()
|
private function deleteAssociatedBackupsS3()
|
||||||
{
|
{
|
||||||
//Add function to delete backups from S3
|
//Add function to delete backups from S3
|
||||||
}
|
}
|
||||||
|
|
||||||
public function deleteAssociatedBackupsSftp()
|
private function deleteAssociatedBackupsSftp()
|
||||||
{
|
{
|
||||||
//Add function to delete backups from SFTP
|
//Add function to delete backups from SFTP
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ use App\Actions\Database\StopDatabaseProxy;
|
|||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Models\StandaloneClickhouse;
|
use App\Models\StandaloneClickhouse;
|
||||||
use Exception;
|
use Exception;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Livewire\Attributes\Validate;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class General extends Component
|
class General extends Component
|
||||||
@@ -15,54 +17,106 @@ class General extends Component
|
|||||||
|
|
||||||
public StandaloneClickhouse $database;
|
public StandaloneClickhouse $database;
|
||||||
|
|
||||||
public ?string $db_url = null;
|
#[Validate(['required', 'string'])]
|
||||||
|
public string $name;
|
||||||
|
|
||||||
public ?string $db_url_public = null;
|
#[Validate(['nullable', 'string'])]
|
||||||
|
public ?string $description = null;
|
||||||
|
|
||||||
protected $listeners = ['refresh'];
|
#[Validate(['required', 'string'])]
|
||||||
|
public string $clickhouseAdminUser;
|
||||||
|
|
||||||
protected $rules = [
|
#[Validate(['required', 'string'])]
|
||||||
'database.name' => 'required',
|
public string $clickhouseAdminPassword;
|
||||||
'database.description' => 'nullable',
|
|
||||||
'database.clickhouse_admin_user' => 'required',
|
#[Validate(['required', 'string'])]
|
||||||
'database.clickhouse_admin_password' => 'required',
|
public string $image;
|
||||||
'database.image' => 'required',
|
|
||||||
'database.ports_mappings' => 'nullable',
|
#[Validate(['nullable', 'string'])]
|
||||||
'database.is_public' => 'nullable|boolean',
|
public ?string $portsMappings = null;
|
||||||
'database.public_port' => 'nullable|integer',
|
|
||||||
'database.is_log_drain_enabled' => 'nullable|boolean',
|
#[Validate(['nullable', 'boolean'])]
|
||||||
'database.custom_docker_run_options' => 'nullable',
|
public ?bool $isPublic = null;
|
||||||
];
|
|
||||||
|
#[Validate(['nullable', 'integer'])]
|
||||||
protected $validationAttributes = [
|
public ?int $publicPort = null;
|
||||||
'database.name' => 'Name',
|
|
||||||
'database.description' => 'Description',
|
#[Validate(['nullable', 'string'])]
|
||||||
'database.clickhouse_admin_user' => 'Postgres User',
|
public ?string $customDockerRunOptions = null;
|
||||||
'database.clickhouse_admin_password' => 'Postgres Password',
|
|
||||||
'database.image' => 'Image',
|
#[Validate(['nullable', 'string'])]
|
||||||
'database.ports_mappings' => 'Port Mapping',
|
public ?string $dbUrl = null;
|
||||||
'database.is_public' => 'Is Public',
|
|
||||||
'database.public_port' => 'Public Port',
|
#[Validate(['nullable', 'string'])]
|
||||||
'database.custom_docker_run_options' => 'Custom Docker Run Options',
|
public ?string $dbUrlPublic = null;
|
||||||
|
|
||||||
|
#[Validate(['nullable', 'boolean'])]
|
||||||
|
public bool $isLogDrainEnabled = false;
|
||||||
|
|
||||||
|
public function getListeners()
|
||||||
|
{
|
||||||
|
$teamId = Auth::user()->currentTeam()->id;
|
||||||
|
|
||||||
|
return [
|
||||||
|
"echo-private:team.{$teamId},DatabaseProxyStopped" => 'databaseProxyStopped',
|
||||||
];
|
];
|
||||||
|
}
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->db_url = $this->database->internal_db_url;
|
try {
|
||||||
$this->db_url_public = $this->database->external_db_url;
|
$this->syncData();
|
||||||
$this->server = data_get($this->database, 'destination.server');
|
$this->server = data_get($this->database, 'destination.server');
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function syncData(bool $toModel = false)
|
||||||
|
{
|
||||||
|
if ($toModel) {
|
||||||
|
$this->validate();
|
||||||
|
$this->database->name = $this->name;
|
||||||
|
$this->database->description = $this->description;
|
||||||
|
$this->database->clickhouse_admin_user = $this->clickhouseAdminUser;
|
||||||
|
$this->database->clickhouse_admin_password = $this->clickhouseAdminPassword;
|
||||||
|
$this->database->image = $this->image;
|
||||||
|
$this->database->ports_mappings = $this->portsMappings;
|
||||||
|
$this->database->is_public = $this->isPublic;
|
||||||
|
$this->database->public_port = $this->publicPort;
|
||||||
|
$this->database->custom_docker_run_options = $this->customDockerRunOptions;
|
||||||
|
$this->database->is_log_drain_enabled = $this->isLogDrainEnabled;
|
||||||
|
$this->database->save();
|
||||||
|
|
||||||
|
$this->dbUrl = $this->database->internal_db_url;
|
||||||
|
$this->dbUrlPublic = $this->database->external_db_url;
|
||||||
|
} else {
|
||||||
|
$this->name = $this->database->name;
|
||||||
|
$this->description = $this->database->description;
|
||||||
|
$this->clickhouseAdminUser = $this->database->clickhouse_admin_user;
|
||||||
|
$this->clickhouseAdminPassword = $this->database->clickhouse_admin_password;
|
||||||
|
$this->image = $this->database->image;
|
||||||
|
$this->portsMappings = $this->database->ports_mappings;
|
||||||
|
$this->isPublic = $this->database->is_public;
|
||||||
|
$this->publicPort = $this->database->public_port;
|
||||||
|
$this->customDockerRunOptions = $this->database->custom_docker_run_options;
|
||||||
|
$this->isLogDrainEnabled = $this->database->is_log_drain_enabled;
|
||||||
|
$this->dbUrl = $this->database->internal_db_url;
|
||||||
|
$this->dbUrlPublic = $this->database->external_db_url;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function instantSaveAdvanced()
|
public function instantSaveAdvanced()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if (! $this->server->isLogDrainEnabled()) {
|
if (! $this->server->isLogDrainEnabled()) {
|
||||||
$this->database->is_log_drain_enabled = false;
|
$this->isLogDrainEnabled = false;
|
||||||
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
|
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$this->database->save();
|
$this->syncData(true);
|
||||||
|
|
||||||
$this->dispatch('success', 'Database updated.');
|
$this->dispatch('success', 'Database updated.');
|
||||||
$this->dispatch('success', 'You need to restart the service for the changes to take effect.');
|
$this->dispatch('success', 'You need to restart the service for the changes to take effect.');
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
@@ -73,16 +127,16 @@ class General extends Component
|
|||||||
public function instantSave()
|
public function instantSave()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if ($this->database->is_public && ! $this->database->public_port) {
|
if ($this->isPublic && ! $this->publicPort) {
|
||||||
$this->dispatch('error', 'Public port is required.');
|
$this->dispatch('error', 'Public port is required.');
|
||||||
$this->database->is_public = false;
|
$this->isPublic = false;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ($this->database->is_public) {
|
if ($this->isPublic) {
|
||||||
if (! str($this->database->status)->startsWith('running')) {
|
if (! str($this->database->status)->startsWith('running')) {
|
||||||
$this->dispatch('error', 'Database must be started to be publicly accessible.');
|
$this->dispatch('error', 'Database must be started to be publicly accessible.');
|
||||||
$this->database->is_public = false;
|
$this->isPublic = false;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -92,28 +146,28 @@ class General extends Component
|
|||||||
StopDatabaseProxy::run($this->database);
|
StopDatabaseProxy::run($this->database);
|
||||||
$this->dispatch('success', 'Database is no longer publicly accessible.');
|
$this->dispatch('success', 'Database is no longer publicly accessible.');
|
||||||
}
|
}
|
||||||
$this->db_url_public = $this->database->external_db_url;
|
$this->dbUrlPublic = $this->database->external_db_url;
|
||||||
$this->database->save();
|
$this->syncData(true);
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->database->is_public = ! $this->database->is_public;
|
$this->isPublic = ! $this->isPublic;
|
||||||
|
$this->syncData(true);
|
||||||
|
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function refresh(): void
|
public function databaseProxyStopped()
|
||||||
{
|
{
|
||||||
$this->database->refresh();
|
$this->syncData();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function submit()
|
public function submit()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if (str($this->database->public_port)->isEmpty()) {
|
if (str($this->publicPort)->isEmpty()) {
|
||||||
$this->database->public_port = null;
|
$this->publicPort = null;
|
||||||
}
|
}
|
||||||
$this->validate();
|
$this->syncData(true);
|
||||||
$this->database->save();
|
|
||||||
$this->dispatch('success', 'Database updated.');
|
$this->dispatch('success', 'Database updated.');
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
|
|||||||
@@ -4,59 +4,62 @@ namespace App\Livewire\Project\Database;
|
|||||||
|
|
||||||
use App\Models\ScheduledDatabaseBackup;
|
use App\Models\ScheduledDatabaseBackup;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
|
use Livewire\Attributes\Locked;
|
||||||
|
use Livewire\Attributes\Validate;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class CreateScheduledBackup extends Component
|
class CreateScheduledBackup extends Component
|
||||||
{
|
{
|
||||||
public $database;
|
#[Validate(['required', 'string'])]
|
||||||
|
|
||||||
public $frequency;
|
public $frequency;
|
||||||
|
|
||||||
|
#[Validate(['required', 'boolean'])]
|
||||||
|
public bool $saveToS3 = false;
|
||||||
|
|
||||||
|
#[Locked]
|
||||||
|
public $database;
|
||||||
|
|
||||||
public bool $enabled = true;
|
public bool $enabled = true;
|
||||||
|
|
||||||
public bool $save_s3 = false;
|
#[Validate(['nullable', 'integer'])]
|
||||||
|
public ?int $s3StorageId = null;
|
||||||
|
|
||||||
public $s3_storage_id;
|
public Collection $definedS3s;
|
||||||
|
|
||||||
public Collection $s3s;
|
|
||||||
|
|
||||||
protected $rules = [
|
|
||||||
'frequency' => 'required|string',
|
|
||||||
'save_s3' => 'required|boolean',
|
|
||||||
];
|
|
||||||
|
|
||||||
protected $validationAttributes = [
|
|
||||||
'frequency' => 'Backup Frequency',
|
|
||||||
'save_s3' => 'Save to S3',
|
|
||||||
];
|
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->s3s = currentTeam()->s3s;
|
try {
|
||||||
if ($this->s3s->count() > 0) {
|
$this->definedS3s = currentTeam()->s3s;
|
||||||
$this->s3_storage_id = $this->s3s->first()->id;
|
if ($this->definedS3s->count() > 0) {
|
||||||
|
$this->s3StorageId = $this->definedS3s->first()->id;
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function submit(): void
|
public function submit()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->validate();
|
$this->validate();
|
||||||
|
|
||||||
$isValid = validate_cron_expression($this->frequency);
|
$isValid = validate_cron_expression($this->frequency);
|
||||||
if (! $isValid) {
|
if (! $isValid) {
|
||||||
$this->dispatch('error', 'Invalid Cron / Human expression.');
|
$this->dispatch('error', 'Invalid Cron / Human expression.');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$payload = [
|
$payload = [
|
||||||
'enabled' => true,
|
'enabled' => true,
|
||||||
'frequency' => $this->frequency,
|
'frequency' => $this->frequency,
|
||||||
'save_s3' => $this->save_s3,
|
'save_s3' => $this->saveToS3,
|
||||||
's3_storage_id' => $this->s3_storage_id,
|
's3_storage_id' => $this->s3StorageId,
|
||||||
'database_id' => $this->database->id,
|
'database_id' => $this->database->id,
|
||||||
'database_type' => $this->database->getMorphClass(),
|
'database_type' => $this->database->getMorphClass(),
|
||||||
'team_id' => currentTeam()->id,
|
'team_id' => currentTeam()->id,
|
||||||
];
|
];
|
||||||
|
|
||||||
if ($this->database->type() === 'standalone-postgresql') {
|
if ($this->database->type() === 'standalone-postgresql') {
|
||||||
$payload['databases_to_backup'] = $this->database->postgres_db;
|
$payload['databases_to_backup'] = $this->database->postgres_db;
|
||||||
} elseif ($this->database->type() === 'standalone-mysql') {
|
} elseif ($this->database->type() === 'standalone-mysql') {
|
||||||
@@ -71,11 +74,11 @@ class CreateScheduledBackup extends Component
|
|||||||
} else {
|
} else {
|
||||||
$this->dispatch('refreshScheduledBackups');
|
$this->dispatch('refreshScheduledBackups');
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
handleError($e, $this);
|
return handleError($e, $this);
|
||||||
} finally {
|
} finally {
|
||||||
$this->frequency = '';
|
$this->frequency = '';
|
||||||
$this->save_s3 = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,60 +7,111 @@ use App\Actions\Database\StopDatabaseProxy;
|
|||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Models\StandaloneDragonfly;
|
use App\Models\StandaloneDragonfly;
|
||||||
use Exception;
|
use Exception;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Livewire\Attributes\Validate;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class General extends Component
|
class General extends Component
|
||||||
{
|
{
|
||||||
protected $listeners = ['refresh'];
|
|
||||||
|
|
||||||
public Server $server;
|
public Server $server;
|
||||||
|
|
||||||
public StandaloneDragonfly $database;
|
public StandaloneDragonfly $database;
|
||||||
|
|
||||||
public ?string $db_url = null;
|
#[Validate(['required', 'string'])]
|
||||||
|
public string $name;
|
||||||
|
|
||||||
public ?string $db_url_public = null;
|
#[Validate(['nullable', 'string'])]
|
||||||
|
public ?string $description = null;
|
||||||
|
|
||||||
protected $rules = [
|
#[Validate(['required', 'string'])]
|
||||||
'database.name' => 'required',
|
public string $dragonflyPassword;
|
||||||
'database.description' => 'nullable',
|
|
||||||
'database.dragonfly_password' => 'required',
|
#[Validate(['required', 'string'])]
|
||||||
'database.image' => 'required',
|
public string $image;
|
||||||
'database.ports_mappings' => 'nullable',
|
|
||||||
'database.is_public' => 'nullable|boolean',
|
#[Validate(['nullable', 'string'])]
|
||||||
'database.public_port' => 'nullable|integer',
|
public ?string $portsMappings = null;
|
||||||
'database.is_log_drain_enabled' => 'nullable|boolean',
|
|
||||||
'database.custom_docker_run_options' => 'nullable',
|
#[Validate(['nullable', 'boolean'])]
|
||||||
];
|
public ?bool $isPublic = null;
|
||||||
|
|
||||||
protected $validationAttributes = [
|
#[Validate(['nullable', 'integer'])]
|
||||||
'database.name' => 'Name',
|
public ?int $publicPort = null;
|
||||||
'database.description' => 'Description',
|
|
||||||
'database.dragonfly_password' => 'Redis Password',
|
#[Validate(['nullable', 'string'])]
|
||||||
'database.image' => 'Image',
|
public ?string $customDockerRunOptions = null;
|
||||||
'database.ports_mappings' => 'Port Mapping',
|
|
||||||
'database.is_public' => 'Is Public',
|
#[Validate(['nullable', 'string'])]
|
||||||
'database.public_port' => 'Public Port',
|
public ?string $dbUrl = null;
|
||||||
'database.custom_docker_run_options' => 'Custom Docker Run Options',
|
|
||||||
|
#[Validate(['nullable', 'string'])]
|
||||||
|
public ?string $dbUrlPublic = null;
|
||||||
|
|
||||||
|
#[Validate(['nullable', 'boolean'])]
|
||||||
|
public bool $isLogDrainEnabled = false;
|
||||||
|
|
||||||
|
public function getListeners()
|
||||||
|
{
|
||||||
|
$teamId = Auth::user()->currentTeam()->id;
|
||||||
|
|
||||||
|
return [
|
||||||
|
"echo-private:team.{$teamId},DatabaseProxyStopped" => 'databaseProxyStopped',
|
||||||
];
|
];
|
||||||
|
}
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->db_url = $this->database->internal_db_url;
|
try {
|
||||||
$this->db_url_public = $this->database->external_db_url;
|
$this->syncData();
|
||||||
$this->server = data_get($this->database, 'destination.server');
|
$this->server = data_get($this->database, 'destination.server');
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function syncData(bool $toModel = false)
|
||||||
|
{
|
||||||
|
if ($toModel) {
|
||||||
|
$this->validate();
|
||||||
|
$this->database->name = $this->name;
|
||||||
|
$this->database->description = $this->description;
|
||||||
|
$this->database->dragonfly_password = $this->dragonflyPassword;
|
||||||
|
$this->database->image = $this->image;
|
||||||
|
$this->database->ports_mappings = $this->portsMappings;
|
||||||
|
$this->database->is_public = $this->isPublic;
|
||||||
|
$this->database->public_port = $this->publicPort;
|
||||||
|
$this->database->custom_docker_run_options = $this->customDockerRunOptions;
|
||||||
|
$this->database->is_log_drain_enabled = $this->isLogDrainEnabled;
|
||||||
|
$this->database->save();
|
||||||
|
|
||||||
|
$this->dbUrl = $this->database->internal_db_url;
|
||||||
|
$this->dbUrlPublic = $this->database->external_db_url;
|
||||||
|
} else {
|
||||||
|
$this->name = $this->database->name;
|
||||||
|
$this->description = $this->database->description;
|
||||||
|
$this->dragonflyPassword = $this->database->dragonfly_password;
|
||||||
|
$this->image = $this->database->image;
|
||||||
|
$this->portsMappings = $this->database->ports_mappings;
|
||||||
|
$this->isPublic = $this->database->is_public;
|
||||||
|
$this->publicPort = $this->database->public_port;
|
||||||
|
$this->customDockerRunOptions = $this->database->custom_docker_run_options;
|
||||||
|
$this->isLogDrainEnabled = $this->database->is_log_drain_enabled;
|
||||||
|
$this->dbUrl = $this->database->internal_db_url;
|
||||||
|
$this->dbUrlPublic = $this->database->external_db_url;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function instantSaveAdvanced()
|
public function instantSaveAdvanced()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if (! $this->server->isLogDrainEnabled()) {
|
if (! $this->server->isLogDrainEnabled()) {
|
||||||
$this->database->is_log_drain_enabled = false;
|
$this->isLogDrainEnabled = false;
|
||||||
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
|
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$this->database->save();
|
$this->syncData(true);
|
||||||
|
|
||||||
$this->dispatch('success', 'Database updated.');
|
$this->dispatch('success', 'Database updated.');
|
||||||
$this->dispatch('success', 'You need to restart the service for the changes to take effect.');
|
$this->dispatch('success', 'You need to restart the service for the changes to take effect.');
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
@@ -68,11 +119,50 @@ class General extends Component
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function instantSave()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if ($this->isPublic && ! $this->publicPort) {
|
||||||
|
$this->dispatch('error', 'Public port is required.');
|
||||||
|
$this->isPublic = false;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ($this->isPublic) {
|
||||||
|
if (! str($this->database->status)->startsWith('running')) {
|
||||||
|
$this->dispatch('error', 'Database must be started to be publicly accessible.');
|
||||||
|
$this->isPublic = false;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
StartDatabaseProxy::run($this->database);
|
||||||
|
$this->dispatch('success', 'Database is now publicly accessible.');
|
||||||
|
} else {
|
||||||
|
StopDatabaseProxy::run($this->database);
|
||||||
|
$this->dispatch('success', 'Database is no longer publicly accessible.');
|
||||||
|
}
|
||||||
|
$this->dbUrlPublic = $this->database->external_db_url;
|
||||||
|
$this->syncData(true);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$this->isPublic = ! $this->isPublic;
|
||||||
|
$this->syncData(true);
|
||||||
|
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function databaseProxyStopped()
|
||||||
|
{
|
||||||
|
$this->syncData();
|
||||||
|
}
|
||||||
|
|
||||||
public function submit()
|
public function submit()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->validate();
|
if (str($this->publicPort)->isEmpty()) {
|
||||||
$this->database->save();
|
$this->publicPort = null;
|
||||||
|
}
|
||||||
|
$this->syncData(true);
|
||||||
$this->dispatch('success', 'Database updated.');
|
$this->dispatch('success', 'Database updated.');
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
@@ -84,45 +174,4 @@ class General extends Component
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function instantSave()
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
if ($this->database->is_public && ! $this->database->public_port) {
|
|
||||||
$this->dispatch('error', 'Public port is required.');
|
|
||||||
$this->database->is_public = false;
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if ($this->database->is_public) {
|
|
||||||
if (! str($this->database->status)->startsWith('running')) {
|
|
||||||
$this->dispatch('error', 'Database must be started to be publicly accessible.');
|
|
||||||
$this->database->is_public = false;
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
StartDatabaseProxy::run($this->database);
|
|
||||||
$this->dispatch('success', 'Database is now publicly accessible.');
|
|
||||||
} else {
|
|
||||||
StopDatabaseProxy::run($this->database);
|
|
||||||
$this->dispatch('success', 'Database is no longer publicly accessible.');
|
|
||||||
}
|
|
||||||
$this->db_url_public = $this->database->external_db_url;
|
|
||||||
$this->database->save();
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
$this->database->is_public = ! $this->database->is_public;
|
|
||||||
|
|
||||||
return handleError($e, $this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function refresh(): void
|
|
||||||
{
|
|
||||||
$this->database->refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function render()
|
|
||||||
{
|
|
||||||
return view('livewire.project.database.dragonfly.general');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use App\Actions\Database\RestartDatabase;
|
|||||||
use App\Actions\Database\StartDatabase;
|
use App\Actions\Database\StartDatabase;
|
||||||
use App\Actions\Database\StopDatabase;
|
use App\Actions\Database\StopDatabase;
|
||||||
use App\Actions\Docker\GetContainersStatus;
|
use App\Actions\Docker\GetContainersStatus;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class Heading extends Component
|
class Heading extends Component
|
||||||
@@ -18,7 +19,7 @@ class Heading extends Component
|
|||||||
|
|
||||||
public function getListeners()
|
public function getListeners()
|
||||||
{
|
{
|
||||||
$userId = auth()->user()->id;
|
$userId = Auth::id();
|
||||||
|
|
||||||
return [
|
return [
|
||||||
"echo-private:user.{$userId},DatabaseStatusChanged" => 'activityFinished',
|
"echo-private:user.{$userId},DatabaseStatusChanged" => 'activityFinished',
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
namespace App\Livewire\Project\Database;
|
namespace App\Livewire\Project\Database;
|
||||||
|
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
@@ -46,7 +47,7 @@ class Import extends Component
|
|||||||
|
|
||||||
public function getListeners()
|
public function getListeners()
|
||||||
{
|
{
|
||||||
$userId = auth()->user()->id;
|
$userId = Auth::id();
|
||||||
|
|
||||||
return [
|
return [
|
||||||
"echo-private:user.{$userId},DatabaseStatusChanged" => '$refresh',
|
"echo-private:user.{$userId},DatabaseStatusChanged" => '$refresh',
|
||||||
|
|||||||
@@ -3,39 +3,39 @@
|
|||||||
namespace App\Livewire\Project\Database;
|
namespace App\Livewire\Project\Database;
|
||||||
|
|
||||||
use Exception;
|
use Exception;
|
||||||
|
use Livewire\Attributes\Locked;
|
||||||
|
use Livewire\Attributes\Validate;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class InitScript extends Component
|
class InitScript extends Component
|
||||||
{
|
{
|
||||||
|
#[Locked]
|
||||||
public array $script;
|
public array $script;
|
||||||
|
|
||||||
|
#[Locked]
|
||||||
public int $index;
|
public int $index;
|
||||||
|
|
||||||
public ?string $filename;
|
#[Validate(['nullable', 'string'])]
|
||||||
|
public ?string $filename = null;
|
||||||
|
|
||||||
public ?string $content;
|
#[Validate(['nullable', 'string'])]
|
||||||
|
public ?string $content = null;
|
||||||
protected $rules = [
|
|
||||||
'filename' => 'required|string',
|
|
||||||
'content' => 'required|string',
|
|
||||||
];
|
|
||||||
|
|
||||||
protected $validationAttributes = [
|
|
||||||
'filename' => 'Filename',
|
|
||||||
'content' => 'Content',
|
|
||||||
];
|
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
|
try {
|
||||||
$this->index = data_get($this->script, 'index');
|
$this->index = data_get($this->script, 'index');
|
||||||
$this->filename = data_get($this->script, 'filename');
|
$this->filename = data_get($this->script, 'filename');
|
||||||
$this->content = data_get($this->script, 'content');
|
$this->content = data_get($this->script, 'content');
|
||||||
|
} catch (Exception $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function submit()
|
public function submit()
|
||||||
{
|
{
|
||||||
$this->validate();
|
|
||||||
try {
|
try {
|
||||||
|
$this->validate();
|
||||||
$this->script['index'] = $this->index;
|
$this->script['index'] = $this->index;
|
||||||
$this->script['content'] = $this->content;
|
$this->script['content'] = $this->content;
|
||||||
$this->script['filename'] = $this->filename;
|
$this->script['filename'] = $this->filename;
|
||||||
@@ -47,6 +47,10 @@ class InitScript extends Component
|
|||||||
|
|
||||||
public function delete()
|
public function delete()
|
||||||
{
|
{
|
||||||
|
try {
|
||||||
$this->dispatch('delete_init_script', $this->script);
|
$this->dispatch('delete_init_script', $this->script);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,62 +7,116 @@ use App\Actions\Database\StopDatabaseProxy;
|
|||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Models\StandaloneKeydb;
|
use App\Models\StandaloneKeydb;
|
||||||
use Exception;
|
use Exception;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Livewire\Attributes\Validate;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class General extends Component
|
class General extends Component
|
||||||
{
|
{
|
||||||
protected $listeners = ['refresh'];
|
|
||||||
|
|
||||||
public Server $server;
|
public Server $server;
|
||||||
|
|
||||||
public StandaloneKeydb $database;
|
public StandaloneKeydb $database;
|
||||||
|
|
||||||
public ?string $db_url = null;
|
#[Validate(['required', 'string'])]
|
||||||
|
public string $name;
|
||||||
|
|
||||||
public ?string $db_url_public = null;
|
#[Validate(['nullable', 'string'])]
|
||||||
|
public ?string $description = null;
|
||||||
|
|
||||||
protected $rules = [
|
#[Validate(['nullable', 'string'])]
|
||||||
'database.name' => 'required',
|
public ?string $keydbConf = null;
|
||||||
'database.description' => 'nullable',
|
|
||||||
'database.keydb_conf' => 'nullable',
|
#[Validate(['required', 'string'])]
|
||||||
'database.keydb_password' => 'required',
|
public string $keydbPassword;
|
||||||
'database.image' => 'required',
|
|
||||||
'database.ports_mappings' => 'nullable',
|
#[Validate(['required', 'string'])]
|
||||||
'database.is_public' => 'nullable|boolean',
|
public string $image;
|
||||||
'database.public_port' => 'nullable|integer',
|
|
||||||
'database.is_log_drain_enabled' => 'nullable|boolean',
|
#[Validate(['nullable', 'string'])]
|
||||||
'database.custom_docker_run_options' => 'nullable',
|
public ?string $portsMappings = null;
|
||||||
];
|
|
||||||
|
#[Validate(['nullable', 'boolean'])]
|
||||||
protected $validationAttributes = [
|
public ?bool $isPublic = null;
|
||||||
'database.name' => 'Name',
|
|
||||||
'database.description' => 'Description',
|
#[Validate(['nullable', 'integer'])]
|
||||||
'database.keydb_conf' => 'Redis Configuration',
|
public ?int $publicPort = null;
|
||||||
'database.keydb_password' => 'Redis Password',
|
|
||||||
'database.image' => 'Image',
|
#[Validate(['nullable', 'string'])]
|
||||||
'database.ports_mappings' => 'Port Mapping',
|
public ?string $customDockerRunOptions = null;
|
||||||
'database.is_public' => 'Is Public',
|
|
||||||
'database.public_port' => 'Public Port',
|
#[Validate(['nullable', 'string'])]
|
||||||
'database.custom_docker_run_options' => 'Custom Docker Run Options',
|
public ?string $dbUrl = null;
|
||||||
|
|
||||||
|
#[Validate(['nullable', 'string'])]
|
||||||
|
public ?string $dbUrlPublic = null;
|
||||||
|
|
||||||
|
#[Validate(['nullable', 'boolean'])]
|
||||||
|
public bool $isLogDrainEnabled = false;
|
||||||
|
|
||||||
|
public function getListeners()
|
||||||
|
{
|
||||||
|
$teamId = Auth::user()->currentTeam()->id;
|
||||||
|
|
||||||
|
return [
|
||||||
|
"echo-private:team.{$teamId},DatabaseProxyStopped" => 'databaseProxyStopped',
|
||||||
];
|
];
|
||||||
|
}
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->db_url = $this->database->internal_db_url;
|
try {
|
||||||
$this->db_url_public = $this->database->external_db_url;
|
$this->syncData();
|
||||||
$this->server = data_get($this->database, 'destination.server');
|
$this->server = data_get($this->database, 'destination.server');
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function syncData(bool $toModel = false)
|
||||||
|
{
|
||||||
|
if ($toModel) {
|
||||||
|
$this->validate();
|
||||||
|
$this->database->name = $this->name;
|
||||||
|
$this->database->description = $this->description;
|
||||||
|
$this->database->keydb_conf = $this->keydbConf;
|
||||||
|
$this->database->keydb_password = $this->keydbPassword;
|
||||||
|
$this->database->image = $this->image;
|
||||||
|
$this->database->ports_mappings = $this->portsMappings;
|
||||||
|
$this->database->is_public = $this->isPublic;
|
||||||
|
$this->database->public_port = $this->publicPort;
|
||||||
|
$this->database->custom_docker_run_options = $this->customDockerRunOptions;
|
||||||
|
$this->database->is_log_drain_enabled = $this->isLogDrainEnabled;
|
||||||
|
$this->database->save();
|
||||||
|
|
||||||
|
$this->dbUrl = $this->database->internal_db_url;
|
||||||
|
$this->dbUrlPublic = $this->database->external_db_url;
|
||||||
|
} else {
|
||||||
|
$this->name = $this->database->name;
|
||||||
|
$this->description = $this->database->description;
|
||||||
|
$this->keydbConf = $this->database->keydb_conf;
|
||||||
|
$this->keydbPassword = $this->database->keydb_password;
|
||||||
|
$this->image = $this->database->image;
|
||||||
|
$this->portsMappings = $this->database->ports_mappings;
|
||||||
|
$this->isPublic = $this->database->is_public;
|
||||||
|
$this->publicPort = $this->database->public_port;
|
||||||
|
$this->customDockerRunOptions = $this->database->custom_docker_run_options;
|
||||||
|
$this->isLogDrainEnabled = $this->database->is_log_drain_enabled;
|
||||||
|
$this->dbUrl = $this->database->internal_db_url;
|
||||||
|
$this->dbUrlPublic = $this->database->external_db_url;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function instantSaveAdvanced()
|
public function instantSaveAdvanced()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if (! $this->server->isLogDrainEnabled()) {
|
if (! $this->server->isLogDrainEnabled()) {
|
||||||
$this->database->is_log_drain_enabled = false;
|
$this->isLogDrainEnabled = false;
|
||||||
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
|
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$this->database->save();
|
$this->syncData(true);
|
||||||
|
|
||||||
$this->dispatch('success', 'Database updated.');
|
$this->dispatch('success', 'Database updated.');
|
||||||
$this->dispatch('success', 'You need to restart the service for the changes to take effect.');
|
$this->dispatch('success', 'You need to restart the service for the changes to take effect.');
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
@@ -70,14 +124,50 @@ class General extends Component
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function instantSave()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if ($this->isPublic && ! $this->publicPort) {
|
||||||
|
$this->dispatch('error', 'Public port is required.');
|
||||||
|
$this->isPublic = false;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ($this->isPublic) {
|
||||||
|
if (! str($this->database->status)->startsWith('running')) {
|
||||||
|
$this->dispatch('error', 'Database must be started to be publicly accessible.');
|
||||||
|
$this->isPublic = false;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
StartDatabaseProxy::run($this->database);
|
||||||
|
$this->dispatch('success', 'Database is now publicly accessible.');
|
||||||
|
} else {
|
||||||
|
StopDatabaseProxy::run($this->database);
|
||||||
|
$this->dispatch('success', 'Database is no longer publicly accessible.');
|
||||||
|
}
|
||||||
|
$this->dbUrlPublic = $this->database->external_db_url;
|
||||||
|
$this->syncData(true);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$this->isPublic = ! $this->isPublic;
|
||||||
|
$this->syncData(true);
|
||||||
|
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function databaseProxyStopped()
|
||||||
|
{
|
||||||
|
$this->syncData();
|
||||||
|
}
|
||||||
|
|
||||||
public function submit()
|
public function submit()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->validate();
|
if (str($this->publicPort)->isEmpty()) {
|
||||||
if ($this->database->keydb_conf === '') {
|
$this->publicPort = null;
|
||||||
$this->database->keydb_conf = null;
|
|
||||||
}
|
}
|
||||||
$this->database->save();
|
$this->syncData(true);
|
||||||
$this->dispatch('success', 'Database updated.');
|
$this->dispatch('success', 'Database updated.');
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
@@ -89,45 +179,4 @@ class General extends Component
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function instantSave()
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
if ($this->database->is_public && ! $this->database->public_port) {
|
|
||||||
$this->dispatch('error', 'Public port is required.');
|
|
||||||
$this->database->is_public = false;
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if ($this->database->is_public) {
|
|
||||||
if (! str($this->database->status)->startsWith('running')) {
|
|
||||||
$this->dispatch('error', 'Database must be started to be publicly accessible.');
|
|
||||||
$this->database->is_public = false;
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
StartDatabaseProxy::run($this->database);
|
|
||||||
$this->dispatch('success', 'Database is now publicly accessible.');
|
|
||||||
} else {
|
|
||||||
StopDatabaseProxy::run($this->database);
|
|
||||||
$this->dispatch('success', 'Database is no longer publicly accessible.');
|
|
||||||
}
|
|
||||||
$this->db_url_public = $this->database->external_db_url;
|
|
||||||
$this->database->save();
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
$this->database->is_public = ! $this->database->is_public;
|
|
||||||
|
|
||||||
return handleError($e, $this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function refresh(): void
|
|
||||||
{
|
|
||||||
$this->database->refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function render()
|
|
||||||
{
|
|
||||||
return view('livewire.project.database.keydb.general');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,6 @@ class DeleteEnvironment extends Component
|
|||||||
return redirect()->route('project.show', parameters: ['project_uuid' => $this->parameters['project_uuid']]);
|
return redirect()->route('project.show', parameters: ['project_uuid' => $this->parameters['project_uuid']]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->dispatch('error', 'Environment has defined resources, please delete them first.');
|
return $this->dispatch('error', "<strong>Environment {$environment->name}</strong> has defined resources, please delete them first.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,11 +27,12 @@ class DeleteProject extends Component
|
|||||||
'project_id' => 'required|int',
|
'project_id' => 'required|int',
|
||||||
]);
|
]);
|
||||||
$project = Project::findOrFail($this->project_id);
|
$project = Project::findOrFail($this->project_id);
|
||||||
if ($project->applications->count() > 0) {
|
if ($project->isEmpty()) {
|
||||||
return $this->dispatch('error', 'Project has resources defined, please delete them first.');
|
|
||||||
}
|
|
||||||
$project->delete();
|
$project->delete();
|
||||||
|
|
||||||
return redirect()->route('project.index');
|
return redirect()->route('project.index');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return $this->dispatch('error', "<strong>Project {$project->name}</strong> has resources defined, please delete them first.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,17 +3,17 @@
|
|||||||
namespace App\Livewire\Project;
|
namespace App\Livewire\Project;
|
||||||
|
|
||||||
use App\Models\Project;
|
use App\Models\Project;
|
||||||
use Livewire\Attributes\Rule;
|
use Livewire\Attributes\Validate;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class Edit extends Component
|
class Edit extends Component
|
||||||
{
|
{
|
||||||
public Project $project;
|
public Project $project;
|
||||||
|
|
||||||
#[Rule(['required', 'string', 'min:3', 'max:255'])]
|
#[Validate(['required', 'string', 'min:3', 'max:255'])]
|
||||||
public string $name;
|
public string $name;
|
||||||
|
|
||||||
#[Rule(['nullable', 'string', 'max:255'])]
|
#[Validate(['nullable', 'string', 'max:255'])]
|
||||||
public ?string $description = null;
|
public ?string $description = null;
|
||||||
|
|
||||||
public function mount(string $project_uuid)
|
public function mount(string $project_uuid)
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ namespace App\Livewire\Project;
|
|||||||
use App\Models\Application;
|
use App\Models\Application;
|
||||||
use App\Models\Project;
|
use App\Models\Project;
|
||||||
use Livewire\Attributes\Locked;
|
use Livewire\Attributes\Locked;
|
||||||
use Livewire\Attributes\Rule;
|
use Livewire\Attributes\Validate;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class EnvironmentEdit extends Component
|
class EnvironmentEdit extends Component
|
||||||
@@ -17,10 +17,10 @@ class EnvironmentEdit extends Component
|
|||||||
#[Locked]
|
#[Locked]
|
||||||
public $environment;
|
public $environment;
|
||||||
|
|
||||||
#[Rule(['required', 'string', 'min:3', 'max:255'])]
|
#[Validate(['required', 'string', 'min:3', 'max:255'])]
|
||||||
public string $name;
|
public string $name;
|
||||||
|
|
||||||
#[Rule(['nullable', 'string', 'max:255'])]
|
#[Validate(['nullable', 'string', 'max:255'])]
|
||||||
public ?string $description = null;
|
public ?string $description = null;
|
||||||
|
|
||||||
public function mount(string $project_uuid, string $environment_name)
|
public function mount(string $project_uuid, string $environment_name)
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user