mirror of
https://github.com/ershisan99/coolify.git
synced 2025-12-18 12:33:06 +00:00
Compare commits
99 Commits
v4.0.0-bet
...
v4.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ed5f831c86 | ||
|
|
65be83e75d | ||
|
|
25a471b045 | ||
|
|
60c7a29aa8 | ||
|
|
11ab6669a0 | ||
|
|
53965ab8ed | ||
|
|
ea271ca079 | ||
|
|
f16d0f650f | ||
|
|
cb80341a78 | ||
|
|
83d96c8d11 | ||
|
|
a8ca57d095 | ||
|
|
2d936a4b22 | ||
|
|
0653eb8511 | ||
|
|
cc64132627 | ||
|
|
e0391e5abd | ||
|
|
025135bd2a | ||
|
|
5e5873a08d | ||
|
|
14652c2878 | ||
|
|
a7ef5c456d | ||
|
|
117f1d4155 | ||
|
|
075f3ce930 | ||
|
|
d5b3e88fc4 | ||
|
|
ba55e0c1bb | ||
|
|
54671354f0 | ||
|
|
a2c7e8d455 | ||
|
|
32dbdf5204 | ||
|
|
019887739c | ||
|
|
5596e41f2b | ||
|
|
6a73a00a1f | ||
|
|
4121c9dd8b | ||
|
|
e4781dc129 | ||
|
|
1c90f46f2a | ||
|
|
e78d851c85 | ||
|
|
52d05005ed | ||
|
|
f03aa57758 | ||
|
|
8c20c833ba | ||
|
|
2fe6766b7f | ||
|
|
d9599da4a8 | ||
|
|
3f453ba7c0 | ||
|
|
bd02c3055a | ||
|
|
7ea7d85d15 | ||
|
|
d38350c282 | ||
|
|
a83c70004c | ||
|
|
49e1404a2c | ||
|
|
76f23e7dbf | ||
|
|
ad8653f54d | ||
|
|
8939d77051 | ||
|
|
37be4a1796 | ||
|
|
e4c923e358 | ||
|
|
62ca3ffaa5 | ||
|
|
9af3ce4be5 | ||
|
|
fe143ef8a5 | ||
|
|
5fb5845e90 | ||
|
|
794cfbd8eb | ||
|
|
29ee9915f3 | ||
|
|
331d485213 | ||
|
|
665e3761c4 | ||
|
|
ac19f0e34f | ||
|
|
d7cfa0578f | ||
|
|
694169bb84 | ||
|
|
ab853cac87 | ||
|
|
51db2f797d | ||
|
|
0c90d3d0a1 | ||
|
|
51efe23690 | ||
|
|
e3ee84105c | ||
|
|
6cbd61ac6c | ||
|
|
638d0c8c99 | ||
|
|
aecc81fe9d | ||
|
|
c9a1437870 | ||
|
|
66b41b3d4c | ||
|
|
c41cfe2a2f | ||
|
|
5f2ad56529 | ||
|
|
cd842bc1b2 | ||
|
|
27b6aad53a | ||
|
|
64b58b7661 | ||
|
|
94960d96a9 | ||
|
|
2549244f97 | ||
|
|
5bfffce33b | ||
|
|
3a4f19f368 | ||
|
|
50e17ed932 | ||
|
|
a8fcd7aee4 | ||
|
|
87036cc49b | ||
|
|
e48842c6ec | ||
|
|
b9e405c497 | ||
|
|
f75effe022 | ||
|
|
a745f568f3 | ||
|
|
e2e3ad0358 | ||
|
|
ba769f5fb7 | ||
|
|
0126286731 | ||
|
|
7952202435 | ||
|
|
798acb8ee5 | ||
|
|
ef595dd4c2 | ||
|
|
70c662daf8 | ||
|
|
8ae385b9f9 | ||
|
|
802a0f7684 | ||
|
|
62c38c9859 | ||
|
|
27c36bec83 | ||
|
|
c6b8eabe10 | ||
|
|
967fca9eca |
94
.github/workflows/development-build.yml
vendored
94
.github/workflows/development-build.yml
vendored
@@ -2,7 +2,7 @@ name: Development Build (v4)
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["next"]
|
||||
branches-ignore: ["main", "v3"]
|
||||
paths-ignore:
|
||||
- .github/workflows/coolify-helper.yml
|
||||
- docker/coolify-helper/Dockerfile
|
||||
@@ -29,51 +29,51 @@ jobs:
|
||||
file: docker/prod-ssu/Dockerfile
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:next
|
||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}
|
||||
aarch64:
|
||||
runs-on: [self-hosted, arm64]
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Login to ghcr.io
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Build image and push to registry
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
file: docker/prod-ssu/Dockerfile
|
||||
platforms: linux/aarch64
|
||||
push: true
|
||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:next-aarch64
|
||||
runs-on: [self-hosted, arm64]
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Login to ghcr.io
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Build image and push to registry
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
file: docker/prod-ssu/Dockerfile
|
||||
platforms: linux/aarch64
|
||||
push: true
|
||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}-aarch64
|
||||
merge-manifest:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
needs: [amd64, aarch64]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
- name: Login to ghcr.io
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Create & publish manifest
|
||||
run: |
|
||||
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:next-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:next
|
||||
- uses: sarisia/actions-status-discord@v1
|
||||
if: always()
|
||||
with:
|
||||
webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
needs: [amd64, aarch64]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
- name: Login to ghcr.io
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Create & publish manifest
|
||||
run: |
|
||||
docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}-aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}
|
||||
- uses: sarisia/actions-status-discord@v1
|
||||
if: always()
|
||||
with:
|
||||
webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }}
|
||||
|
||||
62
README.md
62
README.md
@@ -17,6 +17,40 @@ https://coolify.io/sponsorships
|
||||
|
||||
Thank you so much!
|
||||
|
||||
Special thanks to our biggest sponsors, [CCCareers](https://cccareers.org/) and [Appwrite](https://appwrite.io)!
|
||||
|
||||
<a href="https://cccareers.org/" target="_blank"><img src="./other/logos/ccc-logo.webp" alt="cccareers logo" width="200"/></a>
|
||||
<a href="https://appwrite.io" target="_blank"><img src="./other/logos/appwrite.svg" alt="appwrite logo" width="200"/></a>
|
||||
|
||||
## Github Sponsors ($15+)
|
||||
<a href="https://bc.direct"><img width="60px" alt="BC Direct" src="https://github.com/coollabsio/coolify/assets/5845193/a4063c41-95ed-4a32-8814-cd1475572e37"/></a>
|
||||
<a href="https://github.com/automazeio"><img src="https://github.com/automazeio.png" width="60px" alt="Corentin Clichy" /></a>
|
||||
<a href="https://github.com/corentinclichy"><img src="https://github.com/corentinclichy.png" width="60px" alt="Corentin Clichy" /></a>
|
||||
<a href="https://github.com/Niki2k1"><img src="https://github.com/Niki2k1.png" width="60px" alt="Niklas Lausch" /></a>
|
||||
<a href="https://github.com/pixelinfinito"><img src="https://github.com/pixelinfinito.png" width="60px" alt="Pixel Infinito" /></a>
|
||||
<a href="https://github.com/whitesidest"><img src="https://avatars.githubusercontent.com/u/12365916?s=52&v=4" width="60px" alt="Tyler Whitesides" /></a>
|
||||
<a href="https://github.com/aniftyco"><img src="https://github.com/aniftyco.png" width="60px" alt="NiftyCo" /></a>
|
||||
<a href="https://github.com/iujlaki"><img src="https://github.com/iujlaki.png" width="60px" alt="Imre Ujlaki" /></a>
|
||||
<a href="https://github.com/Illyism"><img src="https://github.com/Illyism.png" width="60px" alt="Ilias Ism" /></a>
|
||||
<a href="https://github.com/urtho"><img src="https://github.com/urtho.png" width="60px" alt="Paweł Pierścionek" /></a>
|
||||
<a href="https://github.com/monocursive"><img src="https://github.com/monocursive.png" width="60px" alt="Michael Mazurczak" /></a>
|
||||
|
||||
## Organizations
|
||||
<a href="https://opencollective.com/coollabsio/organization/0/website"><img src="https://opencollective.com/coollabsio/organization/0/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/coollabsio/organization/1/website"><img src="https://opencollective.com/coollabsio/organization/1/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/coollabsio/organization/2/website"><img src="https://opencollective.com/coollabsio/organization/2/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/coollabsio/organization/3/website"><img src="https://opencollective.com/coollabsio/organization/3/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/coollabsio/organization/4/website"><img src="https://opencollective.com/coollabsio/organization/4/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/coollabsio/organization/5/website"><img src="https://opencollective.com/coollabsio/organization/5/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/coollabsio/organization/6/website"><img src="https://opencollective.com/coollabsio/organization/6/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/coollabsio/organization/7/website"><img src="https://opencollective.com/coollabsio/organization/7/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/coollabsio/organization/8/website"><img src="https://opencollective.com/coollabsio/organization/8/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/coollabsio/organization/9/website"><img src="https://opencollective.com/coollabsio/organization/9/avatar.svg"></a>
|
||||
|
||||
## Individuals
|
||||
|
||||
<a href="https://opencollective.com/coollabsio"><img src="https://opencollective.com/coollabsio/individuals.svg?width=890"></a>
|
||||
|
||||
# Cloud
|
||||
|
||||
If you do not want to self-host Coolify, there is a paid cloud version available: https://app.coolify.io
|
||||
@@ -59,34 +93,6 @@ Contact us [here](https://coolify.io/docs/contact).
|
||||
|
||||
<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>
|
||||
|
||||
# 💰 Financial Contributors
|
||||
|
||||
Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/coollabsio/contribute)]
|
||||
|
||||
## Organizations
|
||||
|
||||
Special thanks to our biggest sponsors, [CCCareers](https://cccareers.org/) and [Appwrite](https://appwrite.io)!
|
||||
|
||||
<a href="https://cccareers.org/" target="_blank"><img src="./other/logos/ccc-logo.webp" alt="appwrite logo" width="200"/></a>
|
||||
<a href="https://appwrite.io" target="_blank"><img src="./other/logos/appwrite.svg" alt="appwrite logo" width="200"/></a>
|
||||
|
||||
Support this project with your organization. Your logo will show up here with a link to your website.
|
||||
|
||||
<a href="https://opencollective.com/coollabsio/organization/0/website"><img src="https://opencollective.com/coollabsio/organization/0/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/coollabsio/organization/1/website"><img src="https://opencollective.com/coollabsio/organization/1/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/coollabsio/organization/2/website"><img src="https://opencollective.com/coollabsio/organization/2/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/coollabsio/organization/3/website"><img src="https://opencollective.com/coollabsio/organization/3/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/coollabsio/organization/4/website"><img src="https://opencollective.com/coollabsio/organization/4/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/coollabsio/organization/5/website"><img src="https://opencollective.com/coollabsio/organization/5/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/coollabsio/organization/6/website"><img src="https://opencollective.com/coollabsio/organization/6/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/coollabsio/organization/7/website"><img src="https://opencollective.com/coollabsio/organization/7/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/coollabsio/organization/8/website"><img src="https://opencollective.com/coollabsio/organization/8/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/coollabsio/organization/9/website"><img src="https://opencollective.com/coollabsio/organization/9/avatar.svg"></a>
|
||||
|
||||
## Individuals
|
||||
|
||||
<a href="https://opencollective.com/coollabsio"><img src="https://opencollective.com/coollabsio/individuals.svg?width=890"></a>
|
||||
|
||||
# Star History
|
||||
|
||||
[](https://star-history.com/#coollabsio/coolify&Date)
|
||||
|
||||
@@ -56,7 +56,7 @@ class StartMariadb
|
||||
'memswap_limit' => $this->database->limits_memory_swap,
|
||||
'mem_swappiness' => $this->database->limits_memory_swappiness,
|
||||
'mem_reservation' => $this->database->limits_memory_reservation,
|
||||
'cpus' => (int) $this->database->limits_cpus,
|
||||
'cpus' => (float) $this->database->limits_cpus,
|
||||
'cpuset' => $this->database->limits_cpuset,
|
||||
'cpu_shares' => $this->database->limits_cpu_shares,
|
||||
]
|
||||
|
||||
@@ -63,7 +63,7 @@ class StartMongodb
|
||||
'memswap_limit' => $this->database->limits_memory_swap,
|
||||
'mem_swappiness' => $this->database->limits_memory_swappiness,
|
||||
'mem_reservation' => $this->database->limits_memory_reservation,
|
||||
'cpus' => (int) $this->database->limits_cpus,
|
||||
'cpus' => (float) $this->database->limits_cpus,
|
||||
'cpuset' => $this->database->limits_cpuset,
|
||||
'cpu_shares' => $this->database->limits_cpu_shares,
|
||||
]
|
||||
|
||||
@@ -56,7 +56,7 @@ class StartMysql
|
||||
'memswap_limit' => $this->database->limits_memory_swap,
|
||||
'mem_swappiness' => $this->database->limits_memory_swappiness,
|
||||
'mem_reservation' => $this->database->limits_memory_reservation,
|
||||
'cpus' => (int) $this->database->limits_cpus,
|
||||
'cpus' => (float) $this->database->limits_cpus,
|
||||
'cpuset' => $this->database->limits_cpuset,
|
||||
'cpu_shares' => $this->database->limits_cpu_shares,
|
||||
]
|
||||
|
||||
@@ -66,7 +66,7 @@ class StartPostgresql
|
||||
'memswap_limit' => $this->database->limits_memory_swap,
|
||||
'mem_swappiness' => $this->database->limits_memory_swappiness,
|
||||
'mem_reservation' => $this->database->limits_memory_reservation,
|
||||
'cpus' => (int) $this->database->limits_cpus,
|
||||
'cpus' => (float) $this->database->limits_cpus,
|
||||
'cpuset' => $this->database->limits_cpuset,
|
||||
'cpu_shares' => $this->database->limits_cpu_shares,
|
||||
]
|
||||
|
||||
@@ -65,7 +65,7 @@ class StartRedis
|
||||
'memswap_limit' => $this->database->limits_memory_swap,
|
||||
'mem_swappiness' => $this->database->limits_memory_swappiness,
|
||||
'mem_reservation' => $this->database->limits_memory_reservation,
|
||||
'cpus' => (int) $this->database->limits_cpus,
|
||||
'cpus' => (float) $this->database->limits_cpus,
|
||||
'cpuset' => $this->database->limits_cpuset,
|
||||
'cpu_shares' => $this->database->limits_cpu_shares,
|
||||
]
|
||||
|
||||
@@ -21,7 +21,10 @@ class CheckProxy
|
||||
$status = getContainerStatus($server, 'coolify-proxy_traefik');
|
||||
$server->proxy->set('status', $status);
|
||||
$server->save();
|
||||
return false;
|
||||
if ($status === 'running') {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
$status = getContainerStatus($server, 'coolify-proxy');
|
||||
if ($status === 'running') {
|
||||
|
||||
@@ -21,14 +21,14 @@ class DeleteService
|
||||
foreach ($storages as $storage) {
|
||||
$storagesToDelete->push($storage);
|
||||
}
|
||||
$application->delete();
|
||||
$application->forceDelete();
|
||||
}
|
||||
foreach ($service->databases()->get() as $database) {
|
||||
$storages = $database->persistentStorages()->get();
|
||||
foreach ($storages as $storage) {
|
||||
$storagesToDelete->push($storage);
|
||||
}
|
||||
$database->delete();
|
||||
$database->forceDelete();
|
||||
}
|
||||
foreach ($storagesToDelete as $storage) {
|
||||
$commands[] = "docker volume rm -f $storage->name";
|
||||
|
||||
@@ -7,6 +7,7 @@ use App\Jobs\CleanupHelperContainersJob;
|
||||
use App\Models\Application;
|
||||
use App\Models\ApplicationDeploymentQueue;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\ScheduledDatabaseBackup;
|
||||
use App\Models\Server;
|
||||
use App\Models\Service;
|
||||
use App\Models\ServiceApplication;
|
||||
@@ -31,6 +32,9 @@ class Init extends Command
|
||||
if ($cleanup) {
|
||||
echo "Running cleanup\n";
|
||||
$this->cleanup_stucked_resources();
|
||||
// Required for falsely deleted coolify db
|
||||
$this->restore_coolify_db_backup();
|
||||
|
||||
// $this->cleanup_ssh();
|
||||
}
|
||||
$this->cleanup_in_progress_application_deployments();
|
||||
@@ -51,6 +55,30 @@ class Init extends Command
|
||||
}
|
||||
}
|
||||
}
|
||||
private function restore_coolify_db_backup()
|
||||
{
|
||||
try {
|
||||
$database = StandalonePostgresql::withTrashed()->find(0);
|
||||
if ($database && $database->trashed()) {
|
||||
echo "Restoring coolify db backup\n";
|
||||
$database->restore();
|
||||
$scheduledBackup = ScheduledDatabaseBackup::find(0);
|
||||
if (!$scheduledBackup) {
|
||||
ScheduledDatabaseBackup::create([
|
||||
'id' => 0,
|
||||
'enabled' => true,
|
||||
'save_s3' => false,
|
||||
'frequency' => '0 0 * * *',
|
||||
'database_id' => $database->id,
|
||||
'database_type' => 'App\Models\StandalonePostgresql',
|
||||
'team_id' => 0,
|
||||
]);
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in restoring coolify db backup: {$e->getMessage()}\n";
|
||||
}
|
||||
}
|
||||
private function cleanup_stucked_helper_containers()
|
||||
{
|
||||
$servers = Server::all();
|
||||
@@ -110,6 +138,89 @@ class Init extends Command
|
||||
}
|
||||
private function cleanup_stucked_resources()
|
||||
{
|
||||
|
||||
try {
|
||||
$applications = Application::withTrashed()->whereNotNull('deleted_at')->get();
|
||||
foreach ($applications as $application) {
|
||||
echo "Deleting stucked application: {$application->name}\n";
|
||||
$application->forceDelete();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in cleaning stucked application: {$e->getMessage()}\n";
|
||||
}
|
||||
try {
|
||||
$postgresqls = StandalonePostgresql::withTrashed()->whereNotNull('deleted_at')->get();
|
||||
foreach ($postgresqls as $postgresql) {
|
||||
echo "Deleting stucked postgresql: {$postgresql->name}\n";
|
||||
$postgresql->forceDelete();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in cleaning stucked postgresql: {$e->getMessage()}\n";
|
||||
}
|
||||
try {
|
||||
$redis = StandaloneRedis::withTrashed()->whereNotNull('deleted_at')->get();
|
||||
foreach ($redis as $redis) {
|
||||
echo "Deleting stucked redis: {$redis->name}\n";
|
||||
$redis->forceDelete();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in cleaning stucked redis: {$e->getMessage()}\n";
|
||||
}
|
||||
try {
|
||||
$mongodbs = StandaloneMongodb::withTrashed()->whereNotNull('deleted_at')->get();
|
||||
foreach ($mongodbs as $mongodb) {
|
||||
echo "Deleting stucked mongodb: {$mongodb->name}\n";
|
||||
$mongodb->forceDelete();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in cleaning stucked mongodb: {$e->getMessage()}\n";
|
||||
}
|
||||
try {
|
||||
$mysqls = StandaloneMysql::withTrashed()->whereNotNull('deleted_at')->get();
|
||||
foreach ($mysqls as $mysql) {
|
||||
echo "Deleting stucked mysql: {$mysql->name}\n";
|
||||
$mysql->forceDelete();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in cleaning stucked mysql: {$e->getMessage()}\n";
|
||||
}
|
||||
try {
|
||||
$mariadbs = StandaloneMariadb::withTrashed()->whereNotNull('deleted_at')->get();
|
||||
foreach ($mariadbs as $mariadb) {
|
||||
echo "Deleting stucked mariadb: {$mariadb->name}\n";
|
||||
$mariadb->forceDelete();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in cleaning stucked mariadb: {$e->getMessage()}\n";
|
||||
}
|
||||
try {
|
||||
$services = Service::withTrashed()->whereNotNull('deleted_at')->get();
|
||||
foreach ($services as $service) {
|
||||
echo "Deleting stucked service: {$service->name}\n";
|
||||
$service->forceDelete();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in cleaning stucked service: {$e->getMessage()}\n";
|
||||
}
|
||||
try {
|
||||
$serviceApps = ServiceApplication::withTrashed()->whereNotNull('deleted_at')->get();
|
||||
foreach ($serviceApps as $serviceApp) {
|
||||
echo "Deleting stucked serviceapp: {$serviceApp->name}\n";
|
||||
$serviceApp->forceDelete();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in cleaning stucked serviceapp: {$e->getMessage()}\n";
|
||||
}
|
||||
try {
|
||||
$serviceDbs = ServiceDatabase::withTrashed()->whereNotNull('deleted_at')->get();
|
||||
foreach ($serviceDbs as $serviceDb) {
|
||||
echo "Deleting stucked serviceapp: {$serviceDb->name}\n";
|
||||
$serviceDb->forceDelete();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in cleaning stucked serviceapp: {$e->getMessage()}\n";
|
||||
}
|
||||
|
||||
// Cleanup any resources that are not attached to any environment or destination or server
|
||||
try {
|
||||
$applications = Application::all();
|
||||
@@ -134,7 +245,7 @@ class Init extends Command
|
||||
echo "Error in application: {$e->getMessage()}\n";
|
||||
}
|
||||
try {
|
||||
$postgresqls = StandalonePostgresql::all();
|
||||
$postgresqls = StandalonePostgresql::all()->where('id', '!=', 0);
|
||||
foreach ($postgresqls as $postgresql) {
|
||||
if (!data_get($postgresql, 'environment')) {
|
||||
echo 'Postgresql without environment: ' . $postgresql->name . ' soft deleting\n';
|
||||
|
||||
@@ -56,16 +56,20 @@ class Kernel extends ConsoleKernel
|
||||
$servers = Server::all()->whereNotNull('team.subscription')->where('team.subscription.stripe_trial_already_ended', false)->where('ip', '!=', '1.2.3.4');
|
||||
$own = Team::find(0)->servers;
|
||||
$servers = $servers->merge($own);
|
||||
$containerServers = $servers->where('settings.is_swarm_worker', false);
|
||||
} else {
|
||||
$servers = Server::all()->where('ip', '!=', '1.2.3.4');
|
||||
$containerServers = $servers->where('settings.is_swarm_worker', false);
|
||||
}
|
||||
foreach ($servers as $server) {
|
||||
$schedule->job(new ServerStatusJob($server))->everyTenMinutes()->onOneServer();
|
||||
foreach ($containerServers as $server) {
|
||||
$schedule->job(new ContainerStatusJob($server))->everyMinute()->onOneServer();
|
||||
if ($server->isLogDrainEnabled()) {
|
||||
$schedule->job(new CheckLogDrainContainerJob($server))->everyMinute()->onOneServer();
|
||||
}
|
||||
}
|
||||
foreach ($servers as $server) {
|
||||
$schedule->job(new ServerStatusJob($server))->everyTenMinutes()->onOneServer();
|
||||
}
|
||||
}
|
||||
private function instance_auto_update($schedule)
|
||||
{
|
||||
|
||||
@@ -47,7 +47,7 @@ class Handler extends ExceptionHandler
|
||||
if ($request->is('api/*') || $request->expectsJson() || $this->shouldReturnJson($request, $exception)) {
|
||||
return response()->json(['message' => $exception->getMessage()], 401);
|
||||
}
|
||||
return redirect()->guest($exception->redirectTo() ?? route('login'));
|
||||
return redirect()->guest($exception->redirectTo() ?? route('login'));
|
||||
}
|
||||
/**
|
||||
* Register the exception handling callbacks for the application.
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Providers\RouteServiceProvider;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
@@ -16,7 +17,7 @@ class DecideWhatToDoWithUser
|
||||
}
|
||||
if (!auth()->user() || !isCloud() || isInstanceAdmin()) {
|
||||
if (!isCloud() && showBoarding() && !in_array($request->path(), allowedPathsForBoardingAccounts())) {
|
||||
return redirect('boarding');
|
||||
return redirect()->route('boarding');
|
||||
}
|
||||
return $next($request);
|
||||
}
|
||||
@@ -24,27 +25,27 @@ class DecideWhatToDoWithUser
|
||||
if ($request->path() === 'verify' || in_array($request->path(), allowedPathsForInvalidAccounts()) || $request->routeIs('verify.verify')) {
|
||||
return $next($request);
|
||||
}
|
||||
return redirect('/verify');
|
||||
return redirect()->route('verify.email');
|
||||
}
|
||||
if (!isSubscriptionActive() && !isSubscriptionOnGracePeriod()) {
|
||||
if (!in_array($request->path(), allowedPathsForUnsubscribedAccounts())) {
|
||||
if (Str::startsWith($request->path(), 'invitations')) {
|
||||
return $next($request);
|
||||
}
|
||||
return redirect('subscription');
|
||||
return redirect()->route('subscription.index');
|
||||
}
|
||||
}
|
||||
if (showBoarding() && !in_array($request->path(), allowedPathsForBoardingAccounts())) {
|
||||
if (Str::startsWith($request->path(), 'invitations')) {
|
||||
return $next($request);
|
||||
}
|
||||
return redirect('boarding');
|
||||
return redirect()->route('boarding');
|
||||
}
|
||||
if (auth()->user()->hasVerifiedEmail() && $request->path() === 'verify') {
|
||||
return redirect('/');
|
||||
return redirect(RouteServiceProvider::HOME);
|
||||
}
|
||||
if (isSubscriptionActive() && $request->path() === 'subscription') {
|
||||
return redirect('/');
|
||||
return redirect(RouteServiceProvider::HOME);
|
||||
}
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
@@ -75,10 +75,13 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
private $docker_compose_base64;
|
||||
private string $dockerfile_location = '/Dockerfile';
|
||||
private string $docker_compose_location = '/docker-compose.yml';
|
||||
private ?string $docker_compose_custom_start_command = null;
|
||||
private ?string $docker_compose_custom_build_command = null;
|
||||
private ?string $addHosts = null;
|
||||
private ?string $buildTarget = null;
|
||||
private Collection $saved_outputs;
|
||||
private ?string $full_healthcheck_url = null;
|
||||
private ?string $nixpacks_command = null;
|
||||
|
||||
private string $serverUser = 'root';
|
||||
private string $serverUserHomeDir = '/root';
|
||||
@@ -215,19 +218,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
if ($this->server->isProxyShouldRun()) {
|
||||
dispatch(new ContainerStatusJob($this->server));
|
||||
}
|
||||
if ($this->application->docker_registry_image_name && $this->application->build_pack !== 'dockerimage') {
|
||||
if ($this->application->docker_registry_image_name && $this->application->build_pack !== 'dockerimage' && !$this->application->destination->server->isSwarm()) {
|
||||
$this->push_to_docker_registry();
|
||||
if ($this->server->isSwarm()) {
|
||||
$this->application_deployment_queue->addLogEntry("Creating / updating stack.");
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
executeInDocker($this->deployment_uuid, "cd {$this->workdir} && docker stack deploy --with-registry-auth -c docker-compose.yml {$this->application->uuid}")
|
||||
],
|
||||
[
|
||||
"echo 'Stack deployed. It may take a few minutes to fully available in your swarm.'"
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
$this->next(ApplicationDeploymentStatus::FINISHED->value);
|
||||
$this->application->isConfigurationChanged(true);
|
||||
@@ -299,6 +291,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
"echo -n 'Image pushed to docker registry.'"
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
if ($this->application->destination->server->isSwarm()) {
|
||||
throw $e;
|
||||
}
|
||||
$this->execute_remote_command(
|
||||
["echo -n 'Failed to push image to docker registry. Please check debug logs for more information.'"],
|
||||
);
|
||||
@@ -432,6 +427,12 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
if (data_get($this->application, 'docker_compose_location')) {
|
||||
$this->docker_compose_location = $this->application->docker_compose_location;
|
||||
}
|
||||
if (data_get($this->application, 'docker_compose_custom_start_command')) {
|
||||
$this->docker_compose_custom_start_command = $this->application->docker_compose_custom_start_command;
|
||||
}
|
||||
if (data_get($this->application, 'docker_compose_custom_build_command')) {
|
||||
$this->docker_compose_custom_build_command = $this->application->docker_compose_custom_build_command;
|
||||
}
|
||||
if ($this->pull_request_id === 0) {
|
||||
$this->application_deployment_queue->addLogEntry("Starting deployment of {$this->application->name}.");
|
||||
} else {
|
||||
@@ -446,15 +447,30 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$this->generate_image_names();
|
||||
$this->cleanup_git();
|
||||
$this->application->loadComposeFile(isInit: false);
|
||||
$composeFile = $this->application->parseCompose(pull_request_id: $this->pull_request_id);
|
||||
$yaml = Yaml::dump($composeFile->toArray(), 10);
|
||||
if ($this->application->settings->is_raw_compose_deployment_enabled) {
|
||||
$yaml = $composeFile = $this->application->docker_compose_raw;
|
||||
} else {
|
||||
$composeFile = $this->application->parseCompose(pull_request_id: $this->pull_request_id);
|
||||
$yaml = Yaml::dump($composeFile->toArray(), 10);
|
||||
}
|
||||
$this->docker_compose_base64 = base64_encode($yaml);
|
||||
$this->execute_remote_command([
|
||||
executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d > {$this->workdir}{$this->docker_compose_location}"), "hidden" => true
|
||||
]);
|
||||
$this->save_environment_variables();
|
||||
// Build new container to limit downtime.
|
||||
$this->build_by_compose_file();
|
||||
$this->application_deployment_queue->addLogEntry("Pulling & building required images.");
|
||||
|
||||
if ($this->docker_compose_custom_build_command) {
|
||||
$this->execute_remote_command(
|
||||
[executeInDocker($this->deployment_uuid, "cd {$this->basedir} && {$this->docker_compose_custom_build_command}"), "hidden" => true],
|
||||
);
|
||||
} else {
|
||||
$this->execute_remote_command(
|
||||
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} build"), "hidden" => true],
|
||||
);
|
||||
}
|
||||
|
||||
$this->stop_running_container(force: true);
|
||||
|
||||
$networkId = $this->application->uuid;
|
||||
@@ -488,7 +504,17 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
]
|
||||
);
|
||||
}
|
||||
$this->start_by_compose_file();
|
||||
// Start compose file
|
||||
if ($this->docker_compose_custom_start_command) {
|
||||
$this->execute_remote_command(
|
||||
[executeInDocker($this->deployment_uuid, "cd {$this->basedir} && {$this->docker_compose_custom_start_command}"), "hidden" => true],
|
||||
);
|
||||
} else {
|
||||
$this->execute_remote_command(
|
||||
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up -d"), "hidden" => true],
|
||||
);
|
||||
}
|
||||
$this->application_deployment_queue->addLogEntry("New container started.");
|
||||
}
|
||||
private function deploy_dockerfile_buildpack()
|
||||
{
|
||||
@@ -575,7 +601,16 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
private function rolling_update()
|
||||
{
|
||||
if ($this->server->isSwarm()) {
|
||||
// Skip this.
|
||||
if ($this->build_pack !== 'dockerimage') {
|
||||
$this->push_to_docker_registry();
|
||||
}
|
||||
$this->application_deployment_queue->addLogEntry("Rolling update started.");
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
executeInDocker($this->deployment_uuid, "docker stack deploy --with-registry-auth -c {$this->workdir}{$this->docker_compose_location} {$this->application->uuid}")
|
||||
],
|
||||
);
|
||||
$this->application_deployment_queue->addLogEntry("Rolling update completed.");
|
||||
} else {
|
||||
if (count($this->application->ports_mappings_array) > 0) {
|
||||
$this->execute_remote_command(
|
||||
@@ -674,10 +709,20 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$this->add_build_env_variables_to_dockerfile();
|
||||
$this->build_image();
|
||||
$this->stop_running_container();
|
||||
$this->execute_remote_command(
|
||||
["echo -n 'Starting preview deployment.'"],
|
||||
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up -d"), "hidden" => true],
|
||||
);
|
||||
if ($this->application->destination->server->isSwarm()) {
|
||||
ray("{$this->workdir}{$this->docker_compose_location}");
|
||||
$this->push_to_docker_registry();
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
executeInDocker($this->deployment_uuid, "docker stack deploy --with-registry-auth -c {$this->workdir}{$this->docker_compose_location} {$this->application->uuid}-{$this->pull_request_id}")
|
||||
],
|
||||
);
|
||||
} else {
|
||||
$this->execute_remote_command(
|
||||
["echo -n 'Starting preview deployment.'"],
|
||||
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up -d"), "hidden" => true],
|
||||
);
|
||||
}
|
||||
}
|
||||
private function create_workdir()
|
||||
{
|
||||
@@ -835,7 +880,11 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
if ($this->pull_request_id !== 0) {
|
||||
$cacheKey = "{$this->application->uuid}-pr-{$this->pull_request_id}";
|
||||
}
|
||||
$nixpacks_command = "nixpacks build --cache-key '{$cacheKey}' -o {$this->workdir} {$this->env_args} --no-error-without-start";
|
||||
if ($this->force_rebuild) {
|
||||
$nixpacks_command = "nixpacks build --no-cache {$this->env_args} --no-error-without-start";
|
||||
} else {
|
||||
$nixpacks_command = "nixpacks build --cache-key '{$cacheKey}' {$this->env_args} --no-error-without-start";
|
||||
}
|
||||
if ($this->application->build_command) {
|
||||
$nixpacks_command .= " --build-cmd \"{$this->application->build_command}\"";
|
||||
}
|
||||
@@ -845,7 +894,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
if ($this->application->install_command) {
|
||||
$nixpacks_command .= " --install-cmd \"{$this->application->install_command}\"";
|
||||
}
|
||||
$nixpacks_command .= " {$this->workdir}";
|
||||
$this->nixpacks_command = $nixpacks_command;
|
||||
$nixpacks_command .= " -o {$this->workdir} {$this->workdir}";
|
||||
return $nixpacks_command;
|
||||
}
|
||||
|
||||
@@ -914,7 +964,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
'memswap_limit' => $this->application->limits_memory_swap,
|
||||
'mem_swappiness' => $this->application->limits_memory_swappiness,
|
||||
'mem_reservation' => $this->application->limits_memory_reservation,
|
||||
'cpus' => (int) $this->application->limits_cpus,
|
||||
'cpus' => (float) $this->application->limits_cpus,
|
||||
'cpuset' => $this->application->limits_cpuset,
|
||||
'cpu_shares' => $this->application->limits_cpu_shares,
|
||||
]
|
||||
@@ -941,13 +991,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
data_forget($docker_compose, 'services.' . $this->container_name . '.cpu_shares');
|
||||
|
||||
$docker_compose['services'][$this->container_name]['deploy'] = [
|
||||
'placement' => [
|
||||
'constraints' => [
|
||||
'node.role == worker'
|
||||
]
|
||||
],
|
||||
'mode' => 'replicated',
|
||||
'replicas' => 1,
|
||||
'replicas' => data_get($this->application, 'swarm_replicas', 1),
|
||||
'update_config' => [
|
||||
'order' => 'start-first'
|
||||
],
|
||||
@@ -966,6 +1011,16 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
]
|
||||
]
|
||||
];
|
||||
if (data_get($this->application, 'settings.is_swarm_only_worker_nodes')) {
|
||||
$docker_compose['services'][$this->container_name]['deploy']['placement'] = [
|
||||
'constraints' => [
|
||||
'node.role == worker'
|
||||
]
|
||||
];
|
||||
}
|
||||
if ($this->pull_request_id !== 0) {
|
||||
$docker_compose['services'][$this->container_name]['deploy']['replicas'] = 1;
|
||||
}
|
||||
} else {
|
||||
$docker_compose['services'][$this->container_name]['labels'] = $labels;
|
||||
}
|
||||
@@ -1085,6 +1140,11 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('PORT'))->isEmpty()) {
|
||||
$environment_variables->push("PORT={$ports[0]}");
|
||||
}
|
||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('SOURCE_COMMIT'))->isEmpty()) {
|
||||
if (!is_null($this->commit)) {
|
||||
$environment_variables->push("SOURCE_COMMIT={$this->commit}");
|
||||
}
|
||||
}
|
||||
return $environment_variables->all();
|
||||
}
|
||||
|
||||
@@ -1143,6 +1203,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
if ($this->application->settings->is_static || $this->application->build_pack === 'static') {
|
||||
if ($this->application->static_image) {
|
||||
$this->pull_latest_image($this->application->static_image);
|
||||
$this->execute_remote_command(
|
||||
["echo -n 'Continue with the building process.'"],
|
||||
);
|
||||
}
|
||||
if ($this->application->build_pack === 'static') {
|
||||
$dockerfile = base64_encode("FROM {$this->application->static_image}
|
||||
@@ -1169,9 +1232,26 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
}
|
||||
}");
|
||||
} else {
|
||||
$this->execute_remote_command([
|
||||
executeInDocker($this->deployment_uuid, "docker build $this->buildTarget $this->addHosts --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t $this->build_image_name {$this->workdir}"), "hidden" => true
|
||||
]);
|
||||
if ($this->application->build_pack === 'nixpacks') {
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
executeInDocker($this->deployment_uuid, "mv {$this->workdir}/Dockerfile {$this->workdir}/.nixpacks/Dockerfile")
|
||||
],
|
||||
[
|
||||
executeInDocker($this->deployment_uuid, $this->nixpacks_command . " --name $this->build_image_name {$this->workdir}"), "hidden" => true
|
||||
]
|
||||
);
|
||||
} else {
|
||||
if ($this->force_rebuild) {
|
||||
$this->execute_remote_command([
|
||||
executeInDocker($this->deployment_uuid, "docker build --no-cache $this->buildTarget $this->addHosts --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t $this->build_image_name {$this->workdir}"), "hidden" => true
|
||||
]);
|
||||
} else {
|
||||
$this->execute_remote_command([
|
||||
executeInDocker($this->deployment_uuid, "docker build $this->buildTarget $this->addHosts --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t $this->build_image_name {$this->workdir}"), "hidden" => true
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
$dockerfile = base64_encode("FROM {$this->application->static_image}
|
||||
WORKDIR /usr/share/nginx/html/
|
||||
@@ -1214,9 +1294,27 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
executeInDocker($this->deployment_uuid, "docker build --pull $this->buildTarget $this->addHosts --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}"), "hidden" => true
|
||||
]);
|
||||
} else {
|
||||
$this->execute_remote_command([
|
||||
executeInDocker($this->deployment_uuid, "docker build $this->buildTarget $this->addHosts --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}"), "hidden" => true
|
||||
]);
|
||||
|
||||
if ($this->application->build_pack === 'nixpacks') {
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
executeInDocker($this->deployment_uuid, "mv {$this->workdir}/Dockerfile {$this->workdir}/.nixpacks/Dockerfile")
|
||||
],
|
||||
[
|
||||
executeInDocker($this->deployment_uuid, $this->nixpacks_command . " --name {$this->production_image_name} {$this->workdir}"), "hidden" => true
|
||||
]
|
||||
);
|
||||
} else {
|
||||
if ($this->force_rebuild) {
|
||||
$this->execute_remote_command([
|
||||
executeInDocker($this->deployment_uuid, "docker build --no-cache $this->buildTarget $this->addHosts --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}"), "hidden" => true
|
||||
]);
|
||||
} else {
|
||||
$this->execute_remote_command([
|
||||
executeInDocker($this->deployment_uuid, "docker build $this->buildTarget $this->addHosts --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}"), "hidden" => true
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->execute_remote_command([
|
||||
@@ -1242,6 +1340,9 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
});
|
||||
} else {
|
||||
$this->application_deployment_queue->addLogEntry("New container is not healthy, rolling back to the old container.");
|
||||
$this->application_deployment_queue->update([
|
||||
'status' => ApplicationDeploymentStatus::FAILED->value,
|
||||
]);
|
||||
$this->execute_remote_command(
|
||||
[executeInDocker($this->deployment_uuid, "docker rm -f $this->container_name >/dev/null 2>&1"), "hidden" => true, "ignore_errors" => true],
|
||||
);
|
||||
@@ -1258,15 +1359,9 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} build"), "hidden" => true],
|
||||
);
|
||||
} else {
|
||||
if ($this->docker_compose_location) {
|
||||
$this->execute_remote_command(
|
||||
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} build"), "hidden" => true],
|
||||
);
|
||||
} else {
|
||||
$this->execute_remote_command(
|
||||
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} build"), "hidden" => true],
|
||||
);
|
||||
}
|
||||
$this->execute_remote_command(
|
||||
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} build"), "hidden" => true],
|
||||
);
|
||||
}
|
||||
$this->application_deployment_queue->addLogEntry("New images built.");
|
||||
}
|
||||
|
||||
@@ -17,20 +17,24 @@ use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Sleep;
|
||||
|
||||
class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public $tries = 4;
|
||||
public function backoff(): int
|
||||
{
|
||||
return isDev() ? 1 : 3;
|
||||
}
|
||||
public function middleware(): array
|
||||
{
|
||||
return [(new WithoutOverlapping($this->server->id))->dontRelease()];
|
||||
return [(new WithoutOverlapping($this->server->uuid))];
|
||||
}
|
||||
|
||||
public function uniqueId(): int
|
||||
{
|
||||
return $this->server->id;
|
||||
return $this->server->uuid;
|
||||
}
|
||||
|
||||
public function __construct(public Server $server)
|
||||
@@ -40,13 +44,13 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
||||
|
||||
public function handle()
|
||||
{
|
||||
if (!$this->server->isServerReady($this->tries)) {
|
||||
return 'Server is not reachable.';
|
||||
};
|
||||
try {
|
||||
if (!$this->server->isServerReady()) {
|
||||
return;
|
||||
};
|
||||
if ($this->server->isSwarm()) {
|
||||
$containers = instant_remote_process(["docker service inspect $(docker service ls -q) --format '{{json .}}'"], $this->server, false);
|
||||
$containerReplicase = instant_remote_process(["docker service ls --format '{{json .}}'"], $this->server, false);
|
||||
$containerReplicates = instant_remote_process(["docker service ls --format '{{json .}}'"], $this->server, false);
|
||||
} else {
|
||||
// Precheck for containers
|
||||
$containers = instant_remote_process(["docker container ls -q"], $this->server, false);
|
||||
@@ -54,15 +58,15 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
||||
return;
|
||||
}
|
||||
$containers = instant_remote_process(["docker container inspect $(docker container ls -q) --format '{{json .}}'"], $this->server, false);
|
||||
$containerReplicase = null;
|
||||
$containerReplicates = null;
|
||||
}
|
||||
if (is_null($containers)) {
|
||||
return;
|
||||
}
|
||||
$containers = format_docker_command_output_to_json($containers);
|
||||
if ($containerReplicase) {
|
||||
$containerReplicase = format_docker_command_output_to_json($containerReplicase);
|
||||
foreach ($containerReplicase as $containerReplica) {
|
||||
if ($containerReplicates) {
|
||||
$containerReplicates = format_docker_command_output_to_json($containerReplicates);
|
||||
foreach ($containerReplicates as $containerReplica) {
|
||||
$name = data_get($containerReplica, 'Name');
|
||||
$containers = $containers->map(function ($container) use ($name, $containerReplica) {
|
||||
if (data_get($container, 'Spec.Name') === $name) {
|
||||
|
||||
@@ -17,22 +17,26 @@ class ServerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public ?int $disk_usage = null;
|
||||
public $tries = 4;
|
||||
public function backoff(): int
|
||||
{
|
||||
return isDev() ? 1 : 3;
|
||||
}
|
||||
public function __construct(public Server $server)
|
||||
{
|
||||
}
|
||||
public function middleware(): array
|
||||
{
|
||||
return [(new WithoutOverlapping($this->server->id))->dontRelease()];
|
||||
return [(new WithoutOverlapping($this->server->uuid))];
|
||||
}
|
||||
|
||||
public function uniqueId(): int
|
||||
{
|
||||
return $this->server->id;
|
||||
return $this->server->uuid;
|
||||
}
|
||||
|
||||
public function handle(): void
|
||||
public function handle()
|
||||
{
|
||||
ray("checking server status for {$this->server->id}");
|
||||
try {
|
||||
if ($this->server->isFunctional()) {
|
||||
$this->cleanup(notify: false);
|
||||
@@ -40,7 +44,7 @@ class ServerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
||||
} catch (\Throwable $e) {
|
||||
send_internal_notification('ServerStatusJob failed with: ' . $e->getMessage());
|
||||
ray($e->getMessage());
|
||||
handleError($e);
|
||||
return handleError($e);
|
||||
}
|
||||
}
|
||||
public function cleanup(bool $notify = false): void
|
||||
|
||||
@@ -37,7 +37,7 @@ class CheckLicense extends Component
|
||||
} catch (\Throwable $e) {
|
||||
session()->flash('error', 'Something went wrong. Please contact support. <br>Error: ' . $e->getMessage());
|
||||
ray($e->getMessage());
|
||||
return $this->redirect('/settings/license', navigate: true);
|
||||
return redirect()->route('settings.license');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ class Form extends Component
|
||||
instant_remote_process(['docker network rm -f ' . $this->destination->network], $this->destination->server);
|
||||
}
|
||||
$this->destination->delete();
|
||||
return $this->redirectRoute('dashboard', navigate: true);
|
||||
return redirect()->route('dashboard');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
||||
@@ -4,29 +4,32 @@ namespace App\Livewire\Destination\New;
|
||||
|
||||
use App\Models\Server;
|
||||
use App\Models\StandaloneDocker as ModelsStandaloneDocker;
|
||||
use App\Models\SwarmDocker;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Support\Str;
|
||||
use Livewire\Component;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
|
||||
class StandaloneDocker extends Component
|
||||
class Docker extends Component
|
||||
{
|
||||
public string $name;
|
||||
public string $network;
|
||||
|
||||
public Collection $servers;
|
||||
public Server $server;
|
||||
public int|null $server_id = null;
|
||||
public ?int $server_id = null;
|
||||
public bool $is_swarm = false;
|
||||
|
||||
protected $rules = [
|
||||
'name' => 'required|string',
|
||||
'network' => 'required|string',
|
||||
'server_id' => 'required|integer'
|
||||
'server_id' => 'required|integer',
|
||||
'is_swarm' => 'boolean'
|
||||
];
|
||||
protected $validationAttributes = [
|
||||
'name' => 'name',
|
||||
'network' => 'network',
|
||||
'server_id' => 'server'
|
||||
'server_id' => 'server',
|
||||
'is_swarm' => 'swarm'
|
||||
];
|
||||
|
||||
public function mount()
|
||||
@@ -43,13 +46,13 @@ class StandaloneDocker extends Component
|
||||
} else {
|
||||
$this->network = new Cuid2(7);
|
||||
}
|
||||
$this->name = Str::kebab("{$this->servers->first()->name}-{$this->network}");
|
||||
$this->name = str("{$this->servers->first()->name}-{$this->network}")->kebab();
|
||||
}
|
||||
|
||||
public function generate_name()
|
||||
{
|
||||
$this->server = Server::find($this->server_id);
|
||||
$this->name = Str::kebab("{$this->server->name}-{$this->network}");
|
||||
$this->name = str("{$this->server->name}-{$this->network}")->kebab();
|
||||
}
|
||||
|
||||
public function submit()
|
||||
@@ -57,20 +60,33 @@ class StandaloneDocker extends Component
|
||||
$this->validate();
|
||||
try {
|
||||
$this->server = Server::find($this->server_id);
|
||||
$found = $this->server->standaloneDockers()->where('network', $this->network)->first();
|
||||
if ($found) {
|
||||
$this->createNetworkAndAttachToProxy();
|
||||
$this->dispatch('error', 'Network already added to this server.');
|
||||
return;
|
||||
if ($this->is_swarm) {
|
||||
$found = $this->server->swarmDockers()->where('network', $this->network)->first();
|
||||
if ($found) {
|
||||
$this->dispatch('error', 'Network already added to this server.');
|
||||
return;
|
||||
} else {
|
||||
$docker = SwarmDocker::create([
|
||||
'name' => $this->name,
|
||||
'network' => $this->network,
|
||||
'server_id' => $this->server_id,
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
$docker = ModelsStandaloneDocker::create([
|
||||
'name' => $this->name,
|
||||
'network' => $this->network,
|
||||
'server_id' => $this->server_id,
|
||||
]);
|
||||
$found = $this->server->standaloneDockers()->where('network', $this->network)->first();
|
||||
if ($found) {
|
||||
$this->dispatch('error', 'Network already added to this server.');
|
||||
return;
|
||||
} else {
|
||||
$docker = ModelsStandaloneDocker::create([
|
||||
'name' => $this->name,
|
||||
'network' => $this->network,
|
||||
'server_id' => $this->server_id,
|
||||
]);
|
||||
}
|
||||
}
|
||||
$this->createNetworkAndAttachToProxy();
|
||||
return $this->redirectRoute('destination.show', $docker->uuid, navigate: true);
|
||||
return redirect()->route('destination.show', $docker->uuid);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
@@ -13,7 +13,11 @@ class Show extends Component
|
||||
|
||||
public function scan()
|
||||
{
|
||||
$alreadyAddedNetworks = $this->server->standaloneDockers;
|
||||
if ($this->server->isSwarm()) {
|
||||
$alreadyAddedNetworks = $this->server->swarmDockers;
|
||||
} else {
|
||||
$alreadyAddedNetworks = $this->server->standaloneDockers;
|
||||
}
|
||||
$networks = instant_remote_process(['docker network ls --format "{{json .}}"'], $this->server, false);
|
||||
$this->networks = format_docker_command_output_to_json($networks)->filter(function ($network) {
|
||||
return $network['Name'] !== 'bridge' && $network['Name'] !== 'host' && $network['Name'] !== 'none';
|
||||
|
||||
@@ -35,7 +35,7 @@ class ForcePasswordReset extends Component
|
||||
if ($firstLogin) {
|
||||
send_internal_notification('First login for ' . auth()->user()->email);
|
||||
}
|
||||
return $this->redirectRoute('dashboard', navigate: true);
|
||||
return redirect()->route('dashboard');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ class Change extends Component
|
||||
if ($this->private_key->isEmpty()) {
|
||||
$this->private_key->delete();
|
||||
currentTeam()->privateKeys = PrivateKey::where('team_id', currentTeam()->id)->get();
|
||||
return $this->redirectRoute('security.private-key.index', navigate: true);
|
||||
return redirect()->route('security.private-key.index');
|
||||
}
|
||||
$this->dispatch('error', 'This private key is in use and cannot be deleted. Please delete all servers, applications, and GitHub/GitLab apps that use this private key before deleting it.');
|
||||
} catch (\Throwable $e) {
|
||||
|
||||
@@ -67,9 +67,9 @@ class Create extends Component
|
||||
'team_id' => currentTeam()->id
|
||||
]);
|
||||
if ($this->from === 'server') {
|
||||
return $this->redirectRoute('server.create', navigate: true);
|
||||
return redirect()->route('server.create');
|
||||
}
|
||||
return $this->redirectRoute('security.private-key.show', ['private_key_uuid' => $private_key->uuid], navigate: true);
|
||||
return redirect()->route('security.private-key.show', ['private_key_uuid' => $private_key->uuid]);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ class AddEmpty extends Component
|
||||
'description' => $this->description,
|
||||
'team_id' => currentTeam()->id,
|
||||
]);
|
||||
return $this->redirectRoute('project.show', $project->uuid, navigate: true);
|
||||
return redirect()->route('project.show', $project->uuid);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
} finally {
|
||||
|
||||
@@ -27,10 +27,10 @@ class AddEnvironment extends Component
|
||||
'project_id' => $this->project->id,
|
||||
]);
|
||||
|
||||
return $this->redirectRoute('project.resources', [
|
||||
return redirect()->route('project.resources', [
|
||||
'project_uuid' => $this->project->uuid,
|
||||
'environment_name' => $environment->name,
|
||||
], navigate: true);
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
handleError($e, $this);
|
||||
} finally {
|
||||
|
||||
@@ -15,15 +15,15 @@ class Configuration extends Component
|
||||
{
|
||||
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
|
||||
if (!$project) {
|
||||
return $this->redirectRoute('dashboard', navigate: true);
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
$environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first()->load(['applications']);
|
||||
if (!$environment) {
|
||||
return $this->redirectRoute('dashboard', navigate: true);
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
$application = $environment->applications->where('uuid', request()->route('application_uuid'))->first();
|
||||
if (!$application) {
|
||||
return $this->redirectRoute('dashboard', navigate: true);
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
$this->application = $application;
|
||||
$mainServer = $this->application->destination->server;
|
||||
|
||||
@@ -64,6 +64,9 @@ class General extends Component
|
||||
'application.custom_labels' => 'nullable',
|
||||
'application.dockerfile_target_build' => 'nullable',
|
||||
'application.settings.is_static' => 'boolean|required',
|
||||
'application.docker_compose_custom_start_command' => 'nullable',
|
||||
'application.docker_compose_custom_build_command' => 'nullable',
|
||||
'application.settings.is_raw_compose_deployment_enabled' => 'boolean|required',
|
||||
];
|
||||
protected $validationAttributes = [
|
||||
'application.name' => 'name',
|
||||
@@ -94,6 +97,9 @@ class General extends Component
|
||||
'application.custom_labels' => 'Custom labels',
|
||||
'application.dockerfile_target_build' => 'Dockerfile target build',
|
||||
'application.settings.is_static' => 'Is static',
|
||||
'application.docker_compose_custom_start_command' => 'Docker compose custom start command',
|
||||
'application.docker_compose_custom_build_command' => 'Docker compose custom build command',
|
||||
'application.settings.is_raw_compose_deployment_enabled' => 'Is raw compose deployment enabled',
|
||||
];
|
||||
public function mount()
|
||||
{
|
||||
@@ -109,7 +115,12 @@ class General extends Component
|
||||
$this->application->isConfigurationChanged(true);
|
||||
}
|
||||
$this->isConfigurationChanged = $this->application->isConfigurationChanged();
|
||||
$this->customLabels = $this->application->parseContainerLabels();
|
||||
$this->customLabels = $this->application->parseContainerLabels();
|
||||
if (!$this->customLabels && $this->application->destination->server->proxyType() === 'TRAEFIK_V2') {
|
||||
$this->customLabels = str(implode(",", generateLabelsApplication($this->application)))->replace(',', "\n");
|
||||
$this->application->custom_labels = base64_encode($this->customLabels);
|
||||
$this->application->save();
|
||||
}
|
||||
$this->initialDockerComposeLocation = $this->application->docker_compose_location;
|
||||
$this->checkLabelUpdates();
|
||||
}
|
||||
@@ -195,13 +206,20 @@ class General extends Component
|
||||
public function submit($showToaster = true)
|
||||
{
|
||||
try {
|
||||
if ($this->application->build_pack === 'dockercompose' && ($this->initialDockerComposeLocation !== $this->application->docker_compose_location || $this->initialDockerComposePrLocation !== $this->application->docker_compose_pr_location)) {
|
||||
if (!$this->customLabels && $this->application->destination->server->proxyType() === 'TRAEFIK_V2') {
|
||||
$this->customLabels = str(implode(",", generateLabelsApplication($this->application)))->replace(',', "\n");
|
||||
$this->application->custom_labels = base64_encode($this->customLabels);
|
||||
$this->application->save();
|
||||
}
|
||||
|
||||
if ($this->application->build_pack === 'dockercompose' && $this->initialDockerComposeLocation !== $this->application->docker_compose_location) {
|
||||
$this->loadComposeFile();
|
||||
}
|
||||
$this->validate();
|
||||
if ($this->ports_exposes !== $this->application->ports_exposes) {
|
||||
$this->resetDefaultLabels(false);
|
||||
}
|
||||
|
||||
if (data_get($this->application, 'build_pack') === 'dockerimage') {
|
||||
$this->validate([
|
||||
'application.docker_registry_image_name' => 'required',
|
||||
|
||||
@@ -60,12 +60,12 @@ class Heading extends Component
|
||||
force_rebuild: false,
|
||||
is_new_deployment: true,
|
||||
);
|
||||
return $this->redirectRoute('project.application.deployment', [
|
||||
return redirect()->route('project.application.deployment', [
|
||||
'project_uuid' => $this->parameters['project_uuid'],
|
||||
'application_uuid' => $this->parameters['application_uuid'],
|
||||
'deployment_uuid' => $this->deploymentUuid,
|
||||
'environment_name' => $this->parameters['environment_name'],
|
||||
], navigate: true);
|
||||
]);
|
||||
}
|
||||
public function deploy(bool $force_rebuild = false)
|
||||
{
|
||||
@@ -73,18 +73,22 @@ class Heading extends Component
|
||||
$this->dispatch('error', 'Please load a Compose file first.');
|
||||
return;
|
||||
}
|
||||
if ($this->application->destination->server->isSwarm() && is_null($this->application->docker_registry_image_name)) {
|
||||
$this->dispatch('error', 'Please set a Docker image name first.');
|
||||
return;
|
||||
}
|
||||
$this->setDeploymentUuid();
|
||||
queue_application_deployment(
|
||||
application_id: $this->application->id,
|
||||
deployment_uuid: $this->deploymentUuid,
|
||||
force_rebuild: $force_rebuild,
|
||||
);
|
||||
$this->redirectRoute('project.application.deployment', [
|
||||
return redirect()->route('project.application.deployment', [
|
||||
'project_uuid' => $this->parameters['project_uuid'],
|
||||
'application_uuid' => $this->parameters['application_uuid'],
|
||||
'deployment_uuid' => $this->deploymentUuid,
|
||||
'environment_name' => $this->parameters['environment_name'],
|
||||
], navigate: true);
|
||||
]);
|
||||
}
|
||||
|
||||
protected function setDeploymentUuid()
|
||||
@@ -109,12 +113,12 @@ class Heading extends Component
|
||||
restart_only: true,
|
||||
is_new_deployment: true,
|
||||
);
|
||||
return $this->redirectRoute('project.application.deployment', [
|
||||
return redirect()->route('project.application.deployment', [
|
||||
'project_uuid' => $this->parameters['project_uuid'],
|
||||
'application_uuid' => $this->parameters['application_uuid'],
|
||||
'deployment_uuid' => $this->deploymentUuid,
|
||||
'environment_name' => $this->parameters['environment_name'],
|
||||
], navigate: true);
|
||||
]);
|
||||
}
|
||||
public function restart()
|
||||
{
|
||||
@@ -124,11 +128,11 @@ class Heading extends Component
|
||||
deployment_uuid: $this->deploymentUuid,
|
||||
restart_only: true,
|
||||
);
|
||||
return $this->redirectRoute('project.application.deployment', [
|
||||
return redirect()->route('project.application.deployment', [
|
||||
'project_uuid' => $this->parameters['project_uuid'],
|
||||
'application_uuid' => $this->parameters['application_uuid'],
|
||||
'deployment_uuid' => $this->deploymentUuid,
|
||||
'environment_name' => $this->parameters['environment_name'],
|
||||
], navigate: true);
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,12 +52,12 @@ class Previews extends Component
|
||||
force_rebuild: true,
|
||||
pull_request_id: $pull_request_id,
|
||||
);
|
||||
return $this->redirectRoute('project.application.deployment', [
|
||||
return redirect()->route('project.application.deployment', [
|
||||
'project_uuid' => $this->parameters['project_uuid'],
|
||||
'application_uuid' => $this->parameters['application_uuid'],
|
||||
'deployment_uuid' => $this->deployment_uuid,
|
||||
'environment_name' => $this->parameters['environment_name'],
|
||||
], navigate: true);
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
@@ -72,10 +72,14 @@ class Previews extends Component
|
||||
public function stop(int $pull_request_id)
|
||||
{
|
||||
try {
|
||||
$containers = getCurrentApplicationContainerStatus($this->application->destination->server, $this->application->id, $pull_request_id);
|
||||
foreach ($containers as $container) {
|
||||
$name = str_replace('/', '', $container['Names']);
|
||||
instant_remote_process(["docker rm -f $name"], $this->application->destination->server, throwError: false);
|
||||
if ($this->application->destination->server->isSwarm()) {
|
||||
instant_remote_process(["docker stack rm {$this->application->uuid}-{$pull_request_id}"], $this->application->destination->server);
|
||||
} else {
|
||||
$containers = getCurrentApplicationContainerStatus($this->application->destination->server, $this->application->id, $pull_request_id);
|
||||
foreach ($containers as $container) {
|
||||
$name = str_replace('/', '', $container['Names']);
|
||||
instant_remote_process(["docker rm -f $name"], $this->application->destination->server, throwError: false);
|
||||
}
|
||||
}
|
||||
ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->first()->delete();
|
||||
$this->application->refresh();
|
||||
|
||||
@@ -29,12 +29,12 @@ class Rollback extends Component
|
||||
commit: $commit,
|
||||
force_rebuild: false,
|
||||
);
|
||||
return $this->redirectRoute('project.application.deployment', [
|
||||
return redirect()->route('project.application.deployment', [
|
||||
'project_uuid' => $this->parameters['project_uuid'],
|
||||
'application_uuid' => $this->parameters['application_uuid'],
|
||||
'deployment_uuid' => $deployment_uuid,
|
||||
'environment_name' => $this->parameters['environment_name'],
|
||||
], navigate: true);
|
||||
]);
|
||||
}
|
||||
|
||||
public function loadImages($showToast = false)
|
||||
|
||||
51
app/Livewire/Project/Application/Swarm.php
Normal file
51
app/Livewire/Project/Application/Swarm.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Project\Application;
|
||||
|
||||
use App\Models\Application;
|
||||
use Livewire\Component;
|
||||
|
||||
class Swarm extends Component
|
||||
{
|
||||
public Application $application;
|
||||
public string $swarm_placement_constraints = '';
|
||||
|
||||
protected $rules = [
|
||||
'application.swarm_replicas' => 'required',
|
||||
'application.swarm_placement_constraints' => 'nullable',
|
||||
'application.settings.is_swarm_only_worker_nodes' => 'required',
|
||||
];
|
||||
public function mount() {
|
||||
if ($this->application->swarm_placement_constraints) {
|
||||
$this->swarm_placement_constraints = base64_decode($this->application->swarm_placement_constraints);
|
||||
}
|
||||
}
|
||||
public function instantSave() {
|
||||
try {
|
||||
$this->validate();
|
||||
$this->application->settings->save();
|
||||
$this->dispatch('success', 'Swarm settings updated.');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function submit() {
|
||||
try {
|
||||
$this->validate();
|
||||
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.');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.project.application.swarm');
|
||||
}
|
||||
}
|
||||
@@ -152,10 +152,10 @@ class CloneProject extends Component
|
||||
}
|
||||
$newService->parse();
|
||||
}
|
||||
return $this->redirectRoute('project.resources', [
|
||||
return redirect()->route('project.resources', [
|
||||
'project_uuid' => $newProject->uuid,
|
||||
'environment_name' => $newEnvironment->name,
|
||||
], navigate: true);
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
||||
@@ -50,9 +50,9 @@ class BackupEdit extends Component
|
||||
$url = $url->withoutQueryParameter('selectedBackupId');
|
||||
$url = $url->withFragment('backups');
|
||||
$url = $url->getPath() . "#{$url->getFragment()}";
|
||||
return $this->redirect($url,navigate: true);
|
||||
return redirect($url);
|
||||
} else {
|
||||
return $this->redirectRoute('project.database.backups.all', $this->parameters);
|
||||
return redirect()->route('project.database.backups.all', $this->parameters);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ class DeleteEnvironment extends Component
|
||||
$environment = Environment::findOrFail($this->environment_id);
|
||||
if ($environment->isEmpty()) {
|
||||
$environment->delete();
|
||||
return $this->redirectRoute('project.show', ['project_uuid' => $this->parameters['project_uuid']], navigate: true);
|
||||
return redirect()->route('project.show', ['project_uuid' => $this->parameters['project_uuid']]);
|
||||
}
|
||||
return $this->dispatch('error', 'Environment has defined resources, please delete them first.');
|
||||
}
|
||||
|
||||
@@ -25,6 +25,6 @@ class DeleteProject extends Component
|
||||
return $this->dispatch('error', 'Project has resources defined, please delete them first.');
|
||||
}
|
||||
$project->delete();
|
||||
return $this->redirectRoute('projects', navigate: true);
|
||||
return redirect()->route('projects');
|
||||
}
|
||||
}
|
||||
|
||||
41
app/Livewire/Project/EnvironmentEdit.php
Normal file
41
app/Livewire/Project/EnvironmentEdit.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Project;
|
||||
|
||||
use App\Models\Application;
|
||||
use App\Models\Project;
|
||||
use Livewire\Component;
|
||||
|
||||
class EnvironmentEdit extends Component
|
||||
{
|
||||
public Project $project;
|
||||
public Application $application;
|
||||
public $environment;
|
||||
public array $parameters;
|
||||
|
||||
protected $rules = [
|
||||
'environment.name' => 'required|min:3|max:255',
|
||||
'environment.description' => 'nullable|min:3|max:255',
|
||||
];
|
||||
public function mount() {
|
||||
$this->parameters = get_route_parameters();
|
||||
|
||||
$this->project = Project::ownedByCurrentTeam()->where('uuid', request()->route('project_uuid'))->first();
|
||||
$this->environment = $this->project->environments()->where('name', request()->route('environment_name'))->first();
|
||||
}
|
||||
|
||||
public function submit()
|
||||
{
|
||||
$this->validate();
|
||||
try {
|
||||
$this->environment->save();
|
||||
return redirect()->route('project.environment.edit', ['project_uuid' => $this->project->uuid, 'environment_name' => $this->environment->name]);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.project.environment-edit');
|
||||
}
|
||||
}
|
||||
@@ -69,12 +69,12 @@ class DockerCompose extends Component
|
||||
|
||||
$service->parse(isNew: true);
|
||||
|
||||
return $this->redirectRoute('project.service.configuration', [
|
||||
return redirect()->route('project.service.configuration', [
|
||||
'service_uuid' => $service->uuid,
|
||||
'environment_name' => $environment->name,
|
||||
'project_uuid' => $project->uuid,
|
||||
]);
|
||||
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
||||
@@ -64,11 +64,11 @@ class DockerImage extends Component
|
||||
'name' => 'docker-image-' . $application->uuid,
|
||||
'fqdn' => $fqdn
|
||||
]);
|
||||
return $this->redirectRoute('project.application.configuration', [
|
||||
return redirect()->route('project.application.configuration', [
|
||||
'application_uuid' => $application->uuid,
|
||||
'environment_name' => $environment->name,
|
||||
'project_uuid' => $project->uuid,
|
||||
], navigate: true);
|
||||
]);
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
|
||||
@@ -13,6 +13,6 @@ class EmptyProject extends Component
|
||||
'name' => generate_random_name(),
|
||||
'team_id' => currentTeam()->id,
|
||||
]);
|
||||
return $this->redirectRoute('project.show', ['project_uuid' => $project->uuid, 'environment_name' => 'production'], navigate: true);
|
||||
return redirect()->route('project.show', ['project_uuid' => $project->uuid, 'environment_name' => 'production']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,11 +151,11 @@ class GithubPrivateRepository extends Component
|
||||
$application->name = generate_application_name($this->selected_repository_owner . '/' . $this->selected_repository_repo, $this->selected_branch_name, $application->uuid);
|
||||
$application->save();
|
||||
|
||||
return $this->redirectRoute('project.application.configuration', [
|
||||
return redirect()->route('project.application.configuration', [
|
||||
'application_uuid' => $application->uuid,
|
||||
'environment_name' => $environment->name,
|
||||
'project_uuid' => $project->uuid,
|
||||
], navigate: true);
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
||||
@@ -55,7 +55,11 @@ class GithubPrivateRepositoryDeployKey extends Component
|
||||
}
|
||||
$this->parameters = get_route_parameters();
|
||||
$this->query = request()->query();
|
||||
$this->private_keys = PrivateKey::where('team_id', currentTeam()->id)->where('id', '!=', 0)->get();
|
||||
if (isDev()) {
|
||||
$this->private_keys = PrivateKey::where('team_id', currentTeam()->id)->get();
|
||||
} else {
|
||||
$this->private_keys = PrivateKey::where('team_id', currentTeam()->id)->where('id', '!=', 0)->get();
|
||||
}
|
||||
}
|
||||
|
||||
public function instantSave()
|
||||
@@ -132,11 +136,11 @@ class GithubPrivateRepositoryDeployKey extends Component
|
||||
$application->name = generate_random_name($application->uuid);
|
||||
$application->save();
|
||||
|
||||
return $this->redirectRoute('project.application.configuration', [
|
||||
return redirect()->route('project.application.configuration', [
|
||||
'application_uuid' => $application->uuid,
|
||||
'environment_name' => $environment->name,
|
||||
'project_uuid' => $project->uuid,
|
||||
], navigate: true);
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
||||
@@ -184,11 +184,11 @@ class PublicGitRepository extends Component
|
||||
$application->fqdn = $fqdn;
|
||||
$application->save();
|
||||
|
||||
return $this->redirectRoute('project.application.configuration', [
|
||||
return redirect()->route('project.application.configuration', [
|
||||
'application_uuid' => $application->uuid,
|
||||
'environment_name' => $environment->name,
|
||||
'project_uuid' => $project->uuid,
|
||||
], navigate: true);
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
||||
@@ -6,22 +6,24 @@ use App\Models\Project;
|
||||
use App\Models\Server;
|
||||
use Countable;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Livewire\Component;
|
||||
|
||||
class Select extends Component
|
||||
{
|
||||
public $current_step = 'type';
|
||||
public ?int $server = null;
|
||||
public ?Server $server = null;
|
||||
public string $type;
|
||||
public string $server_id;
|
||||
public string $destination_uuid;
|
||||
public Countable|array|Server $allServers = [];
|
||||
public Countable|array|Server $servers = [];
|
||||
public Collection|array $standaloneDockers = [];
|
||||
public Collection|array $swarmDockers = [];
|
||||
public array $parameters;
|
||||
public Collection|array $services = [];
|
||||
public Collection|array $allServices = [];
|
||||
public bool $isDatabase = false;
|
||||
public bool $includeSwarm = true;
|
||||
|
||||
public bool $loadingServices = true;
|
||||
public bool $loading = false;
|
||||
@@ -31,7 +33,7 @@ class Select extends Component
|
||||
|
||||
public ?string $search = null;
|
||||
protected $queryString = [
|
||||
'server',
|
||||
'server_id',
|
||||
'search'
|
||||
];
|
||||
|
||||
@@ -53,10 +55,10 @@ class Select extends Component
|
||||
|
||||
public function updatedSelectedEnvironment()
|
||||
{
|
||||
return $this->redirectRoute('project.resources.new', [
|
||||
return redirect()->route('project.resources.new', [
|
||||
'project_uuid' => $this->parameters['project_uuid'],
|
||||
'environment_name' => $this->selectedEnvironment,
|
||||
], navigate: true);
|
||||
]);
|
||||
}
|
||||
|
||||
// public function addExistingPostgresql()
|
||||
@@ -97,21 +99,45 @@ class Select extends Component
|
||||
$this->loadingServices = false;
|
||||
}
|
||||
}
|
||||
public function instantSave()
|
||||
{
|
||||
if ($this->includeSwarm) {
|
||||
$this->servers = $this->allServers;
|
||||
} else {
|
||||
$this->servers = $this->allServers->where('settings.is_swarm_worker', false)->where('settings.is_swarm_manager', false);
|
||||
}
|
||||
}
|
||||
public function setType(string $type)
|
||||
{
|
||||
$this->type = $type;
|
||||
if ($this->loading) return;
|
||||
$this->loading = true;
|
||||
$this->type = $type;
|
||||
switch ($type) {
|
||||
case 'postgresql':
|
||||
case 'mysql':
|
||||
case 'mariadb':
|
||||
case 'redis':
|
||||
case 'mongodb':
|
||||
$this->isDatabase = true;
|
||||
$this->includeSwarm = false;
|
||||
$this->servers = $this->allServers->where('settings.is_swarm_worker', false)->where('settings.is_swarm_manager', false);
|
||||
break;
|
||||
}
|
||||
if (str($type)->startsWith('one-click-service') || str($type)->startsWith('docker-compose-empty')) {
|
||||
$this->isDatabase = true;
|
||||
$this->includeSwarm = false;
|
||||
$this->servers = $this->allServers->where('settings.is_swarm_worker', false)->where('settings.is_swarm_manager', false);
|
||||
}
|
||||
if ($type === "existing-postgresql") {
|
||||
$this->current_step = $type;
|
||||
return;
|
||||
}
|
||||
if (count($this->servers) === 1) {
|
||||
$server = $this->servers->first();
|
||||
$this->setServer($server);
|
||||
}
|
||||
// if (count($this->servers) === 1) {
|
||||
// $server = $this->servers->first();
|
||||
// $this->setServer($server);
|
||||
// }
|
||||
if (!is_null($this->server)) {
|
||||
$foundServer = $this->servers->where('id', $this->server)->first();
|
||||
$foundServer = $this->servers->where('id', $this->server->id)->first();
|
||||
if ($foundServer) {
|
||||
return $this->setServer($foundServer);
|
||||
}
|
||||
@@ -122,6 +148,7 @@ class Select extends Component
|
||||
public function setServer(Server $server)
|
||||
{
|
||||
$this->server_id = $server->id;
|
||||
$this->server = $server;
|
||||
$this->standaloneDockers = $server->standaloneDockers;
|
||||
$this->swarmDockers = $server->swarmDockers;
|
||||
$this->current_step = 'destinations';
|
||||
@@ -142,5 +169,6 @@ class Select extends Component
|
||||
public function loadServers()
|
||||
{
|
||||
$this->servers = Server::isUsable()->get();
|
||||
$this->allServers = $this->servers;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,10 +70,10 @@ CMD ["nginx", "-g", "daemon off;"]
|
||||
'fqdn' => $fqdn
|
||||
]);
|
||||
|
||||
return $this->redirectRoute('project.application.configuration', [
|
||||
return redirect()->route('project.application.configuration', [
|
||||
'application_uuid' => $application->uuid,
|
||||
'environment_name' => $environment->name,
|
||||
'project_uuid' => $project->uuid,
|
||||
], navigate: true);
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ class Application extends Component
|
||||
try {
|
||||
$this->application->delete();
|
||||
$this->dispatch('success', 'Application deleted successfully.');
|
||||
return $this->redirectRoute('project.service.configuration', $this->parameters, navigate: true);
|
||||
return redirect()->route('project.service.configuration', $this->parameters);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
||||
@@ -25,10 +25,10 @@ class Danger extends Component
|
||||
{
|
||||
try {
|
||||
DeleteResourceJob::dispatchSync($this->resource);
|
||||
return $this->redirectRoute('project.resources', [
|
||||
return redirect()->route('project.resources', [
|
||||
'project_uuid' => $this->projectUuid,
|
||||
'environment_name' => $this->environmentName
|
||||
], navigate: true);
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
||||
@@ -65,7 +65,11 @@ class Show extends Component
|
||||
|
||||
public function delete()
|
||||
{
|
||||
$this->env->delete();
|
||||
$this->dispatch('refreshEnvs');
|
||||
try {
|
||||
$this->env->delete();
|
||||
$this->dispatch('refreshEnvs');
|
||||
} catch (\Exception $e) {
|
||||
return handleError($e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,9 +73,17 @@ class GetLogs extends Component
|
||||
if (!$refresh && $this->resource?->getMorphClass() === 'App\Models\Service') return;
|
||||
if ($this->container) {
|
||||
if ($this->showTimeStamps) {
|
||||
$sshCommand = generateSshCommand($this->server, "docker logs -n {$this->numberOfLines} -t {$this->container}");
|
||||
if ($this->server->isSwarm()) {
|
||||
$sshCommand = generateSshCommand($this->server, "docker service logs -n {$this->numberOfLines} -t {$this->container}");
|
||||
} else {
|
||||
$sshCommand = generateSshCommand($this->server, "docker logs -n {$this->numberOfLines} -t {$this->container}");
|
||||
}
|
||||
} else {
|
||||
$sshCommand = generateSshCommand($this->server, "docker logs -n {$this->numberOfLines} {$this->container}");
|
||||
if ($this->server->isSwarm()) {
|
||||
$sshCommand = generateSshCommand($this->server, "docker service logs -n {$this->numberOfLines} {$this->container}");
|
||||
} else {
|
||||
$sshCommand = generateSshCommand($this->server, "docker logs -n {$this->numberOfLines} {$this->container}");
|
||||
}
|
||||
}
|
||||
if ($refresh) {
|
||||
$this->outputs = '';
|
||||
|
||||
@@ -34,7 +34,15 @@ class Logs extends Component
|
||||
$this->resource = Application::where('uuid', $this->parameters['application_uuid'])->firstOrFail();
|
||||
$this->status = $this->resource->status;
|
||||
$this->server = $this->resource->destination->server;
|
||||
$containers = getCurrentApplicationContainerStatus($this->server, $this->resource->id, 0);
|
||||
if ($this->server->isSwarm()) {
|
||||
$containers = collect([
|
||||
[
|
||||
'Names' => $this->resource->uuid . '_' . $this->resource->uuid,
|
||||
]
|
||||
]);
|
||||
} else {
|
||||
$containers = getCurrentApplicationContainerStatus($this->server, $this->resource->id, 0);
|
||||
}
|
||||
if ($containers->count() > 0) {
|
||||
$containers->each(function ($container) {
|
||||
$this->containers->push(str_replace('/', '', $container['Names']));
|
||||
@@ -62,7 +70,7 @@ class Logs extends Component
|
||||
$this->status = $this->resource->status;
|
||||
$this->server = $this->resource->destination->server;
|
||||
$this->container = $this->resource->uuid;
|
||||
if (str(data_get($this,'resource.status'))->startsWith('running')) {
|
||||
if (str(data_get($this, 'resource.status'))->startsWith('running')) {
|
||||
$this->containers->push($this->container);
|
||||
}
|
||||
} else if (data_get($this->parameters, 'service_uuid')) {
|
||||
|
||||
@@ -2,22 +2,26 @@
|
||||
|
||||
namespace App\Livewire\Project\Shared\Storages;
|
||||
|
||||
use App\Models\Application;
|
||||
use Livewire\Component;
|
||||
|
||||
class Add extends Component
|
||||
{
|
||||
public $uuid;
|
||||
public $parameters;
|
||||
public $isSwarm = false;
|
||||
public string $name;
|
||||
public string $mount_path;
|
||||
public string|null $host_path = null;
|
||||
public ?string $host_path = null;
|
||||
|
||||
protected $listeners = ['clearAddStorage' => 'clear'];
|
||||
protected $rules = [
|
||||
public $rules = [
|
||||
'name' => 'required|string',
|
||||
'mount_path' => 'required|string',
|
||||
'host_path' => 'string|nullable',
|
||||
];
|
||||
|
||||
protected $listeners = ['clearAddStorage' => 'clear'];
|
||||
|
||||
protected $validationAttributes = [
|
||||
'name' => 'name',
|
||||
'mount_path' => 'mount',
|
||||
@@ -27,17 +31,33 @@ class Add extends Component
|
||||
public function mount()
|
||||
{
|
||||
$this->parameters = get_route_parameters();
|
||||
if (data_get($this->parameters, 'application_uuid')) {
|
||||
$applicationUuid = $this->parameters['application_uuid'];
|
||||
$application = Application::where('uuid', $applicationUuid)->first();
|
||||
if (!$application) {
|
||||
abort(404);
|
||||
}
|
||||
if ($application->destination->server->isSwarm()) {
|
||||
$this->isSwarm = true;
|
||||
$this->rules['host_path'] = 'required|string';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function submit()
|
||||
{
|
||||
$this->validate();
|
||||
$name = $this->uuid . '-' . $this->name;
|
||||
$this->dispatch('addNewVolume', [
|
||||
'name' => $name,
|
||||
'mount_path' => $this->mount_path,
|
||||
'host_path' => $this->host_path,
|
||||
]);
|
||||
try {
|
||||
$this->validate($this->rules);
|
||||
$name = $this->uuid . '-' . $this->name;
|
||||
$this->dispatch('addNewVolume', [
|
||||
'name' => $name,
|
||||
'mount_path' => $this->mount_path,
|
||||
'host_path' => $this->host_path,
|
||||
]);
|
||||
$this->dispatch('closeStorageModal');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function clear()
|
||||
|
||||
@@ -19,7 +19,7 @@ class Delete extends Component
|
||||
return;
|
||||
}
|
||||
$this->server->delete();
|
||||
return $this->redirectRoute('server.all', navigate: true);
|
||||
return redirect()->route('server.all');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ class Show extends Component
|
||||
try {
|
||||
$this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->first();
|
||||
if (is_null($this->server)) {
|
||||
return $this->redirectRoute('server.all', navigate: true);
|
||||
return redirect()->route('server.all');
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
|
||||
@@ -25,7 +25,7 @@ class Form extends Component
|
||||
'server.settings.is_cloudflare_tunnel' => 'required|boolean',
|
||||
'server.settings.is_reachable' => 'required',
|
||||
'server.settings.is_swarm_manager' => 'required|boolean',
|
||||
// 'server.settings.is_swarm_worker' => 'required|boolean',
|
||||
'server.settings.is_swarm_worker' => 'required|boolean',
|
||||
'wildcard_domain' => 'nullable|url',
|
||||
];
|
||||
protected $validationAttributes = [
|
||||
@@ -37,16 +37,13 @@ class Form extends Component
|
||||
'server.settings.is_cloudflare_tunnel' => 'Cloudflare Tunnel',
|
||||
'server.settings.is_reachable' => 'Is reachable',
|
||||
'server.settings.is_swarm_manager' => 'Swarm Manager',
|
||||
// 'server.settings.is_swarm_worker' => 'Swarm Worker',
|
||||
'server.settings.is_swarm_worker' => 'Swarm Worker',
|
||||
];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->wildcard_domain = $this->server->settings->wildcard_domain;
|
||||
$this->cleanup_after_percentage = $this->server->settings->cleanup_after_percentage;
|
||||
if (!$this->server->isFunctional()) {
|
||||
$this->validateServer();
|
||||
}
|
||||
}
|
||||
public function serverRefresh($install = true)
|
||||
{
|
||||
|
||||
@@ -43,7 +43,7 @@ class LogDrains extends Component
|
||||
try {
|
||||
$server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->first();
|
||||
if (is_null($server)) {
|
||||
return $this->redirectRoute('server.all', navigate: true);
|
||||
return redirect()->route('server.all');
|
||||
}
|
||||
$this->server = $server;
|
||||
} catch (\Throwable $e) {
|
||||
|
||||
@@ -22,7 +22,10 @@ class ByIp extends Component
|
||||
public string $user = 'root';
|
||||
public int $port = 22;
|
||||
public bool $is_swarm_manager = false;
|
||||
public bool $is_swarm_worker = false;
|
||||
public $selected_swarm_cluster = null;
|
||||
|
||||
public $swarm_managers = [];
|
||||
protected $rules = [
|
||||
'name' => 'required|string',
|
||||
'description' => 'nullable|string',
|
||||
@@ -30,6 +33,7 @@ class ByIp extends Component
|
||||
'user' => 'required|string',
|
||||
'port' => 'required|integer',
|
||||
'is_swarm_manager' => 'required|boolean',
|
||||
'is_swarm_worker' => 'required|boolean',
|
||||
];
|
||||
protected $validationAttributes = [
|
||||
'name' => 'Name',
|
||||
@@ -38,12 +42,17 @@ class ByIp extends Component
|
||||
'user' => 'User',
|
||||
'port' => 'Port',
|
||||
'is_swarm_manager' => 'Swarm Manager',
|
||||
'is_swarm_worker' => 'Swarm Worker',
|
||||
];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->name = generate_random_name();
|
||||
$this->private_key_id = $this->private_keys->first()->id;
|
||||
$this->swarm_managers = Server::isUsable()->get()->where('settings.is_swarm_manager', true);
|
||||
if ($this->swarm_managers->count() > 0) {
|
||||
$this->selected_swarm_cluster = $this->swarm_managers->first()->id;
|
||||
}
|
||||
}
|
||||
|
||||
public function setPrivateKey(string $private_key_id)
|
||||
@@ -53,7 +62,7 @@ class ByIp extends Component
|
||||
|
||||
public function instantSave()
|
||||
{
|
||||
$this->dispatch('success', 'Application settings updated!');
|
||||
// $this->dispatch('success', 'Application settings updated!');
|
||||
}
|
||||
|
||||
public function submit()
|
||||
@@ -63,7 +72,7 @@ class ByIp extends Component
|
||||
if (is_null($this->private_key_id)) {
|
||||
return $this->dispatch('error', 'You must select a private key');
|
||||
}
|
||||
$server = Server::create([
|
||||
$payload = [
|
||||
'name' => $this->name,
|
||||
'description' => $this->description,
|
||||
'ip' => $this->ip,
|
||||
@@ -75,11 +84,16 @@ class ByIp extends Component
|
||||
"type" => ProxyTypes::TRAEFIK_V2->value,
|
||||
"status" => ProxyStatus::EXITED->value,
|
||||
],
|
||||
]);
|
||||
];
|
||||
if ($this->is_swarm_worker) {
|
||||
$payload['swarm_cluster'] = $this->selected_swarm_cluster;
|
||||
}
|
||||
$server = Server::create($payload);
|
||||
$server->settings->is_swarm_manager = $this->is_swarm_manager;
|
||||
$server->settings->is_swarm_worker = $this->is_swarm_worker;
|
||||
$server->settings->save();
|
||||
$server->addInitialNetwork();
|
||||
return $this->redirectRoute('server.show', $server->uuid, navigate: true);
|
||||
return redirect()->route('server.show', $server->uuid);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ class Show extends Component
|
||||
try {
|
||||
$this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->first();
|
||||
if (is_null($this->server)) {
|
||||
return $this->redirectRoute('server.all', navigate: true);
|
||||
return redirect()->route('server.all');
|
||||
}
|
||||
$this->privateKeys = PrivateKey::ownedByCurrentTeam()->get()->where('is_git_related', false);
|
||||
} catch (\Throwable $e) {
|
||||
|
||||
@@ -15,7 +15,7 @@ class Logs extends Component
|
||||
try {
|
||||
$this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->first();
|
||||
if (is_null($this->server)) {
|
||||
return $this->redirectRoute('server.all', navigate: true);
|
||||
return redirect()->route('server.all');
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
|
||||
@@ -20,7 +20,7 @@ class Show extends Component
|
||||
try {
|
||||
$this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->first();
|
||||
if (is_null($this->server)) {
|
||||
return $this->redirectRoute('server.all', navigate: true);
|
||||
return redirect()->route('server.all');
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
|
||||
@@ -15,7 +15,6 @@ class Status extends Component
|
||||
|
||||
protected $listeners = ['proxyStatusUpdated', 'startProxyPolling'];
|
||||
public function mount() {
|
||||
$this->checkProxy();
|
||||
}
|
||||
public function startProxyPolling()
|
||||
{
|
||||
|
||||
@@ -17,7 +17,7 @@ class Show extends Component
|
||||
try {
|
||||
$this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->first();
|
||||
if (is_null($this->server)) {
|
||||
return $this->redirectRoute('server.all', navigate: true);
|
||||
return redirect()->route('server.all');
|
||||
}
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
|
||||
@@ -41,7 +41,7 @@ class Change extends Component
|
||||
$github_app_uuid = request()->github_app_uuid;
|
||||
$this->github_app = GithubApp::where('uuid', $github_app_uuid)->first();
|
||||
if (!$this->github_app) {
|
||||
return $this->redirectRoute('source.all', navigate: true);
|
||||
return redirect()->route('source.all');
|
||||
}
|
||||
$settings = InstanceSettings::get();
|
||||
$this->github_app->makeVisible('client_secret')->makeVisible('webhook_secret');
|
||||
@@ -67,7 +67,7 @@ class Change extends Component
|
||||
$type = data_get($parameters, 'type');
|
||||
$destination = data_get($parameters, 'destination');
|
||||
session()->forget('from');
|
||||
return $this->redirectRoute($back, [
|
||||
return redirect()->route($back, [
|
||||
'environment_name' => $environment_name,
|
||||
'project_uuid' => $project_uuid,
|
||||
'type' => $type,
|
||||
@@ -117,7 +117,7 @@ class Change extends Component
|
||||
{
|
||||
try {
|
||||
$this->github_app->delete();
|
||||
return $this->redirectRoute('source.all', navigate: true);
|
||||
return redirect()->route('source.all');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ class Create extends Component
|
||||
if (session('from')) {
|
||||
session(['from' => session('from') + ['source_id' => $github_app->id]]);
|
||||
}
|
||||
return $this->redirectRoute('source.github.show', ['github_app_uuid' => $github_app->uuid], navigate: true);
|
||||
return redirect()->route('source.github.show', ['github_app_uuid' => $github_app->uuid]);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Livewire\Subscription;
|
||||
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Providers\RouteServiceProvider;
|
||||
use Livewire\Component;
|
||||
|
||||
class Show extends Component
|
||||
@@ -11,7 +12,7 @@ class Show extends Component
|
||||
public bool $alreadySubscribed = false;
|
||||
public function mount() {
|
||||
if (!isCloud()) {
|
||||
return $this->redirect('/', navigate: true);
|
||||
return redirect(RouteServiceProvider::HOME);
|
||||
}
|
||||
$this->settings = InstanceSettings::get();
|
||||
$this->alreadySubscribed = currentTeam()->subscription()->exists();
|
||||
|
||||
@@ -30,7 +30,7 @@ class Create extends Component
|
||||
]);
|
||||
auth()->user()->teams()->attach($team, ['role' => 'admin']);
|
||||
refreshSession();
|
||||
return $this->redirectRoute('team.index', navigate: true);
|
||||
return redirect()->route('team.index');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
||||
@@ -24,6 +24,6 @@ class Delete extends Component
|
||||
});
|
||||
|
||||
refreshSession();
|
||||
return $this->redirectRoute('team.index', navigate: true);
|
||||
return redirect()->route('team.index');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ class Create extends Component
|
||||
$this->storage->team_id = currentTeam()->id;
|
||||
$this->storage->testConnection();
|
||||
$this->storage->save();
|
||||
return $this->redirectRoute('team.storages.show', $this->storage->uuid, navigate: true);
|
||||
return redirect()->route('team.storages.show', $this->storage->uuid);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ class Form extends Component
|
||||
{
|
||||
try {
|
||||
$this->storage->delete();
|
||||
return $this->redirectRoute('team.storages.all', navigate: true);
|
||||
return redirect()->route('team.storages.all');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ class Index extends Component
|
||||
public function mount()
|
||||
{
|
||||
if (config('coolify.waitlist') == false) {
|
||||
return $this->redirectRoute('register', navigate: true);
|
||||
return redirect()->route('register');
|
||||
}
|
||||
$this->waitingInLine = Waitlist::whereVerified(true)->count();
|
||||
$this->users = User::count();
|
||||
|
||||
@@ -382,6 +382,9 @@ class Application extends BaseModel
|
||||
|
||||
public function deploymentType()
|
||||
{
|
||||
if (isDev() && data_get($this, 'private_key_id') === 0) {
|
||||
return 'deploy_key';
|
||||
}
|
||||
if (data_get($this, 'private_key_id')) {
|
||||
return 'deploy_key';
|
||||
} else if (data_get($this, 'source')) {
|
||||
@@ -422,7 +425,7 @@ class Application extends BaseModel
|
||||
public function isConfigurationChanged($save = false)
|
||||
{
|
||||
$newConfigHash = $this->fqdn . $this->git_repository . $this->git_branch . $this->git_commit_sha . $this->build_pack . $this->static_image . $this->install_command . $this->build_command . $this->start_command . $this->port_exposes . $this->port_mappings . $this->base_directory . $this->publish_directory . $this->health_check_path . $this->health_check_port . $this->health_check_host . $this->health_check_method . $this->health_check_return_code . $this->health_check_scheme . $this->health_check_response_text . $this->health_check_interval . $this->health_check_timeout . $this->health_check_retries . $this->health_check_start_period . $this->health_check_enabled . $this->limits_memory . $this->limits_swap . $this->limits_swappiness . $this->limits_reservation . $this->limits_cpus . $this->limits_cpuset . $this->limits_cpu_shares . $this->dockerfile . $this->dockerfile_location . $this->custom_labels;
|
||||
if ($this->pull_request_id === 0) {
|
||||
if ($this->pull_request_id === 0 || $this->pull_request_id === null) {
|
||||
$newConfigHash .= json_encode($this->environment_variables->all());
|
||||
} else {
|
||||
$newConfigHash .= json_encode($this->environment_variables_preview->all());
|
||||
@@ -633,7 +636,7 @@ class Application extends BaseModel
|
||||
'memswap_limit' => $this->limits_memory_swap,
|
||||
'mem_swappiness' => $this->limits_memory_swappiness,
|
||||
'mem_reservation' => $this->limits_memory_reservation,
|
||||
'cpus' => (int) $this->limits_cpus,
|
||||
'cpus' => (float) $this->limits_cpus,
|
||||
'cpuset' => $this->limits_cpuset,
|
||||
'cpu_shares' => $this->limits_cpu_shares,
|
||||
]
|
||||
@@ -859,9 +862,12 @@ class Application extends BaseModel
|
||||
}
|
||||
$private_key = base64_encode($private_key);
|
||||
$git_clone_command_base = "GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" {$git_clone_command} {$customRepository} {$baseDir}";
|
||||
if (!$only_checkout) {
|
||||
if ($only_checkout) {
|
||||
$git_clone_command = $git_clone_command_base;
|
||||
} else {
|
||||
$git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command_base);
|
||||
}
|
||||
ray($git_clone_command);
|
||||
if ($exec_in_docker) {
|
||||
$commands = collect([
|
||||
executeInDocker($deployment_uuid, "mkdir -p /root/.ssh"),
|
||||
@@ -979,6 +985,7 @@ class Application extends BaseModel
|
||||
// $fileList->push(".$prComposeFile");
|
||||
// }
|
||||
$commands = collect([
|
||||
"rm -rf /tmp/{$uuid}",
|
||||
"mkdir -p /tmp/{$uuid} && cd /tmp/{$uuid}",
|
||||
$cloneCommand,
|
||||
"git sparse-checkout init --cone",
|
||||
|
||||
@@ -12,7 +12,6 @@ use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Process;
|
||||
use Illuminate\Support\Sleep;
|
||||
use Spatie\SchemalessAttributes\Casts\SchemalessAttributes;
|
||||
use Spatie\SchemalessAttributes\SchemalessAttributesTrait;
|
||||
use Illuminate\Support\Str;
|
||||
@@ -72,7 +71,7 @@ class Server extends BaseModel
|
||||
|
||||
static public function isUsable()
|
||||
{
|
||||
return Server::ownedByCurrentTeam()->whereRelation('settings', 'is_reachable', true)->whereRelation('settings', 'is_usable', true);
|
||||
return Server::ownedByCurrentTeam()->whereRelation('settings', 'is_reachable', true)->whereRelation('settings', 'is_usable', true)->whereRelation('settings', 'is_swarm_worker', false);
|
||||
}
|
||||
|
||||
static public function destinationsByServer(string $server_id)
|
||||
@@ -150,30 +149,45 @@ class Server extends BaseModel
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public function isServerReady()
|
||||
public function isServerReady(int $tries = 3)
|
||||
{
|
||||
if ($this->skipServer()) {
|
||||
return false;
|
||||
}
|
||||
$serverUptimeCheckNumber = $this->unreachable_count;
|
||||
$serverUptimeCheckNumberMax = 8;
|
||||
if ($this->unreachable_count < $tries) {
|
||||
$serverUptimeCheckNumber = $this->unreachable_count + 1;
|
||||
}
|
||||
if ($this->unreachable_count > $tries) {
|
||||
$serverUptimeCheckNumber = $tries;
|
||||
}
|
||||
|
||||
$currentTime = now()->timestamp;
|
||||
$runtime = 50;
|
||||
$serverUptimeCheckNumberMax = $tries;
|
||||
|
||||
$isReady = false;
|
||||
// Run for 50 seconds max and check every 5 seconds for 8 times
|
||||
while ($currentTime + $runtime > now()->timestamp) {
|
||||
ray('serverUptimeCheckNumber: ' . $serverUptimeCheckNumber);
|
||||
// ray('server: ' . $this->name);
|
||||
// ray('serverUptimeCheckNumber: ' . $serverUptimeCheckNumber);
|
||||
// ray('serverUptimeCheckNumberMax: ' . $serverUptimeCheckNumberMax);
|
||||
|
||||
$result = $this->validateConnection();
|
||||
if ($result) {
|
||||
if ($this->unreachable_notification_sent === true) {
|
||||
$this->update(['unreachable_notification_sent' => false]);
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
if ($serverUptimeCheckNumber >= $serverUptimeCheckNumberMax) {
|
||||
// Reached max number of retries
|
||||
if ($this->unreachable_notification_sent === false) {
|
||||
ray('Server unreachable, sending notification...');
|
||||
$this->team?->notify(new Unreachable($this));
|
||||
$this->update(['unreachable_notification_sent' => true]);
|
||||
}
|
||||
$this->settings()->update([
|
||||
'is_reachable' => false,
|
||||
]);
|
||||
$this->update([
|
||||
'unreachable_count' => 0,
|
||||
]);
|
||||
if ($this->settings->is_reachable === true) {
|
||||
$this->settings()->update([
|
||||
'is_reachable' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
foreach ($this->applications() as $application) {
|
||||
$application->update(['status' => 'exited']);
|
||||
}
|
||||
@@ -190,23 +204,13 @@ class Server extends BaseModel
|
||||
$db->update(['status' => 'exited']);
|
||||
}
|
||||
}
|
||||
$isReady = false;
|
||||
break;
|
||||
}
|
||||
$result = $this->validateConnection();
|
||||
// ray('validateConnection: ' . $result);
|
||||
if (!$result) {
|
||||
$serverUptimeCheckNumber++;
|
||||
} else {
|
||||
$this->update([
|
||||
'unreachable_count' => $serverUptimeCheckNumber,
|
||||
'unreachable_count' => $this->unreachable_count + 1,
|
||||
]);
|
||||
Sleep::for(5)->seconds();
|
||||
continue;
|
||||
}
|
||||
$isReady = true;
|
||||
break;
|
||||
return false;
|
||||
}
|
||||
return $isReady;
|
||||
}
|
||||
public function getDiskUsage()
|
||||
{
|
||||
@@ -380,9 +384,20 @@ class Server extends BaseModel
|
||||
{
|
||||
return data_get($this, 'settings.is_swarm_manager') || data_get($this, 'settings.is_swarm_worker');
|
||||
}
|
||||
public function isSwarmManager()
|
||||
{
|
||||
return data_get($this, 'settings.is_swarm_manager');
|
||||
}
|
||||
public function isSwarmWorker()
|
||||
{
|
||||
return data_get($this, 'settings.is_swarm_worker');
|
||||
}
|
||||
public function validateConnection()
|
||||
{
|
||||
$server = Server::find($this->id);
|
||||
if (!$server) {
|
||||
return false;
|
||||
}
|
||||
if ($server->skipServer()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ class Revived extends Notification implements ShouldQueue
|
||||
|
||||
public function toDiscord(): string
|
||||
{
|
||||
$message = "Coolify: Server '{$this->server->name}' revived. All automations & integrations are turned on again!";
|
||||
$message = "Coolify: Server '{$this->server->name}' revived. All automations & integrations are turned on again!";
|
||||
return $message;
|
||||
}
|
||||
public function toTelegram(): array
|
||||
|
||||
@@ -31,9 +31,9 @@ class FortifyServiceProvider extends ServiceProvider
|
||||
{
|
||||
// First user (root) will be redirected to /settings instead of / on registration.
|
||||
if ($request->user()->currentTeam->id === 0) {
|
||||
return redirect('/settings');
|
||||
return redirect()->route('settings.configuration');
|
||||
}
|
||||
return redirect('/');
|
||||
return redirect(RouteServiceProvider::HOME);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -41,11 +41,11 @@ function queue_application_deployment(int $application_id, string $deployment_uu
|
||||
dispatch(new ApplicationDeploymentNewJob(
|
||||
deployment: $deployment,
|
||||
application: Application::find($application_id)
|
||||
))->onConnection('long-running')->onQueue('long-running');
|
||||
));
|
||||
} else {
|
||||
dispatch(new ApplicationDeploymentJob(
|
||||
application_deployment_queue_id: $deployment->id,
|
||||
))->onConnection('long-running')->onQueue('long-running');
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,11 +57,11 @@ function queue_next_deployment(Application $application, bool $isNew = false)
|
||||
dispatch(new ApplicationDeploymentNewJob(
|
||||
deployment: $next_found,
|
||||
application: $application
|
||||
))->onConnection('long-running')->onQueue('long-running');
|
||||
));
|
||||
} else {
|
||||
dispatch(new ApplicationDeploymentJob(
|
||||
application_deployment_queue_id: $next_found->id,
|
||||
))->onConnection('long-running')->onQueue('long-running');
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,20 +14,23 @@ use Visus\Cuid2\Cuid2;
|
||||
function getCurrentApplicationContainerStatus(Server $server, int $id, ?int $pullRequestId = null): Collection
|
||||
{
|
||||
$containers = collect([]);
|
||||
$containers = instant_remote_process(["docker ps -a --filter='label=coolify.applicationId={$id}' --format '{{json .}}' "], $server);
|
||||
$containers = format_docker_command_output_to_json($containers);
|
||||
$containers = $containers->map(function ($container) use ($pullRequestId) {
|
||||
$labels = data_get($container, 'Labels');
|
||||
if (!str($labels)->contains("coolify.pullRequestId=")) {
|
||||
data_set($container, 'Labels', $labels . ",coolify.pullRequestId={$pullRequestId}");
|
||||
return $container;
|
||||
}
|
||||
if (str($labels)->contains("coolify.pullRequestId=$pullRequestId")) {
|
||||
return $container;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
$containers = $containers->filter();
|
||||
if (!$server->isSwarm()) {
|
||||
$containers = instant_remote_process(["docker ps -a --filter='label=coolify.applicationId={$id}' --format '{{json .}}' "], $server);
|
||||
$containers = format_docker_command_output_to_json($containers);
|
||||
$containers = $containers->map(function ($container) use ($pullRequestId) {
|
||||
$labels = data_get($container, 'Labels');
|
||||
if (!str($labels)->contains("coolify.pullRequestId=")) {
|
||||
data_set($container, 'Labels', $labels . ",coolify.pullRequestId={$pullRequestId}");
|
||||
return $container;
|
||||
}
|
||||
if (str($labels)->contains("coolify.pullRequestId=$pullRequestId")) {
|
||||
return $container;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
$containers = $containers->filter();
|
||||
return $containers;
|
||||
}
|
||||
return $containers;
|
||||
}
|
||||
|
||||
@@ -213,55 +216,60 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
|
||||
$labels = collect([]);
|
||||
$labels->push('traefik.enable=true');
|
||||
foreach ($domains as $loop => $domain) {
|
||||
$uuid = new Cuid2(7);
|
||||
$url = Url::fromString($domain);
|
||||
$host = $url->getHost();
|
||||
$path = $url->getPath();
|
||||
$schema = $url->getScheme();
|
||||
$port = $url->getPort();
|
||||
if (is_null($port) && !is_null($onlyPort)) {
|
||||
$port = $onlyPort;
|
||||
try {
|
||||
$uuid = new Cuid2(7);
|
||||
$url = Url::fromString($domain);
|
||||
$host = $url->getHost();
|
||||
$path = $url->getPath();
|
||||
$schema = $url->getScheme();
|
||||
$port = $url->getPort();
|
||||
if (is_null($port) && !is_null($onlyPort)) {
|
||||
$port = $onlyPort;
|
||||
}
|
||||
$http_label = "{$uuid}-{$loop}-http";
|
||||
$https_label = "{$uuid}-{$loop}-https";
|
||||
|
||||
if ($schema === 'https') {
|
||||
// Set labels for https
|
||||
$labels->push("traefik.http.routers.{$https_label}.rule=Host(`{$host}`) && PathPrefix(`{$path}`)");
|
||||
$labels->push("traefik.http.routers.{$https_label}.entryPoints=https");
|
||||
$labels->push("traefik.http.routers.{$https_label}.middlewares=gzip");
|
||||
if ($port) {
|
||||
$labels->push("traefik.http.routers.{$https_label}.service={$https_label}");
|
||||
$labels->push("traefik.http.services.{$https_label}.loadbalancer.server.port=$port");
|
||||
}
|
||||
if ($path !== '/') {
|
||||
$labels->push("traefik.http.routers.{$https_label}.middlewares={$https_label}-stripprefix");
|
||||
$labels->push("traefik.http.middlewares.{$https_label}-stripprefix.stripprefix.prefixes={$path}");
|
||||
}
|
||||
|
||||
$labels->push("traefik.http.routers.{$https_label}.tls=true");
|
||||
$labels->push("traefik.http.routers.{$https_label}.tls.certresolver=letsencrypt");
|
||||
|
||||
// Set labels for http (redirect to https)
|
||||
$labels->push("traefik.http.routers.{$http_label}.rule=Host(`{$host}`) && PathPrefix(`{$path}`)");
|
||||
$labels->push("traefik.http.routers.{$http_label}.entryPoints=http");
|
||||
if ($is_force_https_enabled) {
|
||||
$labels->push("traefik.http.routers.{$http_label}.middlewares=redirect-to-https");
|
||||
}
|
||||
} else {
|
||||
// Set labels for http
|
||||
$labels->push("traefik.http.routers.{$http_label}.rule=Host(`{$host}`) && PathPrefix(`{$path}`)");
|
||||
$labels->push("traefik.http.routers.{$http_label}.entryPoints=http");
|
||||
$labels->push("traefik.http.routers.{$http_label}.middlewares=gzip");
|
||||
if ($port) {
|
||||
$labels->push("traefik.http.routers.{$http_label}.service={$http_label}");
|
||||
$labels->push("traefik.http.services.{$http_label}.loadbalancer.server.port=$port");
|
||||
}
|
||||
if ($path !== '/') {
|
||||
$labels->push("traefik.http.routers.{$http_label}.middlewares={$http_label}-stripprefix");
|
||||
$labels->push("traefik.http.middlewares.{$http_label}-stripprefix.stripprefix.prefixes={$path}");
|
||||
}
|
||||
}
|
||||
} catch(\Throwable $e) {
|
||||
continue;
|
||||
}
|
||||
$http_label = "{$uuid}-{$loop}-http";
|
||||
$https_label = "{$uuid}-{$loop}-https";
|
||||
|
||||
if ($schema === 'https') {
|
||||
// Set labels for https
|
||||
$labels->push("traefik.http.routers.{$https_label}.rule=Host(`{$host}`) && PathPrefix(`{$path}`)");
|
||||
$labels->push("traefik.http.routers.{$https_label}.entryPoints=https");
|
||||
$labels->push("traefik.http.routers.{$https_label}.middlewares=gzip");
|
||||
if ($port) {
|
||||
$labels->push("traefik.http.routers.{$https_label}.service={$https_label}");
|
||||
$labels->push("traefik.http.services.{$https_label}.loadbalancer.server.port=$port");
|
||||
}
|
||||
if ($path !== '/') {
|
||||
$labels->push("traefik.http.routers.{$https_label}.middlewares={$https_label}-stripprefix");
|
||||
$labels->push("traefik.http.middlewares.{$https_label}-stripprefix.stripprefix.prefixes={$path}");
|
||||
}
|
||||
|
||||
$labels->push("traefik.http.routers.{$https_label}.tls=true");
|
||||
$labels->push("traefik.http.routers.{$https_label}.tls.certresolver=letsencrypt");
|
||||
|
||||
// Set labels for http (redirect to https)
|
||||
$labels->push("traefik.http.routers.{$http_label}.rule=Host(`{$host}`) && PathPrefix(`{$path}`)");
|
||||
$labels->push("traefik.http.routers.{$http_label}.entryPoints=http");
|
||||
if ($is_force_https_enabled) {
|
||||
$labels->push("traefik.http.routers.{$http_label}.middlewares=redirect-to-https");
|
||||
}
|
||||
} else {
|
||||
// Set labels for http
|
||||
$labels->push("traefik.http.routers.{$http_label}.rule=Host(`{$host}`) && PathPrefix(`{$path}`)");
|
||||
$labels->push("traefik.http.routers.{$http_label}.entryPoints=http");
|
||||
$labels->push("traefik.http.routers.{$http_label}.middlewares=gzip");
|
||||
if ($port) {
|
||||
$labels->push("traefik.http.routers.{$http_label}.service={$http_label}");
|
||||
$labels->push("traefik.http.services.{$http_label}.loadbalancer.server.port=$port");
|
||||
}
|
||||
if ($path !== '/') {
|
||||
$labels->push("traefik.http.routers.{$http_label}.middlewares={$http_label}-stripprefix");
|
||||
$labels->push("traefik.http.middlewares.{$http_label}-stripprefix.stripprefix.prefixes={$path}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $labels;
|
||||
|
||||
@@ -15,10 +15,16 @@ function get_proxy_path()
|
||||
}
|
||||
function connectProxyToNetworks(Server $server)
|
||||
{
|
||||
// Standalone networks
|
||||
$networks = collect($server->standaloneDockers)->map(function ($docker) {
|
||||
return $docker['network'];
|
||||
});
|
||||
if ($server->isSwarm()) {
|
||||
$networks = collect($server->swarmDockers)->map(function ($docker) {
|
||||
return $docker['network'];
|
||||
});
|
||||
} else {
|
||||
// Standalone networks
|
||||
$networks = collect($server->standaloneDockers)->map(function ($docker) {
|
||||
return $docker['network'];
|
||||
});
|
||||
}
|
||||
// Service networks
|
||||
foreach ($server->services()->get() as $service) {
|
||||
$networks->push($service->networks());
|
||||
@@ -41,16 +47,30 @@ function connectProxyToNetworks(Server $server)
|
||||
$networks->push($network);
|
||||
}
|
||||
$networks = collect($networks)->flatten()->unique();
|
||||
if ($networks->count() === 0) {
|
||||
$networks = collect(['coolify']);
|
||||
if ($server->isSwarm()) {
|
||||
if ($networks->count() === 0) {
|
||||
$networks = collect(['coolify-overlay']);
|
||||
}
|
||||
$commands = $networks->map(function ($network) {
|
||||
return [
|
||||
"echo 'Connecting coolify-proxy to $network network...'",
|
||||
"docker network ls --format '{{.Name}}' | grep '^$network$' >/dev/null || docker network create --driver overlay --attachable $network >/dev/null",
|
||||
"docker network connect $network coolify-proxy >/dev/null 2>&1 || true",
|
||||
];
|
||||
});
|
||||
} else {
|
||||
if ($networks->count() === 0) {
|
||||
$networks = collect(['coolify']);
|
||||
}
|
||||
$commands = $networks->map(function ($network) {
|
||||
return [
|
||||
"echo 'Connecting coolify-proxy to $network network...'",
|
||||
"docker network ls --format '{{.Name}}' | grep '^$network$' >/dev/null || docker network create --attachable $network >/dev/null",
|
||||
"docker network connect $network coolify-proxy >/dev/null 2>&1 || true",
|
||||
];
|
||||
});
|
||||
}
|
||||
$commands = $networks->map(function ($network) {
|
||||
return [
|
||||
"echo 'Connecting coolify-proxy to $network network...'",
|
||||
"docker network ls --format '{{.Name}}' | grep '^$network$' >/dev/null || docker network create --attachable $network >/dev/null",
|
||||
"docker network connect $network coolify-proxy >/dev/null 2>&1 || true",
|
||||
];
|
||||
});
|
||||
|
||||
return $commands->flatten();
|
||||
}
|
||||
function generate_default_proxy_configuration(Server $server)
|
||||
@@ -60,14 +80,18 @@ function generate_default_proxy_configuration(Server $server)
|
||||
$networks = collect($server->swarmDockers)->map(function ($docker) {
|
||||
return $docker['network'];
|
||||
})->unique();
|
||||
if ($networks->count() === 0) {
|
||||
$networks = collect(['coolify-overlay']);
|
||||
}
|
||||
} else {
|
||||
$networks = collect($server->standaloneDockers)->map(function ($docker) {
|
||||
return $docker['network'];
|
||||
})->unique();
|
||||
if ($networks->count() === 0) {
|
||||
$networks = collect(['coolify']);
|
||||
}
|
||||
}
|
||||
if ($networks->count() === 0) {
|
||||
$networks = collect(['coolify']);
|
||||
}
|
||||
|
||||
$array_of_networks = collect([]);
|
||||
$networks->map(function ($network) use ($array_of_networks) {
|
||||
$array_of_networks[$network] = [
|
||||
|
||||
@@ -102,6 +102,7 @@ function refreshSession(?Team $team = null): void
|
||||
}
|
||||
function handleError(?Throwable $error = null, ?Livewire\Component $livewire = null, ?string $customErrorMessage = null)
|
||||
{
|
||||
ray($error);
|
||||
if ($error instanceof TooManyRequestsException) {
|
||||
if (isset($livewire)) {
|
||||
return $livewire->dispatch('error', "Too many requests. Please try again in {$error->secondsUntilAvailable} seconds.");
|
||||
@@ -227,11 +228,15 @@ function base_ip(): string
|
||||
}
|
||||
function getFqdnWithoutPort(String $fqdn)
|
||||
{
|
||||
$url = Url::fromString($fqdn);
|
||||
$host = $url->getHost();
|
||||
$scheme = $url->getScheme();
|
||||
$path = $url->getPath();
|
||||
return "$scheme://$host$path";
|
||||
try {
|
||||
$url = Url::fromString($fqdn);
|
||||
$host = $url->getHost();
|
||||
$scheme = $url->getScheme();
|
||||
$path = $url->getPath();
|
||||
return "$scheme://$host$path";
|
||||
} catch (\Throwable $e) {
|
||||
return $fqdn;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* If fqdn is set, return it, otherwise return public ip.
|
||||
@@ -1030,7 +1035,9 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
||||
}
|
||||
data_set($service, 'labels', $serviceLabels->toArray());
|
||||
data_forget($service, 'is_database');
|
||||
data_set($service, 'restart', RESTART_MODE);
|
||||
if (!data_get($service, 'restart')) {
|
||||
data_set($service, 'restart', RESTART_MODE);
|
||||
}
|
||||
data_set($service, 'container_name', $containerName);
|
||||
data_forget($service, 'volumes.*.content');
|
||||
data_forget($service, 'volumes.*.isDirectory');
|
||||
@@ -1330,7 +1337,8 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
||||
if ($value?->startsWith('$')) {
|
||||
$foundEnv = EnvironmentVariable::where([
|
||||
'key' => $key,
|
||||
'service_id' => $resource->id,
|
||||
'application_id' => $resource->id,
|
||||
'is_preview' => false,
|
||||
])->first();
|
||||
$value = Str::of(replaceVariables($value));
|
||||
$key = $value;
|
||||
@@ -1393,18 +1401,31 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
||||
$key = $value;
|
||||
$defaultValue = null;
|
||||
}
|
||||
$foundEnv = EnvironmentVariable::where([
|
||||
'key' => $key,
|
||||
'application_id' => $resource->id,
|
||||
'is_preview' => false,
|
||||
])->first();
|
||||
if ($foundEnv) {
|
||||
$defaultValue = data_get($foundEnv, 'value');
|
||||
}
|
||||
$isBuildTime = data_get($foundEnv, 'is_build_time', false);
|
||||
EnvironmentVariable::updateOrCreate([
|
||||
'key' => $key->value(),
|
||||
'application_id' => $resource->id,
|
||||
], [
|
||||
'value' => $defaultValue,
|
||||
'is_build_time' => $isBuildTime,
|
||||
'application_id' => $resource->id,
|
||||
]);
|
||||
if ($foundEnv) {
|
||||
$foundEnv->update([
|
||||
'key' => $key,
|
||||
'application_id' => $resource->id,
|
||||
'is_build_time' => $isBuildTime,
|
||||
'value' => $defaultValue,
|
||||
]);
|
||||
} else {
|
||||
EnvironmentVariable::create([
|
||||
'key' => $key,
|
||||
'value' => $defaultValue,
|
||||
'is_build_time' => $isBuildTime,
|
||||
'application_id' => $resource->id,
|
||||
'is_preview' => false,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1454,7 +1475,9 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
||||
}
|
||||
data_set($service, 'labels', $serviceLabels->toArray());
|
||||
data_forget($service, 'is_database');
|
||||
data_set($service, 'restart', RESTART_MODE);
|
||||
if (!data_get($service, 'restart')) {
|
||||
data_set($service, 'restart', RESTART_MODE);
|
||||
}
|
||||
data_set($service, 'container_name', $containerName);
|
||||
data_forget($service, 'volumes.*.content');
|
||||
data_forget($service, 'volumes.*.isDirectory');
|
||||
@@ -1511,6 +1534,9 @@ function parseEnvVariable(Str|string $value)
|
||||
$command = $value->after('SERVICE_')->before('_');
|
||||
$forService = $value->after('SERVICE_')->after('_')->before('_');
|
||||
$port = $value->afterLast('_');
|
||||
if (filter_var($port, FILTER_VALIDATE_INT) === false) {
|
||||
$port = null;
|
||||
}
|
||||
} else {
|
||||
// SERVICE_BASE64_64_UMAMI
|
||||
$command = $value->after('SERVICE_')->beforeLast('_');
|
||||
|
||||
991
composer.lock
generated
991
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -184,19 +184,8 @@ return [
|
||||
'connection' => 'redis',
|
||||
'queue' => ['default'],
|
||||
'balance' => 'auto',
|
||||
'autoScalingStrategy' => 'time',
|
||||
'maxProcesses' => 1,
|
||||
'maxTime' => 0,
|
||||
'maxJobs' => 0,
|
||||
'memory' => 128,
|
||||
'tries' => 1,
|
||||
'timeout' => 300,
|
||||
'nice' => 0,
|
||||
],
|
||||
'long-running' => [
|
||||
'connection' => 'redis',
|
||||
'queue' => ['long-running'],
|
||||
'balance' => 'auto',
|
||||
// 'autoScalingStrategy' => 'time',
|
||||
// 'maxProcesses' => 1,
|
||||
'maxTime' => 0,
|
||||
'maxJobs' => 0,
|
||||
'memory' => 128,
|
||||
@@ -210,28 +199,16 @@ return [
|
||||
'production' => [
|
||||
's6' => [
|
||||
'autoScalingStrategy' => 'size',
|
||||
'maxProcesses' => env('HORIZON_MAX_PROCESSES', 2),
|
||||
'maxProcesses' => env('HORIZON_MAX_PROCESSES', 6),
|
||||
'balanceMaxShift' => env('HORIZON_BALANCE_MAX_SHIFT', 1),
|
||||
'balanceCooldown' => env('HORIZON_BALANCE_COOLDOWN', 1),
|
||||
],
|
||||
'long-running' => [
|
||||
'autoScalingStrategy' => 'size',
|
||||
'maxProcesses' => env('HORIZON_MAX_PROCESSES', 2),
|
||||
'balanceMaxShift' => env('HORIZON_BALANCE_MAX_SHIFT', 1),
|
||||
'balanceCooldown' => env('HORIZON_BALANCE_COOLDOWN', 1),
|
||||
],
|
||||
],
|
||||
|
||||
],
|
||||
'local' => [
|
||||
's6' => [
|
||||
'autoScalingStrategy' => 'size',
|
||||
'maxProcesses' => env('HORIZON_MAX_PROCESSES', 2),
|
||||
'balanceMaxShift' => env('HORIZON_BALANCE_MAX_SHIFT', 1),
|
||||
'balanceCooldown' => env('HORIZON_BALANCE_COOLDOWN', 1),
|
||||
],
|
||||
'long-running' => [
|
||||
'autoScalingStrategy' => 'size',
|
||||
'maxProcesses' => env('HORIZON_MAX_PROCESSES', 2),
|
||||
'maxProcesses' => env('HORIZON_MAX_PROCESSES', 6),
|
||||
'balanceMaxShift' => env('HORIZON_BALANCE_MAX_SHIFT', 1),
|
||||
'balanceCooldown' => env('HORIZON_BALANCE_COOLDOWN', 1),
|
||||
],
|
||||
|
||||
@@ -33,14 +33,6 @@ return [
|
||||
'sync' => [
|
||||
'driver' => 'sync',
|
||||
],
|
||||
'long-running' => [
|
||||
'driver' => 'redis',
|
||||
'connection' => 'default',
|
||||
'queue' => 'long-running',
|
||||
'retry_after' => 3600,
|
||||
'block_for' => null,
|
||||
'after_commit' => true,
|
||||
],
|
||||
'database' => [
|
||||
'driver' => 'database',
|
||||
'table' => 'jobs',
|
||||
|
||||
@@ -7,7 +7,7 @@ return [
|
||||
|
||||
// The release version of your application
|
||||
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
|
||||
'release' => '4.0.0-beta.164',
|
||||
'release' => '4.0.0-beta.180',
|
||||
// When left empty or `null` the Laravel environment will be used
|
||||
'environment' => config('app.env'),
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<?php
|
||||
|
||||
return '4.0.0-beta.164';
|
||||
return '4.0.0-beta.180';
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('applications', function (Blueprint $table) {
|
||||
$table->string('docker_compose_custom_start_command')->nullable();
|
||||
$table->string('docker_compose_custom_build_command')->nullable();
|
||||
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('applications', function (Blueprint $table) {
|
||||
$table->dropColumn('docker_compose_custom_start_command');
|
||||
$table->dropColumn('docker_compose_custom_build_command');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('applications', function (Blueprint $table) {
|
||||
$table->integer('swarm_replicas')->default(1);
|
||||
$table->text('swarm_placement_constraints')->nullable();
|
||||
});
|
||||
Schema::table('application_settings', function (Blueprint $table) {
|
||||
$table->boolean('is_swarm_only_worker_nodes')->default(true);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('applications', function (Blueprint $table) {
|
||||
$table->dropColumn('swarm_replicas');
|
||||
$table->dropColumn('swarm_placement_constraints');
|
||||
});
|
||||
Schema::table('application_settings', function (Blueprint $table) {
|
||||
$table->dropColumn('is_swarm_only_worker_nodes');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('servers', function (Blueprint $table) {
|
||||
$table->integer('swarm_cluster')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('servers', function (Blueprint $table) {
|
||||
$table->dropColumn('swarm_cluster');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('environments', function (Blueprint $table) {
|
||||
$table->string('description')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('environments', function (Blueprint $table) {
|
||||
$table->dropColumn('description');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('application_settings', function (Blueprint $table) {
|
||||
$table->boolean('is_raw_compose_deployment_enabled')->default(false);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('application_settings', function (Blueprint $table) {
|
||||
$table->dropColumn('is_raw_compose_deployment_enabled');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -8,9 +8,9 @@ ARG DOCKER_COMPOSE_VERSION=2.21.0
|
||||
# https://github.com/docker/buildx/releases
|
||||
ARG DOCKER_BUILDX_VERSION=0.11.2
|
||||
# https://github.com/buildpacks/pack/releases
|
||||
ARG PACK_VERSION=0.31.0
|
||||
ARG PACK_VERSION=0.32.1
|
||||
# https://github.com/railwayapp/nixpacks/releases
|
||||
ARG NIXPACKS_VERSION=1.18.0
|
||||
ARG NIXPACKS_VERSION=1.20.0
|
||||
|
||||
USER root
|
||||
WORKDIR /artifacts
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
<div class="navbar-main">
|
||||
<a wire:navigate class="{{ request()->routeIs('project.application.configuration') ? 'text-white' : '' }}"
|
||||
<a class="{{ request()->routeIs('project.application.configuration') ? 'text-white' : '' }}"
|
||||
href="{{ route('project.application.configuration', $parameters) }}">
|
||||
<button>Configuration</button>
|
||||
</a>
|
||||
<a wire:navigate class="{{ request()->routeIs('project.application.command') ? 'text-white' : '' }}"
|
||||
href="{{ route('project.application.command', $parameters) }}">
|
||||
<button>Execute Command</button>
|
||||
</a>
|
||||
<a wire:navigate class="{{ request()->routeIs('project.application.logs') ? 'text-white' : '' }}"
|
||||
@if (!$application->destination->server->isSwarm())
|
||||
<a class="{{ request()->routeIs('project.application.command') ? 'text-white' : '' }}"
|
||||
href="{{ route('project.application.command', $parameters) }}">
|
||||
<button>Execute Command</button>
|
||||
</a>
|
||||
@endif
|
||||
<a class="{{ request()->routeIs('project.application.logs') ? 'text-white' : '' }}"
|
||||
href="{{ route('project.application.logs', $parameters) }}">
|
||||
<button>Logs</button>
|
||||
</a>
|
||||
<a wire:navigate class="{{ request()->routeIs('project.application.deployments') ? 'text-white' : '' }}"
|
||||
<a class="{{ request()->routeIs('project.application.deployments') ? 'text-white' : '' }}"
|
||||
href="{{ route('project.application.deployments', $parameters) }}">
|
||||
<button>Deployments</button>
|
||||
</a>
|
||||
@@ -19,37 +21,54 @@
|
||||
<div class="flex-1"></div>
|
||||
@if ($application->build_pack === 'dockercompose' && is_null($application->docker_compose_raw))
|
||||
<div>Please load a Compose file.</div>
|
||||
@elseif ($application->destination->server->isSwarm() && str($application->docker_registry_image_name)->isEmpty())
|
||||
Swarm Deployments requires a Docker Image in a Registry.
|
||||
@else
|
||||
<x-applications.advanced :application="$application" />
|
||||
@if (!$application->destination->server->isSwarm())
|
||||
<x-applications.advanced :application="$application" />
|
||||
@endif
|
||||
@if ($application->status !== 'exited')
|
||||
<button title="With rolling update if possible" wire:click='deploy'
|
||||
class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-orange-400" viewBox="0 0 24 24"
|
||||
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
|
||||
stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path
|
||||
d="M10.09 4.01l.496 -.495a2 2 0 0 1 2.828 0l7.071 7.07a2 2 0 0 1 0 2.83l-7.07 7.07a2 2 0 0 1 -2.83 0l-7.07 -7.07a2 2 0 0 1 0 -2.83l3.535 -3.535h-3.988">
|
||||
</path>
|
||||
<path d="M7.05 11.038v-3.988"></path>
|
||||
</svg>
|
||||
Redeploy
|
||||
</button>
|
||||
@if ($application->build_pack !== 'dockercompose')
|
||||
<button title="Restart without rebuilding" wire:click='restart'
|
||||
@if (!$application->destination->server->isSwarm())
|
||||
<button title="With rolling update if possible" wire:click='deploy'
|
||||
class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400">
|
||||
<svg class="w-5 h-5 text-warning" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||
stroke-width="2">
|
||||
<path d="M19.933 13.041a8 8 0 1 1-9.925-8.788c3.899-1 7.935 1.007 9.425 4.747" />
|
||||
<path d="M20 4v5h-5" />
|
||||
</g>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-orange-400" viewBox="0 0 24 24"
|
||||
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
|
||||
stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path
|
||||
d="M10.09 4.01l.496 -.495a2 2 0 0 1 2.828 0l7.071 7.07a2 2 0 0 1 0 2.83l-7.07 7.07a2 2 0 0 1 -2.83 0l-7.07 -7.07a2 2 0 0 1 0 -2.83l3.535 -3.535h-3.988">
|
||||
</path>
|
||||
<path d="M7.05 11.038v-3.988"></path>
|
||||
</svg>
|
||||
Restart
|
||||
Redeploy
|
||||
</button>
|
||||
@if (isDev())
|
||||
@endif
|
||||
@if ($application->build_pack !== 'dockercompose')
|
||||
@if ($application->destination->server->isSwarm())
|
||||
<button title="Redeploy Swarm Service (rolling update)" wire:click='deploy'
|
||||
class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400">
|
||||
<svg class="w-5 h-5 text-warning" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||
stroke-width="2">
|
||||
<path d="M19.933 13.041a8 8 0 1 1-9.925-8.788c3.899-1 7.935 1.007 9.425 4.747" />
|
||||
<path d="M20 4v5h-5" />
|
||||
</g>
|
||||
</svg>
|
||||
Update Service
|
||||
</button>
|
||||
@else
|
||||
<button title="Restart without rebuilding" wire:click='restart'
|
||||
class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400">
|
||||
<svg class="w-5 h-5 text-warning" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||
stroke-width="2">
|
||||
<path d="M19.933 13.041a8 8 0 1 1-9.925-8.788c3.899-1 7.935 1.007 9.425 4.747" />
|
||||
<path d="M20 4v5h-5" />
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
Restart
|
||||
</button>
|
||||
@endif
|
||||
{{-- @if (isDev())
|
||||
<button title="Restart without rebuilding" wire:click='restartNew'
|
||||
class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400">
|
||||
<svg class="w-5 h-5 text-warning" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
@@ -61,7 +80,7 @@
|
||||
</svg>
|
||||
Restart (new)
|
||||
</button>
|
||||
@endif
|
||||
@endif --}}
|
||||
@endif
|
||||
<button wire:click='stop' class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24" stroke-width="2"
|
||||
@@ -83,7 +102,7 @@
|
||||
</svg>
|
||||
Deploy
|
||||
</button>
|
||||
@if (isDev())
|
||||
{{-- @if (isDev())
|
||||
<button wire:click='deployNew'
|
||||
class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-warning" viewBox="0 0 24 24"
|
||||
@@ -94,7 +113,7 @@
|
||||
</svg>
|
||||
Deploy (new)
|
||||
</button>
|
||||
@endif
|
||||
@endif --}}
|
||||
@endif
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<div class="navbar-main">
|
||||
<a wire:navigate class="{{ request()->routeIs('project.database.configuration') ? 'text-white' : '' }}"
|
||||
<a class="{{ request()->routeIs('project.database.configuration') ? 'text-white' : '' }}"
|
||||
href="{{ route('project.database.configuration', $parameters) }}">
|
||||
<button>Configuration</button>
|
||||
</a>
|
||||
<a wire:navigate class="{{ request()->routeIs('project.database.command') ? 'text-white' : '' }}"
|
||||
<a class="{{ request()->routeIs('project.database.command') ? 'text-white' : '' }}"
|
||||
href="{{ route('project.database.command', $parameters) }}">
|
||||
<button>Execute Command</button>
|
||||
</a>
|
||||
<a wire:navigate class="{{ request()->routeIs('project.database.logs') ? 'text-white' : '' }}"
|
||||
<a class="{{ request()->routeIs('project.database.logs') ? 'text-white' : '' }}"
|
||||
href="{{ route('project.database.logs', $parameters) }}">
|
||||
<button>Logs</button>
|
||||
</a>
|
||||
@@ -16,7 +16,7 @@
|
||||
$database->getMorphClass() === 'App\Models\StandaloneMongodb' ||
|
||||
$database->getMorphClass() === 'App\Models\StandaloneMysql' ||
|
||||
$database->getMorphClass() === 'App\Models\StandaloneMariadb')
|
||||
<a wire:navigate class="{{ request()->routeIs('project.database.backups.all') ? 'text-white' : '' }}"
|
||||
<a class="{{ request()->routeIs('project.database.backups.all') ? 'text-white' : '' }}"
|
||||
href="{{ route('project.database.backups.all', $parameters) }}">
|
||||
<button>Backups</button>
|
||||
</a>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<label class="flex gap-4 px-0 cursor-pointer label">
|
||||
<span class="flex gap-2 label-text min-w-fit">
|
||||
@if ($label)
|
||||
{{ $label }}
|
||||
{!! $label !!}
|
||||
@else
|
||||
{{ $id }}
|
||||
@endif
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
class="transition rounded w-11 h-11" src="{{ asset('coolify-transparent.png') }}"></a>
|
||||
<ul class="flex flex-col h-full gap-4 menu flex-nowrap">
|
||||
<li title="Dashboard">
|
||||
<a wire:navigate class="hover:bg-transparent" href="/">
|
||||
<a class="hover:bg-transparent" href="/">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="{{ request()->is('/') ? 'text-warning icon' : 'icon' }}"
|
||||
fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
@@ -13,7 +13,7 @@
|
||||
</a>
|
||||
</li>
|
||||
<li title="Servers">
|
||||
<a wire:navigate class="hover:bg-transparent" href="/servers">
|
||||
<a class="hover:bg-transparent" href="/servers">
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
class="{{ request()->is('server/*') || request()->is('servers') ? 'text-warning icon' : 'icon' }}"
|
||||
viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
|
||||
@@ -28,7 +28,7 @@
|
||||
</a>
|
||||
</li>
|
||||
<li title="Projects">
|
||||
<a wire:navigate class="hover:bg-transparent" href="/projects">
|
||||
<a class="hover:bg-transparent" href="/projects">
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
class="{{ request()->is('project/*') || request()->is('projects') ? 'text-warning icon' : 'icon' }}"
|
||||
viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
|
||||
@@ -41,7 +41,7 @@
|
||||
</a>
|
||||
</li>
|
||||
<li title="Command Center">
|
||||
<a wire:navigate class="hover:bg-transparent" href="/command-center">
|
||||
<a class="hover:bg-transparent" href="/command-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
class="{{ request()->is('command-center') ? 'text-warning icon' : 'icon' }}" viewBox="0 0 24 24"
|
||||
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
|
||||
@@ -53,7 +53,7 @@
|
||||
</a>
|
||||
</li>
|
||||
<li title="Source">
|
||||
<a wire:navigate class="hover:bg-transparent" href="{{ route('source.all') }}">
|
||||
<a class="hover:bg-transparent" href="{{ route('source.all') }}">
|
||||
<svg class="icon" viewBox="0 0 15 15" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill="currentColor"
|
||||
d="m6.793 1.207l.353.354l-.353-.354ZM1.207 6.793l-.353-.354l.353.354Zm0 1.414l.354-.353l-.354.353Zm5.586 5.586l-.354.353l.354-.353Zm1.414 0l-.353-.354l.353.354Zm5.586-5.586l.353.354l-.353-.354Zm0-1.414l-.354.353l.354-.353ZM8.207 1.207l.354-.353l-.354.353ZM6.44.854L.854 6.439l.707.707l5.585-5.585L6.44.854ZM.854 8.56l5.585 5.585l.707-.707l-5.585-5.585l-.707.707Zm7.707 5.585l5.585-5.585l-.707-.707l-5.585 5.585l.707.707Zm5.585-7.707L8.561.854l-.707.707l5.585 5.585l.707-.707Zm0 2.122a1.5 1.5 0 0 0 0-2.122l-.707.707a.5.5 0 0 1 0 .708l.707.707ZM6.44 14.146a1.5 1.5 0 0 0 2.122 0l-.707-.707a.5.5 0 0 1-.708 0l-.707.707ZM.854 6.44a1.5 1.5 0 0 0 0 2.122l.707-.707a.5.5 0 0 1 0-.708L.854 6.44Zm6.292-4.878a.5.5 0 0 1 .708 0L8.56.854a1.5 1.5 0 0 0-2.122 0l.707.707Zm-2 1.293l1 1l.708-.708l-1-1l-.708.708ZM7.5 5a.5.5 0 0 1-.5-.5H6A1.5 1.5 0 0 0 7.5 6V5Zm.5-.5a.5.5 0 0 1-.5.5v1A1.5 1.5 0 0 0 9 4.5H8ZM7.5 4a.5.5 0 0 1 .5.5h1A1.5 1.5 0 0 0 7.5 3v1Zm0-1A1.5 1.5 0 0 0 6 4.5h1a.5.5 0 0 1 .5-.5V3Zm.646 2.854l1.5 1.5l.707-.708l-1.5-1.5l-.707.708ZM10.5 8a.5.5 0 0 1-.5-.5H9A1.5 1.5 0 0 0 10.5 9V8Zm.5-.5a.5.5 0 0 1-.5.5v1A1.5 1.5 0 0 0 12 7.5h-1Zm-.5-.5a.5.5 0 0 1 .5.5h1A1.5 1.5 0 0 0 10.5 6v1Zm0-1A1.5 1.5 0 0 0 9 7.5h1a.5.5 0 0 1 .5-.5V6ZM7 5.5v4h1v-4H7Zm.5 5.5a.5.5 0 0 1-.5-.5H6A1.5 1.5 0 0 0 7.5 12v-1Zm.5-.5a.5.5 0 0 1-.5.5v1A1.5 1.5 0 0 0 9 10.5H8Zm-.5-.5a.5.5 0 0 1 .5.5h1A1.5 1.5 0 0 0 7.5 9v1Zm0-1A1.5 1.5 0 0 0 6 10.5h1a.5.5 0 0 1 .5-.5V9Z" />
|
||||
@@ -61,7 +61,7 @@
|
||||
</a>
|
||||
</li>
|
||||
<li title="Security">
|
||||
<a wire:navigate class="hover:bg-transparent" href="{{ route('security.private-key.index') }}">
|
||||
<a class="hover:bg-transparent" href="{{ route('security.private-key.index') }}">
|
||||
<svg class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
@@ -70,7 +70,7 @@
|
||||
</a>
|
||||
</li>
|
||||
<li title="Teams">
|
||||
<a wire:navigate class="hover:bg-transparent" href="{{ route('team.index') }}">
|
||||
<a class="hover:bg-transparent" href="{{ route('team.index') }}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24" stroke-width="1.5"
|
||||
stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
@@ -103,7 +103,7 @@
|
||||
</a>
|
||||
</li>
|
||||
<li title="Profile">
|
||||
<a wire:navigate class="hover:bg-transparent" href="/profile">
|
||||
<a class="hover:bg-transparent" href="/profile">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24" stroke-width="1.5"
|
||||
stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
@@ -116,7 +116,7 @@
|
||||
|
||||
@if (isInstanceAdmin())
|
||||
<li title="Settings" class="mt-auto">
|
||||
<a wire:navigate class="hover:bg-transparent" href="/settings">
|
||||
<a class="hover:bg-transparent" href="/settings">
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
class="{{ request()->is('settings*') ? 'text-warning icon' : 'icon' }}" viewBox="0 0 24 24"
|
||||
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
|
||||
clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
<a wire:navigate class="text-xs truncate lg:text-sm"
|
||||
<a class="text-xs truncate lg:text-sm"
|
||||
href="{{ route('project.resources', ['environment_name' => $this->parameters['environment_name'], 'project_uuid' => $this->parameters['project_uuid']]) }}">{{ $this->parameters['environment_name'] }}</a>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
@@ -10,10 +10,10 @@
|
||||
</ol>
|
||||
</nav>
|
||||
<nav class="navbar-main">
|
||||
<a wire:navigate href="{{ route('security.private-key.index') }}">
|
||||
<a href="{{ route('security.private-key.index') }}">
|
||||
<button>Private Keys</button>
|
||||
</a>
|
||||
<a wire:navigate href="{{ route('security.api-tokens') }}">
|
||||
<a href="{{ route('security.api-tokens') }}">
|
||||
<button>API tokens</button>
|
||||
</a>
|
||||
<div class="flex-1"></div>
|
||||
|
||||
@@ -2,43 +2,48 @@
|
||||
<livewire:server.proxy.modal :server="$server" />
|
||||
<div class="flex items-center gap-2">
|
||||
<h1>Server</h1>
|
||||
@if ($server->proxyType() !== 'NONE')
|
||||
@if ($server->proxyType() !== 'NONE' && $server->isFunctional() && !$server->isSwarmWorker())
|
||||
<livewire:server.proxy.status :server="$server" />
|
||||
@endif
|
||||
</div>
|
||||
<div class="subtitle ">{{ data_get($server, 'name') }}</div>
|
||||
<nav class="navbar-main">
|
||||
<a wire:navigate class="{{ request()->routeIs('server.show') ? 'text-white' : '' }}"
|
||||
<a class="{{ request()->routeIs('server.show') ? 'text-white' : '' }}"
|
||||
href="{{ route('server.show', [
|
||||
'server_uuid' => data_get($parameters, 'server_uuid'),
|
||||
]) }}">
|
||||
<button>General</button>
|
||||
</a>
|
||||
<a wire:navigate class="{{ request()->routeIs('server.private-key') ? 'text-white' : '' }}"
|
||||
<a class="{{ request()->routeIs('server.private-key') ? 'text-white' : '' }}"
|
||||
href="{{ route('server.private-key', [
|
||||
'server_uuid' => data_get($parameters, 'server_uuid'),
|
||||
]) }}">
|
||||
<button>Private Key</button>
|
||||
</a>
|
||||
<a wire:navigate class="{{ request()->routeIs('server.proxy') ? 'text-white' : '' }}"
|
||||
href="{{ route('server.proxy', [
|
||||
'server_uuid' => data_get($parameters, 'server_uuid'),
|
||||
]) }}">
|
||||
<button>Proxy</button>
|
||||
</a>
|
||||
<a wire:navigate class="{{ request()->routeIs('server.destinations') ? 'text-white' : '' }}"
|
||||
href="{{ route('server.destinations', [
|
||||
'server_uuid' => data_get($parameters, 'server_uuid'),
|
||||
]) }}">
|
||||
<button>Destinations</button>
|
||||
</a>
|
||||
<a wire:navigate class="{{ request()->routeIs('server.log-drains') ? 'text-white' : '' }}"
|
||||
href="{{ route('server.log-drains', [
|
||||
'server_uuid' => data_get($parameters, 'server_uuid'),
|
||||
]) }}">
|
||||
<button>Log Drains</button>
|
||||
</a>
|
||||
@if (!$server->isSwarmWorker())
|
||||
<a class="{{ request()->routeIs('server.proxy') ? 'text-white' : '' }}"
|
||||
href="{{ route('server.proxy', [
|
||||
'server_uuid' => data_get($parameters, 'server_uuid'),
|
||||
]) }}">
|
||||
<button>Proxy</button>
|
||||
</a>
|
||||
<a class="{{ request()->routeIs('server.destinations') ? 'text-white' : '' }}"
|
||||
href="{{ route('server.destinations', [
|
||||
'server_uuid' => data_get($parameters, 'server_uuid'),
|
||||
]) }}">
|
||||
<button>Destinations</button>
|
||||
</a>
|
||||
<a class="{{ request()->routeIs('server.log-drains') ? 'text-white' : '' }}"
|
||||
href="{{ route('server.log-drains', [
|
||||
'server_uuid' => data_get($parameters, 'server_uuid'),
|
||||
]) }}">
|
||||
<button>Log Drains</button>
|
||||
</a>
|
||||
@endif
|
||||
|
||||
<div class="flex-1"></div>
|
||||
<livewire:server.proxy.deploy :server="$server" />
|
||||
@if ($server->proxyType() !== 'NONE' && $server->isFunctional() && !$server->isSwarmWorker())
|
||||
<livewire:server.proxy.deploy :server="$server" />
|
||||
@endif
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<div class="navbar-main" x-data>
|
||||
<a wire:navigate class="{{ request()->routeIs('project.service.configuration') ? 'text-white' : '' }}"
|
||||
<a class="{{ request()->routeIs('project.service.configuration') ? 'text-white' : '' }}"
|
||||
href="{{ route('project.service.configuration', $parameters) }}">
|
||||
<button>Configuration</button>
|
||||
</a>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@if (Str::of($status)->startsWith('running'))
|
||||
<x-status.running :status="$status" />
|
||||
@elseif(Str::of($status)->startsWith('restarting'))
|
||||
@elseif(Str::of($status)->startsWith('restarting') || Str::of($status)->startsWith('starting'))
|
||||
<x-status.restarting :status="$status" />
|
||||
@else
|
||||
<x-status.stopped :status="$status" />
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div class="pb-6">
|
||||
<div class="flex items-end gap-2">
|
||||
<h1>Team</h1>
|
||||
<a wire:navigate href="/team/new"><x-forms.button>+ New Team</x-forms.button></a>
|
||||
<a href="/team/new"><x-forms.button>+ New Team</x-forms.button></a>
|
||||
</div>
|
||||
<nav class="flex pt-2 pb-10">
|
||||
<ol class="inline-flex items-center">
|
||||
@@ -14,17 +14,17 @@
|
||||
</ol>
|
||||
</nav>
|
||||
<nav class="navbar-main">
|
||||
<a wire:navigate class="{{ request()->routeIs('team.index') ? 'text-white' : '' }}" href="{{ route('team.index') }}">
|
||||
<a class="{{ request()->routeIs('team.index') ? 'text-white' : '' }}" href="{{ route('team.index') }}">
|
||||
<button>General</button>
|
||||
</a>
|
||||
<a wire:navigate class="{{ request()->routeIs('team.members') ? 'text-white' : '' }}" href="{{ route('team.members') }}">
|
||||
<a class="{{ request()->routeIs('team.members') ? 'text-white' : '' }}" href="{{ route('team.members') }}">
|
||||
<button>Members</button>
|
||||
</a>
|
||||
<a wire:navigate class="{{ request()->routeIs('team.storages.all') ? 'text-white' : '' }}"
|
||||
<a class="{{ request()->routeIs('team.storages.all') ? 'text-white' : '' }}"
|
||||
href="{{ route('team.storages.all') }}">
|
||||
<button>S3 Storages</button>
|
||||
</a>
|
||||
<a wire:navigate class="{{ request()->routeIs('team.notifications') ? 'text-white' : '' }}"
|
||||
<a class="{{ request()->routeIs('team.notifications') ? 'text-white' : '' }}"
|
||||
href="{{ route('team.notifications') }}">
|
||||
<button>Notifications</button>
|
||||
</a>
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<x-layout>
|
||||
<livewire:destination.new.standalone-docker :servers="$servers" :server_id="$server_id" />
|
||||
<livewire:destination.new.docker :servers="$servers" :server_id="$server_id" />
|
||||
</x-layout>
|
||||
|
||||
@@ -29,14 +29,14 @@
|
||||
@foreach ($projects as $project)
|
||||
<div class="gap-2 border border-transparent cursor-pointer box group">
|
||||
@if (data_get($project, 'environments.0.name'))
|
||||
<a wire:navigate class="flex flex-col flex-1 mx-6 hover:no-underline"
|
||||
<a class="flex flex-col flex-1 mx-6 hover:no-underline"
|
||||
href="{{ route('project.resources', ['project_uuid' => data_get($project, 'uuid'), 'environment_name' => data_get($project, 'environments.0.name', 'production')]) }}">
|
||||
<div class="font-bold text-white">{{ $project->name }}</div>
|
||||
<div class="description">
|
||||
{{ $project->description }}</div>
|
||||
</a>
|
||||
@else
|
||||
<a wire:navigate class="flex flex-col flex-1 mx-6 hover:no-underline"
|
||||
<a class="flex flex-col flex-1 mx-6 hover:no-underline"
|
||||
href="{{ route('project.show', ['project_uuid' => data_get($project, 'uuid')]) }}">
|
||||
<div class="font-bold text-white">{{ $project->name }}</div>
|
||||
<div class="description">
|
||||
@@ -44,11 +44,11 @@
|
||||
</a>
|
||||
@endif
|
||||
<div class="flex items-center">
|
||||
<a wire:navigate class="mx-4 rounded group-hover:text-white hover:no-underline"
|
||||
<a class="mx-4 rounded group-hover:text-white hover:no-underline"
|
||||
href="{{ route('project.resources.new', ['project_uuid' => data_get($project, 'uuid'), 'environment_name' => data_get($project, 'environments.0.name', 'production')]) }}">
|
||||
<span class="font-bold hover:text-warning">+ New Resource</span>
|
||||
</a>
|
||||
<a wire:navigate class="mx-4 rounded group-hover:text-white"
|
||||
<a class="mx-4 rounded group-hover:text-white"
|
||||
href="{{ route('project.edit', ['project_uuid' => data_get($project, 'uuid')]) }}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon hover:text-warning" viewBox="0 0 24 24"
|
||||
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
|
||||
@@ -72,7 +72,7 @@
|
||||
<div class="grid grid-cols-1 gap-2 xl:grid-cols-2">
|
||||
@endif
|
||||
@foreach ($servers as $server)
|
||||
<a wire:navigate href="{{ route('server.show', ['server_uuid' => data_get($server, 'uuid')]) }}"
|
||||
<a href="{{ route('server.show', ['server_uuid' => data_get($server, 'uuid')]) }}"
|
||||
@class([
|
||||
'gap-2 border cursor-pointer box group',
|
||||
'border-transparent' => $server->settings->is_reachable,
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
<option value="{{ $server->id }}">{{ $server->name }}</option>
|
||||
@endforeach
|
||||
</x-forms.select>
|
||||
{{-- <x-forms.checkbox type="checkbox" id="is_swarm" label="Is it a Swarm network?" /> --}}
|
||||
<x-forms.button type="submit">
|
||||
Save Destination
|
||||
</x-forms.button>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user