mirror of
https://github.com/ershisan99/coolify.git
synced 2025-12-18 12:33:06 +00:00
Compare commits
61 Commits
v4.0.0-bet
...
v4.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c8b974820b | ||
|
|
527373e297 | ||
|
|
a84be8dc33 | ||
|
|
bd856f7f67 | ||
|
|
8ff216e5fb | ||
|
|
5255311a2e | ||
|
|
774a245e84 | ||
|
|
194675c838 | ||
|
|
75862ca8de | ||
|
|
5580a4e704 | ||
|
|
51e601a303 | ||
|
|
734e9fd68d | ||
|
|
09fc950ae8 | ||
|
|
cf6caa279d | ||
|
|
68c976ab70 | ||
|
|
1560ab2a50 | ||
|
|
e3a6458506 | ||
|
|
1768b9374f | ||
|
|
51d0a30a6c | ||
|
|
9701c65297 | ||
|
|
58e3bb2571 | ||
|
|
31cbd1602d | ||
|
|
dd5723d596 | ||
|
|
620f26a6f1 | ||
|
|
540717e809 | ||
|
|
d446cd4103 | ||
|
|
7d1a76570c | ||
|
|
e18766ec21 | ||
|
|
3d0354cf7e | ||
|
|
af5b9fced1 | ||
|
|
ab5202515e | ||
|
|
f863db7ea5 | ||
|
|
3adc0bdd6e | ||
|
|
97027875bf | ||
|
|
fd9c13009f | ||
|
|
46a72fac47 | ||
|
|
5d6ee04991 | ||
|
|
d523becb29 | ||
|
|
41672f75d0 | ||
|
|
acd8541e68 | ||
|
|
ed6af777a4 | ||
|
|
05f162f4e8 | ||
|
|
5e0adc3777 | ||
|
|
7d06fc4403 | ||
|
|
0e1bcceb8e | ||
|
|
a922f2fedf | ||
|
|
1e0226c8ed | ||
|
|
5c45908087 | ||
|
|
aefdc76805 | ||
|
|
b3c8c881b7 | ||
|
|
9ab5a1f7bd | ||
|
|
23968e7886 | ||
|
|
390d24b6d7 | ||
|
|
e4296345b3 | ||
|
|
bcffbe418b | ||
|
|
bfbee4e78f | ||
|
|
2bdb44dac3 | ||
|
|
4daa1b8c16 | ||
|
|
febc399568 | ||
|
|
9b9d4f9941 | ||
|
|
f49b87870c |
28
CONTRIBUTION.md
Normal file
28
CONTRIBUTION.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# Contributing
|
||||
|
||||
> "First, thanks for considering to contribute to my project.
|
||||
It really means a lot!" - [@andrasbacsai](https://github.com/andrasbacsai)
|
||||
|
||||
You can ask for guidance anytime on our
|
||||
[Discord server](https://coollabs.io/discord) in the `#contribution` channel.
|
||||
|
||||
|
||||
## 1) Setup your development environment
|
||||
|
||||
- You need to have Docker Engine (or equivalent) [installed](https://docs.docker.com/engine/install/) on your system.
|
||||
- For better DX, install [Spin](https://serversideup.net/open-source/spin/).
|
||||
|
||||
## 2) Set your environment variables
|
||||
|
||||
- Copy [.env.development.example](./.env.development.example) to .env.
|
||||
- If necessary, set `USERID` & `GROUPID` accordingly (read in .env file).
|
||||
|
||||
## 3) Start & setup Coolify
|
||||
|
||||
- Run `spin up` - You can notice that errors will be thrown. Don't worry.
|
||||
- Run `./scripts/run setup:dev` - This will generate a secret key for you, delete any existing database layouts, migrate database to the new layout, and seed your database.
|
||||
|
||||
## 4) Start development
|
||||
You can login your Coolify instance at `localhost:8000` with `test@example.com` and `password`.
|
||||
|
||||
Your horizon (Laravel scheduler): `localhost:8000/horizon` - Only reachable if you logged in with root user.
|
||||
@@ -4,12 +4,14 @@ namespace App\Actions\Service;
|
||||
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
use App\Models\Service;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
class StartService
|
||||
{
|
||||
use AsAction;
|
||||
public function handle(Service $service)
|
||||
{
|
||||
$network = $service->destination->network;
|
||||
$service->saveComposeConfigs();
|
||||
$commands[] = "cd " . $service->workdir();
|
||||
$commands[] = "echo '####### Saved configuration files to {$service->workdir()}.'";
|
||||
@@ -21,6 +23,11 @@ class StartService
|
||||
$commands[] = "echo '####### Starting containers.'";
|
||||
$commands[] = "docker compose up -d --remove-orphans --force-recreate";
|
||||
$commands[] = "docker network connect $service->uuid coolify-proxy 2>/dev/null || true";
|
||||
$compose = data_get($service,'docker_compose',[]);
|
||||
$serviceNames = data_get(Yaml::parse($compose),'services',[]);
|
||||
foreach($serviceNames as $serviceName => $serviceConfig){
|
||||
$commands[] = "docker network connect --alias {$serviceName}-{$service->uuid} $network {$serviceName}-{$service->uuid} 2>/dev/null || true";
|
||||
}
|
||||
$activity = remote_process($commands, $service->server);
|
||||
return $activity;
|
||||
}
|
||||
|
||||
100
app/Console/Commands/ResourcesDelete.php
Normal file
100
app/Console/Commands/ResourcesDelete.php
Normal file
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Application;
|
||||
use App\Models\Service;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
use function Laravel\Prompts\confirm;
|
||||
use function Laravel\Prompts\select;
|
||||
|
||||
class ResourcesDelete extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'resources:delete';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Delete a resource from the database';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$resource = select(
|
||||
'What resource do you want to delete?',
|
||||
['Application', 'Database', 'Service'],
|
||||
);
|
||||
if ($resource === 'Application') {
|
||||
$this->deleteApplication();
|
||||
} elseif ($resource === 'Database') {
|
||||
$this->deleteDatabase();
|
||||
} elseif ($resource === 'Service') {
|
||||
$this->deleteService();
|
||||
}
|
||||
}
|
||||
private function deleteApplication()
|
||||
{
|
||||
$applications = Application::all();
|
||||
if ($applications->count() === 0) {
|
||||
$this->error('There are no applications to delete.');
|
||||
return;
|
||||
}
|
||||
$application = select(
|
||||
'What application do you want to delete?',
|
||||
$applications->pluck('name')->toArray(),
|
||||
);
|
||||
$application = $applications->where('name', $application)->first();
|
||||
$confirmed = confirm("Are you sure you want to delete {$application->name}?");
|
||||
if (!$confirmed) {
|
||||
return;
|
||||
}
|
||||
$application->delete();
|
||||
}
|
||||
private function deleteDatabase()
|
||||
{
|
||||
$databases = StandalonePostgresql::all();
|
||||
if ($databases->count() === 0) {
|
||||
$this->error('There are no databases to delete.');
|
||||
return;
|
||||
}
|
||||
$database = select(
|
||||
'What database do you want to delete?',
|
||||
$databases->pluck('name')->toArray(),
|
||||
);
|
||||
$database = $databases->where('name', $database)->first();
|
||||
$confirmed = confirm("Are you sure you want to delete {$database->name}?");
|
||||
if (!$confirmed) {
|
||||
return;
|
||||
}
|
||||
$database->delete();
|
||||
}
|
||||
private function deleteService()
|
||||
{
|
||||
$services = Service::all();
|
||||
if ($services->count() === 0) {
|
||||
$this->error('There are no services to delete.');
|
||||
return;
|
||||
}
|
||||
$service = select(
|
||||
'What service do you want to delete?',
|
||||
$services->pluck('name')->toArray(),
|
||||
);
|
||||
$service = $services->where('name', $service)->first();
|
||||
$confirmed = confirm("Are you sure you want to delete {$service->name}?");
|
||||
if (!$confirmed) {
|
||||
return;
|
||||
}
|
||||
$service->delete();
|
||||
}
|
||||
}
|
||||
49
app/Console/Commands/UsersResetRoot.php
Normal file
49
app/Console/Commands/UsersResetRoot.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
|
||||
use function Laravel\Prompts\password;
|
||||
|
||||
class UsersResetRoot extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'users:reset-root';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Reset Root Password';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
//
|
||||
$this->info('You are about to reset the root password.');
|
||||
$password = password('Give me a new password for root user: ');
|
||||
$passwordAgain = password('Again');
|
||||
if ($password != $passwordAgain) {
|
||||
$this->error('Passwords do not match.');
|
||||
return;
|
||||
}
|
||||
$this->info('Updating root password...');
|
||||
try {
|
||||
User::find(0)->update(['password' => Hash::make($password)]);
|
||||
$this->info('Root password updated successfully.');
|
||||
} catch (\Exception $e) {
|
||||
$this->error('Failed to update root password.');
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ use App\Models\EnvironmentVariable;
|
||||
use App\Models\Project;
|
||||
use App\Models\Server;
|
||||
use App\Models\Service;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use App\Models\StandaloneDocker;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class ProjectController extends Controller
|
||||
@@ -75,11 +75,14 @@ class ProjectController extends Controller
|
||||
$oneClickDotEnvs = Str::of(base64_decode($oneClickDotEnvs))->split('/\r\n|\r|\n/');
|
||||
}
|
||||
if ($oneClickService) {
|
||||
$destination = StandaloneDocker::whereUuid($destination_uuid)->first();
|
||||
$service = Service::create([
|
||||
'name' => "$oneClickServiceName-" . Str::random(10),
|
||||
'docker_compose_raw' => base64_decode($oneClickService),
|
||||
'environment_id' => $environment->id,
|
||||
'server_id' => (int) $server_id,
|
||||
'destination_id' => $destination->id,
|
||||
'destination_type' => $destination->getMorphClass(),
|
||||
]);
|
||||
$service->name = "$oneClickServiceName-" . $service->uuid;
|
||||
$service->save();
|
||||
|
||||
@@ -104,13 +104,15 @@ class General extends Component
|
||||
}
|
||||
public function mount()
|
||||
{
|
||||
$this->is_static = $this->application->settings->is_static;
|
||||
$this->is_git_submodules_enabled = $this->application->settings->is_git_submodules_enabled;
|
||||
$this->is_git_lfs_enabled = $this->application->settings->is_git_lfs_enabled;
|
||||
$this->is_debug_enabled = $this->application->settings->is_debug_enabled;
|
||||
$this->is_preview_deployments_enabled = $this->application->settings->is_preview_deployments_enabled;
|
||||
$this->is_auto_deploy_enabled = $this->application->settings->is_auto_deploy_enabled;
|
||||
$this->is_force_https_enabled = $this->application->settings->is_force_https_enabled;
|
||||
if (data_get($this->application,'settings')) {
|
||||
$this->is_static = $this->application->settings->is_static;
|
||||
$this->is_git_submodules_enabled = $this->application->settings->is_git_submodules_enabled;
|
||||
$this->is_git_lfs_enabled = $this->application->settings->is_git_lfs_enabled;
|
||||
$this->is_debug_enabled = $this->application->settings->is_debug_enabled;
|
||||
$this->is_preview_deployments_enabled = $this->application->settings->is_preview_deployments_enabled;
|
||||
$this->is_auto_deploy_enabled = $this->application->settings->is_auto_deploy_enabled;
|
||||
$this->is_force_https_enabled = $this->application->settings->is_force_https_enabled;
|
||||
}
|
||||
}
|
||||
|
||||
public function submit()
|
||||
@@ -125,7 +127,7 @@ class General extends Component
|
||||
}
|
||||
if (data_get($this->application, 'dockerfile')) {
|
||||
$port = get_port_from_dockerfile($this->application->dockerfile);
|
||||
if ($port) {
|
||||
if ($port && !$this->application->ports_exposes) {
|
||||
$this->application->ports_exposes = $port;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,8 +72,7 @@ class Previews extends Component
|
||||
public function stop(int $pull_request_id)
|
||||
{
|
||||
try {
|
||||
$container_name = generateApplicationContainerName($this->application);
|
||||
ray('Stopping container: ' . $container_name);
|
||||
$container_name = generateApplicationContainerName($this->application, $pull_request_id);
|
||||
|
||||
instant_remote_process(["docker rm -f $container_name"], $this->application->destination->server, throwError: false);
|
||||
ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->delete();
|
||||
|
||||
@@ -10,7 +10,7 @@ class CreateScheduledBackup extends Component
|
||||
public $database;
|
||||
public $frequency;
|
||||
public bool $enabled = true;
|
||||
public bool $save_s3 = true;
|
||||
public bool $save_s3 = false;
|
||||
public $s3_storage_id;
|
||||
public $s3s;
|
||||
|
||||
|
||||
@@ -32,8 +32,6 @@ class DockerCompose extends Component
|
||||
- type: volume
|
||||
source: mydata
|
||||
target: /data
|
||||
volume:
|
||||
nocopy: true
|
||||
- type: bind
|
||||
source: ./var/lib/ghost/data
|
||||
target: /data
|
||||
|
||||
@@ -26,6 +26,10 @@ class PublicGitRepository extends Component
|
||||
public string $git_branch = 'main';
|
||||
public int $rate_limit_remaining = 0;
|
||||
public $rate_limit_reset = 0;
|
||||
private object $repository_url_parsed;
|
||||
public GithubApp|GitlabApp|null $git_source = null;
|
||||
public string $git_host;
|
||||
public string $git_repository;
|
||||
protected $rules = [
|
||||
'repository_url' => 'required|url',
|
||||
'port' => 'required|numeric',
|
||||
@@ -38,10 +42,6 @@ class PublicGitRepository extends Component
|
||||
'is_static' => 'static',
|
||||
'publish_directory' => 'publish directory',
|
||||
];
|
||||
private object $repository_url_parsed;
|
||||
private GithubApp|GitlabApp|null $git_source = null;
|
||||
private string $git_host;
|
||||
private string $git_repository;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
@@ -76,6 +76,7 @@ class PublicGitRepository extends Component
|
||||
$this->get_branch();
|
||||
$this->selected_branch = $this->git_branch;
|
||||
} catch (\Throwable $e) {
|
||||
ray($e->getMessage());
|
||||
if (!$this->branch_found && $this->git_branch == 'main') {
|
||||
try {
|
||||
$this->git_branch = 'master';
|
||||
@@ -123,9 +124,6 @@ class PublicGitRepository extends Component
|
||||
$project_uuid = $this->parameters['project_uuid'];
|
||||
$environment_name = $this->parameters['environment_name'];
|
||||
|
||||
$this->get_git_source();
|
||||
$this->git_branch = $this->selected_branch ?? $this->git_branch;
|
||||
|
||||
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
|
||||
if (!$destination) {
|
||||
$destination = SwarmDocker::where('uuid', $destination_uuid)->first();
|
||||
|
||||
@@ -45,6 +45,9 @@ CMD ["nginx", "-g", "daemon off;"]
|
||||
$environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first();
|
||||
|
||||
$port = get_port_from_dockerfile($this->dockerfile);
|
||||
if (!$port) {
|
||||
$port = 80;
|
||||
}
|
||||
$application = Application::create([
|
||||
'name' => 'dockerfile-' . new Cuid2(7),
|
||||
'repository_project_id' => 0,
|
||||
@@ -56,6 +59,7 @@ CMD ["nginx", "-g", "daemon off;"]
|
||||
'environment_id' => $environment->id,
|
||||
'destination_id' => $destination->id,
|
||||
'destination_type' => $destination_class,
|
||||
'health_check_enabled' => false,
|
||||
'source_id' => 0,
|
||||
'source_type' => GithubApp::class
|
||||
]);
|
||||
|
||||
@@ -21,7 +21,6 @@ class Navbar extends Component
|
||||
}
|
||||
public function serviceStatusUpdated()
|
||||
{
|
||||
ray('serviceStatusUpdated');
|
||||
$this->check_status();
|
||||
}
|
||||
public function check_status()
|
||||
|
||||
@@ -33,9 +33,6 @@ class Show extends Component
|
||||
$this->serviceDatabase = $this->service->databases()->whereName($this->parameters['service_name'])->first();
|
||||
$this->serviceDatabase->getFilesFromServer();
|
||||
}
|
||||
if (is_null($service)) {
|
||||
throw new \Exception("Service not found.");
|
||||
}
|
||||
} catch(\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
||||
40
app/Http/Livewire/Project/Shared/GetLogs.php
Normal file
40
app/Http/Livewire/Project/Shared/GetLogs.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire\Project\Shared;
|
||||
|
||||
use App\Models\Server;
|
||||
use Illuminate\Support\Facades\Process;
|
||||
use Livewire\Component;
|
||||
|
||||
class GetLogs extends Component
|
||||
{
|
||||
public string $outputs = '';
|
||||
public string $errors = '';
|
||||
public Server $server;
|
||||
public ?string $container = null;
|
||||
public ?bool $streamLogs = false;
|
||||
public int $numberOfLines = 100;
|
||||
public function doSomethingWithThisChunkOfOutput($output)
|
||||
{
|
||||
$this->outputs .= $output;
|
||||
}
|
||||
public function instantSave()
|
||||
{
|
||||
}
|
||||
public function getLogs($refresh = false)
|
||||
{
|
||||
if ($this->container) {
|
||||
$sshCommand = generateSshCommand($this->server, "docker logs -n {$this->numberOfLines} -t {$this->container}");
|
||||
if ($refresh) {
|
||||
$this->outputs = '';
|
||||
}
|
||||
Process::run($sshCommand, function (string $type, string $output) {
|
||||
$this->doSomethingWithThisChunkOfOutput($output);
|
||||
});
|
||||
}
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.project.shared.get-logs');
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ class HealthChecks extends Component
|
||||
|
||||
public $resource;
|
||||
protected $rules = [
|
||||
'resource.health_check_enabled' => 'boolean',
|
||||
'resource.health_check_path' => 'string',
|
||||
'resource.health_check_port' => 'nullable|string',
|
||||
'resource.health_check_host' => 'string',
|
||||
@@ -22,12 +23,19 @@ class HealthChecks extends Component
|
||||
'resource.health_check_start_period' => 'integer',
|
||||
|
||||
];
|
||||
public function instantSave()
|
||||
{
|
||||
$this->resource->save();
|
||||
$this->emit('success', 'Health check updated.');
|
||||
|
||||
|
||||
}
|
||||
public function submit()
|
||||
{
|
||||
try {
|
||||
$this->validate();
|
||||
$this->resource->save();
|
||||
$this->emit('saved');
|
||||
$this->emit('success', 'Health check updated.');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
||||
53
app/Http/Livewire/Project/Shared/Logs.php
Normal file
53
app/Http/Livewire/Project/Shared/Logs.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire\Project\Shared;
|
||||
|
||||
use App\Models\Application;
|
||||
use App\Models\Server;
|
||||
use App\Models\Service;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use Livewire\Component;
|
||||
|
||||
class Logs extends Component
|
||||
{
|
||||
public ?string $type = null;
|
||||
public Application|StandalonePostgresql|Service $resource;
|
||||
public Server $server;
|
||||
public ?string $container = null;
|
||||
public $parameters;
|
||||
public $query;
|
||||
public $status;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->parameters = get_route_parameters();
|
||||
$this->query = request()->query();
|
||||
if (data_get($this->parameters, 'application_uuid')) {
|
||||
$this->type = 'application';
|
||||
$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);
|
||||
if ($containers->count() > 0) {
|
||||
$this->container = data_get($containers[0], 'Names');
|
||||
}
|
||||
} else if (data_get($this->parameters, 'database_uuid')) {
|
||||
$this->type = 'database';
|
||||
$this->resource = StandalonePostgresql::where('uuid', $this->parameters['database_uuid'])->firstOrFail();
|
||||
$this->status = $this->resource->status;
|
||||
$this->server = $this->resource->destination->server;
|
||||
$this->container = $this->resource->uuid;
|
||||
} else if (data_get($this->parameters, 'service_uuid')) {
|
||||
$this->type = 'service';
|
||||
$this->resource = Service::where('uuid', $this->parameters['service_uuid'])->firstOrFail();
|
||||
$this->status = $this->resource->status;
|
||||
$this->server = $this->resource->server;
|
||||
$this->container = data_get($this->parameters, 'service_name') . '-' . $this->resource->uuid;
|
||||
}
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.project.shared.logs');
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ use Livewire\Component;
|
||||
|
||||
class Add extends Component
|
||||
{
|
||||
public $uuid;
|
||||
public $parameters;
|
||||
public string $name;
|
||||
public string $mount_path;
|
||||
@@ -31,8 +32,9 @@ class Add extends Component
|
||||
public function submit()
|
||||
{
|
||||
$this->validate();
|
||||
$name = $this->uuid . '-' . $this->name;
|
||||
$this->emitUp('submit', [
|
||||
'name' => $this->name,
|
||||
'name' => $name,
|
||||
'mount_path' => $this->mount_path,
|
||||
'host_path' => $this->host_path,
|
||||
]);
|
||||
|
||||
@@ -7,6 +7,7 @@ use Livewire\Component;
|
||||
|
||||
class All extends Component
|
||||
{
|
||||
public bool $isHeaderVisible = true;
|
||||
public $resource;
|
||||
protected $listeners = ['refreshStorages', 'submit'];
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ class Show extends Component
|
||||
public LocalPersistentVolume $storage;
|
||||
public bool $isReadOnly = false;
|
||||
public ?string $modalId = null;
|
||||
public ?string $realName = null;
|
||||
public bool $isFirst = true;
|
||||
|
||||
protected $rules = [
|
||||
'storage.name' => 'required|string',
|
||||
@@ -26,11 +26,6 @@ class Show extends Component
|
||||
|
||||
public function mount()
|
||||
{
|
||||
if ($this->storage->resource_type === 'App\Models\ServiceApplication' || $this->storage->resource_type === 'App\Models\ServiceDatabase') {
|
||||
$this->realName = "{$this->storage->service->service->uuid}_{$this->storage->name}";
|
||||
} else {
|
||||
$this->realName = $this->storage->name;
|
||||
}
|
||||
$this->modalId = new Cuid2(7);
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,9 @@ class Index extends Component
|
||||
}
|
||||
public function mount()
|
||||
{
|
||||
if (config('coolify.waitlist') == false) {
|
||||
return redirect()->route('register');
|
||||
}
|
||||
$this->waitingInLine = Waitlist::whereVerified(true)->count();
|
||||
$this->users = User::count();
|
||||
if (isDev()) {
|
||||
|
||||
@@ -89,7 +89,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$this->build_workdir = "{$this->workdir}" . rtrim($this->application->base_directory, '/');
|
||||
$this->is_debug_enabled = $this->application->settings->is_debug_enabled;
|
||||
|
||||
$this->container_name = generateApplicationContainerName($this->application);
|
||||
$this->container_name = generateApplicationContainerName($this->application, $this->pull_request_id);
|
||||
savePrivateKeyToFs($this->server);
|
||||
$this->saved_outputs = collect();
|
||||
|
||||
@@ -97,7 +97,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
if ($this->pull_request_id !== 0) {
|
||||
$this->preview = ApplicationPreview::findPreviewByApplicationAndPullId($this->application->id, $this->pull_request_id);
|
||||
if ($this->application->fqdn) {
|
||||
$preview_fqdn = getFqdnWithoutPort(data_get($this->preview, 'fqdn'));
|
||||
if (data_get($this->preview, 'fqdn')) {
|
||||
$preview_fqdn = getFqdnWithoutPort(data_get($this->preview, 'fqdn'));
|
||||
}
|
||||
$template = $this->application->preview_url_template;
|
||||
$url = Url::fromString($this->application->fqdn);
|
||||
$host = $url->getHost();
|
||||
@@ -284,7 +286,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
|
||||
private function rolling_update()
|
||||
{
|
||||
if (count($this->application->ports_mappings_array) > 0){
|
||||
if (count($this->application->ports_mappings_array) > 0) {
|
||||
$this->execute_remote_command(
|
||||
["echo -n 'Application has ports mapped to the host system, rolling update is not supported. Stopping current container.'"],
|
||||
);
|
||||
@@ -301,6 +303,10 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
}
|
||||
private function health_check()
|
||||
{
|
||||
if ($this->application->isHealthcheckDisabled()) {
|
||||
$this->newVersionIsHealthy = true;
|
||||
return;
|
||||
}
|
||||
ray('New container name: ', $this->container_name);
|
||||
if ($this->container_name) {
|
||||
$counter = 0;
|
||||
@@ -373,9 +379,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
private function prepare_builder_image()
|
||||
{
|
||||
$pull = "--pull=always";
|
||||
if (isDev()) {
|
||||
$pull = "--pull=never";
|
||||
}
|
||||
$helperImage = config('coolify.helper_image');
|
||||
$runCommand = "docker run {$pull} -d --network {$this->destination->network} -v /:/host --name {$this->deployment_uuid} --rm -v /var/run/docker.sock:/var/run/docker.sock {$helperImage}";
|
||||
|
||||
@@ -399,7 +402,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
{
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
"echo -n 'Importing {$this->application->git_repository}:{$this->application->git_branch} to {$this->workdir}. '"
|
||||
"echo -n 'Importing {$this->application->git_repository}:{$this->application->git_branch} (commit sha {$this->application->git_commit_sha}) to {$this->workdir}. '"
|
||||
],
|
||||
[
|
||||
$this->importing_git_repository()
|
||||
@@ -571,6 +574,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
]
|
||||
]
|
||||
];
|
||||
if ($this->application->isHealthcheckDisabled()) {
|
||||
data_forget($docker_compose, 'services.' . $this->container_name . '.healthcheck');
|
||||
}
|
||||
if (count($this->application->ports_mappings_array) > 0 && $this->pull_request_id === 0) {
|
||||
$docker_compose['services'][$this->container_name]['ports'] = $this->application->ports_mappings_array;
|
||||
}
|
||||
|
||||
@@ -108,9 +108,9 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$labelId = data_get($labels, 'coolify.applicationId');
|
||||
if ($labelId) {
|
||||
if (str_contains($labelId, '-pr-')) {
|
||||
$previewId = (int) Str::after($labelId, '-pr-');
|
||||
$pullRequestId = data_get($labels, 'coolify.pullRequestId');
|
||||
$applicationId = (int) Str::before($labelId, '-pr-');
|
||||
$preview = ApplicationPreview::where('application_id', $applicationId)->where('pull_request_id', $previewId)->first();
|
||||
$preview = ApplicationPreview::where('application_id', $applicationId)->where('pull_request_id', $pullRequestId)->first();
|
||||
if ($preview) {
|
||||
$foundApplicationPreviews[] = $preview->id;
|
||||
$statusFromDb = $preview->status;
|
||||
|
||||
@@ -61,7 +61,8 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
|
||||
public function handle(): void
|
||||
{
|
||||
try {
|
||||
if (data_get($this->database, 'status') !== 'running') {
|
||||
$status = Str::of(data_get($this->database, 'status'));
|
||||
if (!$status->startsWith('running') && $this->database->id !== 0) {
|
||||
ray('database not running');
|
||||
return;
|
||||
}
|
||||
@@ -89,7 +90,6 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$this->upload_to_s3();
|
||||
}
|
||||
$this->save_backup_logs();
|
||||
// TODO: Notify user
|
||||
} catch (\Throwable $e) {
|
||||
ray($e->getMessage());
|
||||
send_internal_notification('DatabaseBackupJob failed with: ' . $e->getMessage());
|
||||
|
||||
@@ -18,10 +18,16 @@ class Application extends BaseModel
|
||||
]);
|
||||
});
|
||||
static::deleting(function ($application) {
|
||||
// Stop Container
|
||||
instant_remote_process(
|
||||
["docker rm -f {$application->uuid}"],
|
||||
$application->destination->server,
|
||||
false
|
||||
);
|
||||
$application->settings()->delete();
|
||||
$storages = $application->persistentStorages()->get();
|
||||
foreach ($storages as $storage) {
|
||||
instant_remote_process(["docker volume rm -f $storage->name"], $application->destination->server);
|
||||
instant_remote_process(["docker volume rm -f $storage->name"], $application->destination->server, false);
|
||||
}
|
||||
$application->persistentStorages()->delete();
|
||||
$application->environment_variables()->delete();
|
||||
@@ -231,4 +237,12 @@ class Application extends BaseModel
|
||||
}
|
||||
return true;
|
||||
}
|
||||
public function isHealthcheckDisabled(): bool
|
||||
{
|
||||
if (data_get($this, 'dockerfile') || data_get($this, 'build_pack') === 'dockerfile' || data_get($this, 'health_check_enabled') === false) {
|
||||
ray('dockerfile');
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
use Illuminate\Support\Str;
|
||||
use Spatie\Url\Url;
|
||||
|
||||
class Service extends BaseModel
|
||||
{
|
||||
@@ -20,6 +19,7 @@ class Service extends BaseModel
|
||||
static::deleted(function ($service) {
|
||||
$storagesToDelete = collect([]);
|
||||
foreach ($service->applications()->get() as $application) {
|
||||
instant_remote_process(["docker rm -f {$application->name}-{$service->uuid}"], $service->server, false);
|
||||
$storages = $application->persistentStorages()->get();
|
||||
foreach ($storages as $storage) {
|
||||
$storagesToDelete->push($storage);
|
||||
@@ -27,6 +27,7 @@ class Service extends BaseModel
|
||||
$application->persistentStorages()->delete();
|
||||
}
|
||||
foreach ($service->databases()->get() as $database) {
|
||||
instant_remote_process(["docker rm -f {$database->name}-{$service->uuid}"], $service->server, false);
|
||||
$storages = $database->persistentStorages()->get();
|
||||
foreach ($storages as $storage) {
|
||||
$storagesToDelete->push($storage);
|
||||
@@ -63,6 +64,10 @@ class Service extends BaseModel
|
||||
{
|
||||
return $this->hasMany(ServiceDatabase::class);
|
||||
}
|
||||
public function destination()
|
||||
{
|
||||
return $this->morphTo();
|
||||
}
|
||||
public function environment()
|
||||
{
|
||||
return $this->belongsTo(Environment::class);
|
||||
@@ -124,9 +129,16 @@ class Service extends BaseModel
|
||||
$topLevelNetworks = collect(data_get($yaml, 'networks', []));
|
||||
$dockerComposeVersion = data_get($yaml, 'version') ?? '3.8';
|
||||
$services = data_get($yaml, 'services');
|
||||
$definedNetwork = $this->uuid;
|
||||
|
||||
$generatedServiceFQDNS = collect([]);
|
||||
if (is_null($this->destination)) {
|
||||
$destination = $this->server->destinations()->first();
|
||||
if ($destination) {
|
||||
$this->destination()->associate($destination);
|
||||
$this->save();
|
||||
}
|
||||
}
|
||||
$definedNetwork = collect([$this->uuid]);
|
||||
|
||||
$services = collect($services)->map(function ($service, $serviceName) use ($topLevelVolumes, $topLevelNetworks, $definedNetwork, $isNew, $generatedServiceFQDNS) {
|
||||
$serviceVolumes = collect(data_get($service, 'volumes', []));
|
||||
@@ -237,18 +249,24 @@ class Service extends BaseModel
|
||||
return $value == $definedNetwork;
|
||||
});
|
||||
if (!$definedNetworkExists) {
|
||||
$topLevelNetworks->put($definedNetwork, [
|
||||
'name' => $definedNetwork,
|
||||
'external' => true
|
||||
]);
|
||||
foreach ($definedNetwork as $network) {
|
||||
$topLevelNetworks->put($network, [
|
||||
'name' => $network,
|
||||
'external' => true
|
||||
]);
|
||||
}
|
||||
}
|
||||
$networks = $serviceNetworks->toArray();
|
||||
$networks = array_merge($networks, [$definedNetwork]);
|
||||
foreach ($definedNetwork as $key => $network) {
|
||||
$networks = array_merge($networks, [
|
||||
$network
|
||||
]);
|
||||
}
|
||||
data_set($service, 'networks', $networks);
|
||||
|
||||
// Collect/create/update volumes
|
||||
if ($serviceVolumes->count() > 0) {
|
||||
foreach ($serviceVolumes as $volume) {
|
||||
$serviceVolumes = $serviceVolumes->map(function ($volume) use ($savedService, $topLevelVolumes, $isNew) {
|
||||
$type = null;
|
||||
$source = null;
|
||||
$target = null;
|
||||
@@ -270,16 +288,19 @@ class Service extends BaseModel
|
||||
$isDirectory = (bool) data_get($volume, 'isDirectory', false);
|
||||
$foundConfig = $savedService->fileStorages()->whereMountPath($target)->first();
|
||||
if ($foundConfig) {
|
||||
$content = data_get($foundConfig, 'content');
|
||||
$contentNotNull = data_get($foundConfig, 'content');
|
||||
if ($contentNotNull) {
|
||||
$content = $contentNotNull;
|
||||
}
|
||||
$isDirectory = (bool) data_get($foundConfig, 'is_directory');
|
||||
}
|
||||
}
|
||||
if ($type->value() === 'bind') {
|
||||
if ($source->value() === "/var/run/docker.sock") {
|
||||
continue;
|
||||
return $volume;
|
||||
}
|
||||
if ($source->value() === '/tmp' || $source->value() === '/tmp/') {
|
||||
continue;
|
||||
return $volume;
|
||||
}
|
||||
LocalFileVolume::updateOrCreate(
|
||||
[
|
||||
@@ -297,7 +318,19 @@ class Service extends BaseModel
|
||||
]
|
||||
);
|
||||
} else if ($type->value() === 'volume') {
|
||||
$topLevelVolumes->put($source->value(), null);
|
||||
$slugWithoutUuid = Str::slug($source, '-');
|
||||
$name = "{$savedService->service->uuid}_{$slugWithoutUuid}";
|
||||
if (is_string($volume)) {
|
||||
$source = Str::of($volume)->before(':');
|
||||
$target = Str::of($volume)->after(':')->beforeLast(':');
|
||||
$source = $name;
|
||||
$volume = "$source:$target";
|
||||
} else if (is_array($volume)) {
|
||||
data_set($volume, 'source', $name);
|
||||
}
|
||||
$topLevelVolumes->put($name, [
|
||||
'name' => $name,
|
||||
]);
|
||||
LocalPersistentVolume::updateOrCreate(
|
||||
[
|
||||
'mount_path' => $target,
|
||||
@@ -305,15 +338,17 @@ class Service extends BaseModel
|
||||
'resource_type' => get_class($savedService)
|
||||
],
|
||||
[
|
||||
'name' => Str::slug($source, '-'),
|
||||
'name' => $name,
|
||||
'mount_path' => $target,
|
||||
'resource_id' => $savedService->id,
|
||||
'resource_type' => get_class($savedService)
|
||||
]
|
||||
);
|
||||
}
|
||||
$savedService->getFilesFromServer();
|
||||
}
|
||||
$savedService->getFilesFromServer(isInit: true);
|
||||
return $volume;
|
||||
});
|
||||
data_set($service, 'volumes', $serviceVolumes->toArray());
|
||||
}
|
||||
|
||||
// Add env_file with at least .env to the service
|
||||
|
||||
@@ -36,8 +36,8 @@ class ServiceApplication extends BaseModel
|
||||
|
||||
);
|
||||
}
|
||||
public function getFilesFromServer()
|
||||
public function getFilesFromServer(bool $isInit = false)
|
||||
{
|
||||
getFilesystemVolumesFromServer($this);
|
||||
getFilesystemVolumesFromServer($this, $isInit);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,8 +26,8 @@ class ServiceDatabase extends BaseModel
|
||||
{
|
||||
return $this->morphMany(LocalFileVolume::class, 'resource');
|
||||
}
|
||||
public function getFilesFromServer()
|
||||
public function getFilesFromServer(bool $isInit = false)
|
||||
{
|
||||
getFilesystemVolumesFromServer($this);
|
||||
getFilesystemVolumesFromServer($this, $isInit);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,9 +29,20 @@ class StandalonePostgresql extends BaseModel
|
||||
]);
|
||||
});
|
||||
static::deleted(function ($database) {
|
||||
// Stop Container
|
||||
instant_remote_process(
|
||||
["docker rm -f {$database->uuid}"],
|
||||
$database->destination->server,
|
||||
false
|
||||
);
|
||||
// Stop TCP Proxy
|
||||
if ($database->is_public) {
|
||||
instant_remote_process(["docker rm -f {$database->uuid}-proxy"], $database->destination->server, false);
|
||||
}
|
||||
$database->scheduledBackups()->delete();
|
||||
$database->persistentStorages()->delete();
|
||||
$database->environment_variables()->delete();
|
||||
// Remove Volume
|
||||
instant_remote_process(['docker volume rm postgres-data-' . $database->uuid], $database->destination->server, false);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -104,16 +104,16 @@ function getContainerStatus(Server $server, string $container_id, bool $all_data
|
||||
return data_get($container[0], 'State.Status', 'exited');
|
||||
}
|
||||
|
||||
function generateApplicationContainerName(Application $application)
|
||||
function generateApplicationContainerName(Application $application, $pull_request_id = 0)
|
||||
{
|
||||
$now = now()->format('Hisu');
|
||||
if ($application->pull_request_id !== 0 && $application->pull_request_id !== null) {
|
||||
return $application->uuid . '-pr-' . $application->pull_request_id;
|
||||
if ($pull_request_id !== 0 && $pull_request_id !== null) {
|
||||
return $application->uuid . '-pr-' . $pull_request_id;
|
||||
} else {
|
||||
return $application->uuid . '-' . $now;
|
||||
}
|
||||
}
|
||||
function get_port_from_dockerfile($dockerfile): int
|
||||
function get_port_from_dockerfile($dockerfile): int|null
|
||||
{
|
||||
$dockerfile_array = explode("\n", $dockerfile);
|
||||
$found_exposed_port = null;
|
||||
@@ -127,7 +127,7 @@ function get_port_from_dockerfile($dockerfile): int
|
||||
if ($found_exposed_port) {
|
||||
return (int)$found_exposed_port->value();
|
||||
}
|
||||
return 80;
|
||||
return null;
|
||||
}
|
||||
|
||||
function defaultLabels($id, $name, $pull_request_id = 0, string $type = 'application', $subType = null, $subId = null)
|
||||
@@ -207,10 +207,10 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
|
||||
{
|
||||
|
||||
$pull_request_id = data_get($preview, 'pull_request_id', 0);
|
||||
$container_name = generateApplicationContainerName($application);
|
||||
$container_name = generateApplicationContainerName($application, $pull_request_id);
|
||||
$appId = $application->id;
|
||||
if ($pull_request_id !== 0) {
|
||||
$appId = $appId . '-pr-' . $application->pull_request_id;
|
||||
if ($pull_request_id !== 0 && $pull_request_id !== null) {
|
||||
$appId = $appId . '-pr-' . $pull_request_id;
|
||||
}
|
||||
$labels = collect([]);
|
||||
$labels = $labels->merge(defaultLabels($appId, $container_name, $pull_request_id));
|
||||
|
||||
@@ -64,7 +64,7 @@ function serviceStatus(Service $service)
|
||||
}
|
||||
return 'exited';
|
||||
}
|
||||
function getFilesystemVolumesFromServer(ServiceApplication|ServiceDatabase $oneService)
|
||||
function getFilesystemVolumesFromServer(ServiceApplication|ServiceDatabase $oneService, bool $isInit = false)
|
||||
{
|
||||
// TODO: make this async
|
||||
try {
|
||||
@@ -87,6 +87,10 @@ function getFilesystemVolumesFromServer(ServiceApplication|ServiceDatabase $oneS
|
||||
}
|
||||
$isFile = instant_remote_process(["test -f $fileLocation && echo OK || echo NOK"], $server);
|
||||
$isDir = instant_remote_process(["test -d $fileLocation && echo OK || echo NOK"], $server);
|
||||
if ($isFile === 'NOK' &&!$fileVolume->is_directory && $isInit) {
|
||||
$fileVolume->saveStorageOnServer($oneService);
|
||||
continue;
|
||||
}
|
||||
if ($isFile == 'OK' && !$fileVolume->is_directory) {
|
||||
$filesystemContent = instant_remote_process(["cat $fileLocation"], $server);
|
||||
if (base64_encode($filesystemContent) != base64_encode($content)) {
|
||||
|
||||
@@ -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.51',
|
||||
'release' => '4.0.0-beta.62',
|
||||
// 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.51';
|
||||
return '4.0.0-beta.62';
|
||||
|
||||
@@ -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('applications', function (Blueprint $table) {
|
||||
$table->boolean('health_check_enabled')->default(true);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('applications', function (Blueprint $table) {
|
||||
$table->dropColumn('health_check_enabled');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -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('services', function (Blueprint $table) {
|
||||
$table->nullableMorphs('destination');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('services', function (Blueprint $table) {
|
||||
$table->dropMorphs('destination');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -34,14 +34,14 @@ services:
|
||||
POSTGRES_DB: "${DB_DATABASE:-coolify}"
|
||||
POSTGRES_HOST_AUTH_METHOD: "trust"
|
||||
volumes:
|
||||
- /data/coolify/_volumes/database/:/var/lib/postgresql/data
|
||||
- ./_data/coolify/_volumes/database/:/var/lib/postgresql/data
|
||||
redis:
|
||||
ports:
|
||||
- "${FORWARD_REDIS_PORT:-6379}:6379"
|
||||
env_file:
|
||||
- .env
|
||||
volumes:
|
||||
- /data/coolify/_volumes/redis/:/data
|
||||
- ./_data/coolify/_volumes/redis/:/data
|
||||
vite:
|
||||
image: node:19
|
||||
working_dir: /var/www/html
|
||||
@@ -56,7 +56,7 @@ services:
|
||||
volumes:
|
||||
- /:/host
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- /data/coolify/:/data/coolify
|
||||
- ./_data/coolify/:/data/coolify
|
||||
mailpit:
|
||||
image: "axllent/mailpit:latest"
|
||||
container_name: coolify-mail
|
||||
@@ -76,6 +76,6 @@ services:
|
||||
MINIO_ACCESS_KEY: "${MINIO_ACCESS_KEY:-minioadmin}"
|
||||
MINIO_SECRET_KEY: "${MINIO_SECRET_KEY:-minioadmin}"
|
||||
volumes:
|
||||
- /data/coolify/_volumes/minio/:/data
|
||||
- ./_data/coolify/_volumes/minio/:/data
|
||||
networks:
|
||||
- coolify
|
||||
|
||||
@@ -7,6 +7,10 @@
|
||||
href="{{ route('project.application.deployments', $parameters) }}">
|
||||
<button>Deployments</button>
|
||||
</a>
|
||||
<a class="{{ request()->routeIs('project.application.logs') ? 'text-white' : '' }}"
|
||||
href="{{ route('project.application.logs', $parameters) }}">
|
||||
<button>Logs</button>
|
||||
</a>
|
||||
<x-applications.links :application="$application" />
|
||||
<div class="flex-1"></div>
|
||||
<x-applications.advanced :application="$application" />
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
href="{{ route('project.database.configuration', $parameters) }}">
|
||||
<button>Configuration</button>
|
||||
</a>
|
||||
<a class="{{ request()->routeIs('project.database.logs') ? 'text-white' : '' }}"
|
||||
href="{{ route('project.database.logs', $parameters) }}">
|
||||
<button>Logs</button>
|
||||
</a>
|
||||
<a class="{{ request()->routeIs('project.database.backups.all') ? 'text-white' : '' }}"
|
||||
href="{{ route('project.database.backups.all', $parameters) }}">
|
||||
<button>Backups</button>
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
<div class="navbar-main">
|
||||
<a class="{{ request()->routeIs('project.service') ? 'text-white' : '' }}"
|
||||
href="{{ route('project.service', $parameters) }}">
|
||||
<button>Configuration</button>
|
||||
</a>
|
||||
<x-services.links :service="$service" />
|
||||
<div class="flex-1"></div>
|
||||
@if (serviceStatus($service) === 'degraded')
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
<x-boarding-step title="Server">
|
||||
<x-slot:question>
|
||||
Do you want to deploy your resources on your <x-highlighted text="Localhost" />
|
||||
or on a<x-highlighted text="Remote Server" />?
|
||||
or on a <x-highlighted text="Remote Server" />?
|
||||
</x-slot:question>
|
||||
<x-slot:actions>
|
||||
<x-forms.button class="justify-center box" wire:target="setServerType('localhost')"
|
||||
|
||||
@@ -54,11 +54,11 @@
|
||||
<div class="flex flex-col p-4 bg-coolgray-200">
|
||||
<div class="flex gap-2">PR #{{ data_get($preview, 'pull_request_id') }} |
|
||||
@if (Str::of(data_get($preview, 'status'))->startsWith('running'))
|
||||
<x-status.running :status="$status" />
|
||||
<x-status.running :status="data_get($preview, 'status')" />
|
||||
@elseif(Str::of(data_get($preview, 'status'))->startsWith('restarting'))
|
||||
<x-status.restarting :status="$status" />
|
||||
<x-status.restarting :status="data_get($preview, 'status')" />
|
||||
@else
|
||||
<x-status.stopped :status="$status" />
|
||||
<x-status.stopped :status="data_get($preview, 'status')" />
|
||||
@endif
|
||||
@if (data_get($preview, 'status') !== 'exited')
|
||||
| <a target="_blank" href="{{ data_get($preview, 'fqdn') }}">Open Preview
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
<dialog id="composeModal" class="modal" x-data="{ raw: true }">
|
||||
<form method="dialog" class="flex flex-col gap-2 rounded max-w-7xl modal-box" wire:submit.prevent='submit'>
|
||||
<div class="flex items-end gap-2">
|
||||
<h1>Docker Compose</h1>
|
||||
<div x-cloak x-show="raw">
|
||||
<x-forms.button class="w-64" @click.prevent="raw = !raw">Check Deployable Compose</x-forms.button>
|
||||
<x-forms.button class="w-64" @click.prevent="raw = !raw">Show Deployable Compose</x-forms.button>
|
||||
</div>
|
||||
<div x-cloak x-show="raw === false">
|
||||
<x-forms.button class="w-64" @click.prevent="raw = !raw">Show Source
|
||||
Compose</x-forms.button>
|
||||
</div>
|
||||
</div>
|
||||
<div>Volume names are updated upon save. The service UUID will be added as a prefix to all volumes, to prevent name collision. <br>To see the actual volume names, check the Deployable Compose file, or go to Storage menu.</div>
|
||||
|
||||
<div x-cloak x-show="raw">
|
||||
<x-forms.textarea rows="20" id="raw">
|
||||
</x-forms.textarea>
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
<a :class="activeTab === 'service-stack' && 'text-white'"
|
||||
@click.prevent="activeTab = 'service-stack'; window.location.hash = 'service-stack'"
|
||||
href="#">Service Stack</a>
|
||||
<a :class="activeTab === 'storages' && 'text-white'"
|
||||
@click.prevent="activeTab = 'storages'; window.location.hash = 'storages'" href="#">Storages</a>
|
||||
<a :class="activeTab === 'environment-variables' && 'text-white'"
|
||||
@click.prevent="activeTab = 'environment-variables'; window.location.hash = 'environment-variables'"
|
||||
href="#">Environment
|
||||
@@ -24,7 +26,8 @@
|
||||
<div>Configuration</div>
|
||||
</div>
|
||||
<x-forms.button type="submit">Save</x-forms.button>
|
||||
<x-forms.button class="w-64" onclick="composeModal.showModal()">Edit Compose File</x-forms.button>
|
||||
<x-forms.button class="w-64" onclick="composeModal.showModal()">Edit Compose
|
||||
File</x-forms.button>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<x-forms.input id="service.name" required label="Service Name"
|
||||
@@ -33,61 +36,110 @@
|
||||
</div>
|
||||
</form>
|
||||
<div class="grid grid-cols-1 gap-2 pt-4 xl:grid-cols-3">
|
||||
@foreach ($service->applications as $application)
|
||||
<a @class([
|
||||
@foreach ($applications as $application)
|
||||
<div @class([
|
||||
'border-l border-dashed border-red-500' => Str::of(
|
||||
$application->status)->contains(['exited']),
|
||||
'border-l border-dashed border-success' => Str::of(
|
||||
$application->status)->contains(['running']),
|
||||
'border-l border-dashed border-warning' => Str::of(
|
||||
$application->status)->contains(['starting']),
|
||||
'flex flex-col justify-center box',
|
||||
])
|
||||
href="{{ route('project.service.show', [...$parameters, 'service_name' => $application->name]) }}">
|
||||
@if ($application->human_name)
|
||||
{{ Str::headline($application->human_name) }}
|
||||
@else
|
||||
{{ Str::headline($application->name) }}
|
||||
@endif
|
||||
@if ($application->configuration_required)
|
||||
<span class="text-xs text-error">(configuration required)</span>
|
||||
@endif
|
||||
@if ($application->description)
|
||||
<span class="text-xs">{{ Str::limit($application->description, 60) }}</span>
|
||||
@endif
|
||||
@if ($application->fqdn)
|
||||
<span class="text-xs">{{ Str::limit($application->fqdn, 60) }}</span>
|
||||
@endif
|
||||
<div class="text-xs">{{ $application->status }}</div>
|
||||
</a>
|
||||
'flex gap-2 box group',
|
||||
])>
|
||||
<a class="flex flex-col flex-1 group-hover:text-white hover:no-underline"
|
||||
href="{{ route('project.service.show', [...$parameters, 'service_name' => $application->name]) }}">
|
||||
|
||||
@if ($application->human_name)
|
||||
{{ Str::headline($application->human_name) }}
|
||||
@else
|
||||
{{ Str::headline($application->name) }}
|
||||
@endif
|
||||
@if ($application->configuration_required)
|
||||
<span class="text-xs text-error">(configuration required)</span>
|
||||
@endif
|
||||
@if ($application->description)
|
||||
<span class="text-xs">{{ Str::limit($application->description, 60) }}</span>
|
||||
@endif
|
||||
@if ($application->fqdn)
|
||||
<span class="text-xs">{{ Str::limit($application->fqdn, 60) }}</span>
|
||||
@endif
|
||||
<div class="text-xs">{{ $application->status }}</div>
|
||||
</a>
|
||||
<a class="flex gap-2 p-1 mx-4 text-xs font-bold rounded hover:no-underline hover:text-warning"
|
||||
href="{{ route('project.service.logs', [...$parameters, 'service_name' => $application->name]) }}">Logs</a>
|
||||
</div>
|
||||
@endforeach
|
||||
@foreach ($databases as $database)
|
||||
<a @class([
|
||||
<div @class([
|
||||
'border-l border-dashed border-red-500' => Str::of(
|
||||
$database->status)->contains(['exited']),
|
||||
'border-l border-dashed border-success' => Str::of(
|
||||
$database->status)->contains(['running']),
|
||||
'border-l border-dashed border-warning' => Str::of(
|
||||
$database->status)->contains(['restarting']),
|
||||
'flex flex-col justify-center box',
|
||||
])
|
||||
href="{{ route('project.service.show', [...$parameters, 'service_name' => $database->name]) }}">
|
||||
@if ($database->human_name)
|
||||
{{ Str::headline($database->human_name) }}
|
||||
@else
|
||||
{{ Str::headline($database->name) }}
|
||||
@endif
|
||||
@if ($database->configuration_required)
|
||||
<span class="text-xs text-error">(configuration required)</span>
|
||||
@endif
|
||||
@if ($database->description)
|
||||
<span class="text-xs">{{ Str::limit($database->description, 60) }}</span>
|
||||
@endif
|
||||
<div class="text-xs">{{ $database->status }}</div>
|
||||
</a>
|
||||
'flex gap-2 box group',
|
||||
])>
|
||||
<a class="flex flex-col flex-1 group-hover:text-white hover:no-underline"
|
||||
href="{{ route('project.service.show', [...$parameters, 'service_name' => $database->name]) }}">
|
||||
@if ($database->human_name)
|
||||
{{ Str::headline($database->human_name) }}
|
||||
@else
|
||||
{{ Str::headline($database->name) }}
|
||||
@endif
|
||||
@if ($database->configuration_required)
|
||||
<span class="text-xs text-error">(configuration required)</span>
|
||||
@endif
|
||||
@if ($database->description)
|
||||
<span class="text-xs">{{ Str::limit($database->description, 60) }}</span>
|
||||
@endif
|
||||
<div class="text-xs">{{ $database->status }}</div>
|
||||
</a>
|
||||
<a class="flex gap-2 p-1 mx-4 text-xs font-bold rounded hover:no-underline hover:text-warning"
|
||||
href="{{ route('project.service.logs', [...$parameters, 'service_name' => $database->name]) }}">Logs</a>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div x-cloak x-show="activeTab === 'storages'">
|
||||
@foreach ($applications as $application)
|
||||
@if ($loop->first)
|
||||
<livewire:project.shared.storages.all :resource="$application" />
|
||||
@else
|
||||
<livewire:project.shared.storages.all :resource="$application" :isHeaderVisible="false" />
|
||||
@endif
|
||||
@if ($application->fileStorages()->get()->count() > 0)
|
||||
<h5 class="py-4">Mounted Files/Dirs (binds)</h5>
|
||||
<div class="flex flex-col gap-4">
|
||||
@foreach ($application->fileStorages()->get()->sort() as $fileStorage)
|
||||
<livewire:project.service.file-storage :fileStorage="$fileStorage"
|
||||
wire:key="{{ $loop->index }}" />
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
@endforeach
|
||||
@foreach ($databases as $database)
|
||||
@if ($loop->first)
|
||||
<h3 class="pt-4">{{ Str::headline($database->name) }}</h3>
|
||||
@if ($applications->count() > 0)
|
||||
<livewire:project.shared.storages.all :resource="$database" :isHeaderVisible="false" />
|
||||
@else
|
||||
<livewire:project.shared.storages.all :resource="$database" />
|
||||
@endif
|
||||
@if ($database->fileStorages()->get()->count() > 0)
|
||||
<h5 class="py-4">Mounted Files/Dirs (binds)</h5>
|
||||
<div class="flex flex-col gap-4">
|
||||
@foreach ($database->fileStorages()->get()->sort() as $fileStorage)
|
||||
<livewire:project.service.file-storage :fileStorage="$fileStorage"
|
||||
wire:key="{{ $loop->index }}" />
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
@else
|
||||
<livewire:project.shared.storages.all :resource="$database" :isHeaderVisible="false" />
|
||||
@endif
|
||||
@endforeach
|
||||
|
||||
</div>
|
||||
<div x-cloak x-show="activeTab === 'environment-variables'">
|
||||
<div x-cloak x-show="activeTab === 'environment-variables'">
|
||||
|
||||
@@ -11,6 +11,12 @@
|
||||
<a :class="activeTab === 'storages' && 'text-white'"
|
||||
@click.prevent="activeTab = 'storages'; window.location.hash = 'storages'" href="#">Storages
|
||||
</a>
|
||||
@if (data_get($parameters, 'service_name'))
|
||||
<a class="{{ request()->routeIs('project.service.logs') ? 'text-white' : '' }}"
|
||||
href="{{ route('project.service.logs', $parameters) }}">
|
||||
<button>Logs</button>
|
||||
</a>
|
||||
@endif
|
||||
</div>
|
||||
<div class="w-full pl-8">
|
||||
@isset($serviceApplication)
|
||||
@@ -20,7 +26,7 @@
|
||||
<div x-cloak x-show="activeTab === 'storages'">
|
||||
<livewire:project.shared.storages.all :resource="$serviceApplication" />
|
||||
@if ($serviceApplication->fileStorages()->get()->count() > 0)
|
||||
<h3 class="py-4">Mounted Files (binds)</h3>
|
||||
<h5 class="py-4">Mounted Files/Dirs (binds)</h5>
|
||||
<div class="flex flex-col gap-4">
|
||||
@foreach ($serviceApplication->fileStorages()->get()->sort() as $fileStorage)
|
||||
<livewire:project.service.file-storage :fileStorage="$fileStorage" wire:key="{{ $loop->index }}" />
|
||||
@@ -32,12 +38,11 @@
|
||||
@isset($serviceDatabase)
|
||||
<div x-cloak x-show="activeTab === 'general'" class="h-full">
|
||||
<livewire:project.service.database :database="$serviceDatabase" />
|
||||
|
||||
</div>
|
||||
<div x-cloak x-show="activeTab === 'storages'">
|
||||
<livewire:project.shared.storages.all :resource="$serviceDatabase" />
|
||||
@if ($serviceDatabase->fileStorages()->get()->count() > 0)
|
||||
<h3 class="py-4">Mounted Files (binds)</h3>
|
||||
<h5 class="py-4">Mounted Files/Dirs (binds)</h5>
|
||||
<div class="flex flex-col gap-4">
|
||||
@foreach ($serviceDatabase->fileStorages()->get()->sort() as $fileStorage)
|
||||
<livewire:project.service.file-storage :fileStorage="$fileStorage" wire:key="{{ $loop->index }}" />
|
||||
|
||||
22
resources/views/livewire/project/shared/get-logs.blade.php
Normal file
22
resources/views/livewire/project/shared/get-logs.blade.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<div x-init="$wire.getLogs">
|
||||
<div class="flex gap-2">
|
||||
<h2>Logs</h2>
|
||||
@if ($streamLogs)
|
||||
<span wire:poll.2000ms='getLogs(true)' class="loading loading-xs text-warning loading-spinner"></span>
|
||||
@endif
|
||||
</div>
|
||||
<form wire:submit.prevent='getLogs(true)' class="flex items-end gap-2">
|
||||
<x-forms.input label="Only Show Number of Lines" placeholder="1000" required id="numberOfLines"></x-forms.input>
|
||||
<x-forms.button type="submit">Refresh</x-forms.button>
|
||||
</form>
|
||||
<div class="w-32">
|
||||
<x-forms.checkbox instantSave label="Stream Logs" id="streamLogs"></x-forms.checkbox>
|
||||
</div>
|
||||
<div class="container w-full pt-4 mx-auto">
|
||||
<div
|
||||
class="scrollbar flex flex-col-reverse w-full overflow-y-auto border border-solid rounded border-coolgray-300 max-h-[32rem] p-4 pt-6 text-xs text-white">
|
||||
|
||||
<pre class="font-mono whitespace-pre-wrap">{{ $outputs }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -5,24 +5,27 @@
|
||||
</div>
|
||||
<div class="pb-4">Define how your resource's health should be checked.</div>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex gap-2">
|
||||
<x-forms.input id="resource.health_check_method" placeholder="GET" label="Method" required />
|
||||
<div class="w-32">
|
||||
<x-forms.checkbox instantSave id="resource.health_check_enabled" label="Enabled" />
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<x-forms.input id="resource.health_check_method" placeholder="GET" label="Method" required />
|
||||
|
||||
<x-forms.input id="resource.health_check_scheme" placeholder="http" label="Scheme" required />
|
||||
<x-forms.input id="resource.health_check_host" placeholder="localhost" label="Host" required />
|
||||
<x-forms.input id="resource.health_check_port"
|
||||
helper="If no port is defined, the first exposed port will be used." placeholder="80" label="Port" />
|
||||
<x-forms.input id="resource.health_check_path" placeholder="/health" label="Path" required />
|
||||
<x-forms.input id="resource.health_check_scheme" placeholder="http" label="Scheme" required />
|
||||
<x-forms.input id="resource.health_check_host" placeholder="localhost" label="Host" required />
|
||||
<x-forms.input id="resource.health_check_port"
|
||||
helper="If no port is defined, the first exposed port will be used." placeholder="80" label="Port" />
|
||||
<x-forms.input id="resource.health_check_path" placeholder="/health" label="Path" required />
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<x-forms.input id="resource.health_check_return_code" placeholder="200" label="Return Code" required />
|
||||
<x-forms.input id="resource.health_check_response_text" placeholder="OK" label="Response Text" />
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<x-forms.input id="resource.health_check_interval" placeholder="30" label="Interval" required />
|
||||
<x-forms.input id="resource.health_check_timeout" placeholder="30" label="Timeout" required />
|
||||
<x-forms.input id="resource.health_check_retries" placeholder="3" label="Retries" required />
|
||||
<x-forms.input id="resource.health_check_start_period" placeholder="30" label="Start Period" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<x-forms.input id="resource.health_check_return_code" placeholder="200" label="Return Code" required />
|
||||
<x-forms.input id="resource.health_check_response_text" placeholder="OK" label="Response Text" />
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<x-forms.input id="resource.health_check_interval" placeholder="30" label="Interval" required />
|
||||
<x-forms.input id="resource.health_check_timeout" placeholder="30" label="Timeout" required />
|
||||
<x-forms.input id="resource.health_check_retries" placeholder="3" label="Retries" required />
|
||||
<x-forms.input id="resource.health_check_start_period" placeholder="30" label="Start Period" required />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
41
resources/views/livewire/project/shared/logs.blade.php
Normal file
41
resources/views/livewire/project/shared/logs.blade.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<x-layout>
|
||||
@if ($type === 'application')
|
||||
<h1>Logs</h1>
|
||||
<livewire:project.application.heading :application="$resource" />
|
||||
<div class="pt-4">
|
||||
@if (Str::of($status)->startsWith('running'))
|
||||
<livewire:project.shared.get-logs :server="$server" :container="$container" />
|
||||
@else
|
||||
Application is not running.
|
||||
@endif
|
||||
</div>
|
||||
@elseif ($type === 'database')
|
||||
<h1>Logs</h1>
|
||||
<livewire:project.database.heading :database="$resource" />
|
||||
<div class="pt-4">
|
||||
@if (Str::of($status)->startsWith('running'))
|
||||
<livewire:project.shared.get-logs :server="$server" :container="$container" />
|
||||
@else
|
||||
Database is not running.
|
||||
@endif
|
||||
</div>
|
||||
@elseif ($type === 'service')
|
||||
<livewire:project.service.navbar :service="$resource" :parameters="$parameters" :query="$query" />
|
||||
<div class="flex gap-4 pt-6">
|
||||
<div>
|
||||
<a class="{{ request()->routeIs('project.service.show') ? 'text-white' : '' }}"
|
||||
href="{{ route('project.service.show', $parameters) }}">
|
||||
<button><- Back</button>
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex-1 pl-8">
|
||||
@if (serviceStatus($resource) === 'running')
|
||||
<livewire:project.shared.get-logs :server="$server" :container="$container" />
|
||||
@else
|
||||
Service is not running.
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
</x-layout>
|
||||
@@ -1,28 +1,33 @@
|
||||
<div>
|
||||
<div>
|
||||
<div class="flex items-center gap-2">
|
||||
<h2>Storages</h2>
|
||||
@if ($resource->type() !== 'service')
|
||||
<x-helper
|
||||
helper="For Preview Deployments, storage has a <span class='text-helper'>-pr-#PRNumber</span> in their
|
||||
@if ($isHeaderVisible)
|
||||
<div>
|
||||
<div class="flex items-center gap-2">
|
||||
<h2>Storages</h2>
|
||||
@if ($resource->type() !== 'service')
|
||||
<x-helper
|
||||
helper="For Preview Deployments, storage has a <span class='text-helper'>-pr-#PRNumber</span> in their
|
||||
volume
|
||||
name, example: <span class='text-helper'>-pr-1</span>" />
|
||||
<x-forms.button class="btn" onclick="newStorage.showModal()">+ Add</x-forms.button>
|
||||
<livewire:project.shared.storages.add />
|
||||
<x-forms.button class="btn" onclick="newStorage.showModal()">+ Add</x-forms.button>
|
||||
<livewire:project.shared.storages.add :uuid="$resource->uuid" />
|
||||
@endif
|
||||
</div>
|
||||
<div class="pb-4">Persistent storage to preserve data between deployments.</div>
|
||||
@if ($resource->type() === 'service')
|
||||
<span class="text-warning">Please modify storage layout in your <a class="underline"
|
||||
href="{{ Str::of(url()->current())->beforeLast('/') }}">Docker Compose</a> file.</span>
|
||||
<h2 class="pt-4">{{ Str::headline($resource->name) }} </h2>
|
||||
@endif
|
||||
</div>
|
||||
<div>Persistent storage to preserve data between deployments.</div>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2 py-4">
|
||||
@forelse ($resource->persistentStorages as $storage)
|
||||
@endif
|
||||
<div class="flex flex-col gap-4">
|
||||
@foreach ($resource->persistentStorages as $storage)
|
||||
@if ($resource->type() === 'service')
|
||||
<livewire:project.shared.storages.show wire:key="storage-{{ $storage->id }}" :storage="$storage"
|
||||
<livewire:project.shared.storages.show wire:key="storage-{{ $storage->id }}" :storage="$storage" :isFirst="$loop->first"
|
||||
isReadOnly='true' />
|
||||
@else
|
||||
<livewire:project.shared.storages.show wire:key="storage-{{ $storage->id }}" :storage="$storage" />
|
||||
@endif
|
||||
@empty
|
||||
<div class="text-neutral-500">No volume storages found.</div>
|
||||
@endforelse
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,19 +6,28 @@
|
||||
reversible. <br>Please think again.</p>
|
||||
</x-slot:modalBody>
|
||||
</x-modal>
|
||||
@once ($isReadOnly)
|
||||
<span class="text-warning">Please modify storage layout in your <a
|
||||
class="underline" href="{{ Str::of(url()->current())->beforeLast('/') }}#compose">Docker Compose</a> file.</span>
|
||||
@endonce
|
||||
<form wire:submit.prevent='submit' class="flex flex-col gap-2 pt-4 xl:items-end xl:flex-row">
|
||||
|
||||
<form wire:submit.prevent='submit' class="flex flex-col gap-2 xl:items-end xl:flex-row">
|
||||
@if ($isReadOnly)
|
||||
<x-forms.input id="realName" label="Volume Name" required readonly />
|
||||
<x-forms.input id="storage.host_path" label="Source Path" readonly />
|
||||
<x-forms.input id="storage.mount_path" label="Destination Path" required readonly />
|
||||
@if ($isFirst)
|
||||
<x-forms.input id="storage.name" label="Volume Name" required readonly />
|
||||
<x-forms.input id="storage.host_path" label="Source Path (on host)" readonly />
|
||||
<x-forms.input id="storage.mount_path" label="Destination Path (in container)" required readonly />
|
||||
@else
|
||||
<x-forms.input id="storage.name" required readonly />
|
||||
<x-forms.input id="storage.host_path" readonly />
|
||||
<x-forms.input id="storage.mount_path" required readonly />
|
||||
@endif
|
||||
@else
|
||||
<x-forms.input id="storage.name" label="Name" required />
|
||||
<x-forms.input id="storage.host_path" label="Source Path" />
|
||||
<x-forms.input id="storage.mount_path" label="Destination Path" required />
|
||||
@if ($isFirst)
|
||||
<x-forms.input id="storage.name" label="Volume Name" required />
|
||||
<x-forms.input id="storage.host_path" label="Source Path (on host)" />
|
||||
<x-forms.input id="storage.mount_path" label="Destination Path (in container)" required />
|
||||
@else
|
||||
<x-forms.input id="storage.name" required />
|
||||
<x-forms.input id="storage.host_path" />
|
||||
<x-forms.input id="storage.mount_path" required />
|
||||
@endif
|
||||
<div class="flex gap-2">
|
||||
<x-forms.button type="submit">
|
||||
Update
|
||||
|
||||
@@ -17,17 +17,16 @@
|
||||
@forelse ($projects as $project)
|
||||
<div class="gap-2 border border-transparent cursor-pointer box group" x-data
|
||||
x-on:click="goto('{{ $project->uuid }}')">
|
||||
<div class="flex flex-col mx-6">
|
||||
<div class="flex flex-col flex-1 mx-6">
|
||||
<a class=" group-hover:text-white hover:no-underline"
|
||||
href="{{ route('project.show', ['project_uuid' => data_get($project, 'uuid')]) }}">{{ $project->name }}</a>
|
||||
<div class="text-xs group-hover:text-white hover:no-underline"
|
||||
href="{{ route('project.show', ['project_uuid' => data_get($project, 'uuid')]) }}">
|
||||
{{ $project->description }}</div>
|
||||
</div>
|
||||
<div class="flex-1"></div>
|
||||
<a class="mx-4 rounded 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" viewBox="0 0 24 24" stroke-width="1.5"
|
||||
<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" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path
|
||||
|
||||
@@ -10,6 +10,7 @@ use App\Http\Livewire\Boarding\Index as BoardingIndex;
|
||||
use App\Http\Livewire\Project\Service\Index as ServiceIndex;
|
||||
use App\Http\Livewire\Project\Service\Show as ServiceShow;
|
||||
use App\Http\Livewire\Dashboard;
|
||||
use App\Http\Livewire\Project\Shared\Logs;
|
||||
use App\Http\Livewire\Server\All;
|
||||
use App\Http\Livewire\Server\Show;
|
||||
use App\Http\Livewire\Waitlist\Index as WaitlistIndex;
|
||||
@@ -80,14 +81,20 @@ Route::middleware(['auth'])->group(function () {
|
||||
[ApplicationController::class, 'deployment']
|
||||
)->name('project.application.deployment');
|
||||
|
||||
Route::get('/project/{project_uuid}/{environment_name}/application/{application_uuid}/logs', Logs::class)->name('project.application.logs');
|
||||
|
||||
// Databases
|
||||
Route::get('/project/{project_uuid}/{environment_name}/database/{database_uuid}', [DatabaseController::class, 'configuration'])->name('project.database.configuration');
|
||||
Route::get('/project/{project_uuid}/{environment_name}/database/{database_uuid}/backups', [DatabaseController::class, 'backups'])->name('project.database.backups.all');
|
||||
Route::get('/project/{project_uuid}/{environment_name}/database/{database_uuid}/backups/{backup_uuid}', [DatabaseController::class, 'executions'])->name('project.database.backups.executions');
|
||||
Route::get('/project/{project_uuid}/{environment_name}/database/{database_uuid}/logs', Logs::class)->name('project.database.logs');
|
||||
|
||||
|
||||
// Services
|
||||
Route::get('/project/{project_uuid}/{environment_name}/service/{service_uuid}', ServiceIndex::class)->name('project.service');
|
||||
Route::get('/project/{project_uuid}/{environment_name}/service/{service_uuid}/{service_name}', ServiceShow::class)->name('project.service.show');
|
||||
Route::get('/project/{project_uuid}/{environment_name}/service/{service_uuid}/{service_name}/logs', Logs::class)->name('project.service.logs');
|
||||
|
||||
});
|
||||
|
||||
Route::middleware(['auth'])->group(function () {
|
||||
|
||||
@@ -111,13 +111,17 @@ Route::post('/source/github/events', function () {
|
||||
$applications = Application::where('repository_project_id', $id)->whereRelation('source', 'is_public', false);
|
||||
if ($x_github_event === 'push') {
|
||||
$applications = $applications->where('git_branch', $branch)->get();
|
||||
if ($applications->isEmpty()) {
|
||||
return response("Nothing to do. No applications found with branch '$branch'.");
|
||||
}
|
||||
}
|
||||
if ($x_github_event === 'pull_request') {
|
||||
$applications = $applications->where('git_branch', $base_branch)->get();
|
||||
if ($applications->isEmpty()) {
|
||||
return response("Nothing to do. No applications found with branch '$base_branch'.");
|
||||
}
|
||||
}
|
||||
if ($applications->isEmpty()) {
|
||||
return response("Nothing to do. No applications found with branch '$base_branch'.");
|
||||
}
|
||||
|
||||
foreach ($applications as $application) {
|
||||
$isFunctional = $application->destination->server->isFunctional();
|
||||
if (!$isFunctional) {
|
||||
@@ -168,9 +172,9 @@ Route::post('/source/github/events', function () {
|
||||
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
||||
if ($found) {
|
||||
$found->delete();
|
||||
$container_name = generateApplicationContainerName($application);
|
||||
ray('Stopping container: ' . $container_name);
|
||||
remote_process(["docker rm -f $container_name"], $application->destination->server);
|
||||
$container_name = generateApplicationContainerName($application,$pull_request_id);
|
||||
// ray('Stopping container: ' . $container_name);
|
||||
instant_remote_process(["docker rm -f $container_name"], $application->destination->server);
|
||||
return response('Preview Deployment closed.');
|
||||
}
|
||||
return response('Nothing to do. No Preview Deployment found');
|
||||
@@ -323,7 +327,7 @@ Route::post('/payments/stripe/events', function () {
|
||||
}
|
||||
if ($alreadyCancelAtPeriodEnd !== $cancelAtPeriodEnd) {
|
||||
if ($cancelAtPeriodEnd) {
|
||||
send_internal_notification('Subscription cancelled at period end for team: ' . $subscription->team->id);
|
||||
// send_internal_notification('Subscription cancelled at period end for team: ' . $subscription->team->id);
|
||||
} else {
|
||||
send_internal_notification('Subscription resumed for team: ' . $subscription->team->id);
|
||||
}
|
||||
@@ -342,7 +346,7 @@ Route::post('/payments/stripe/events', function () {
|
||||
'stripe_invoice_paid' => false,
|
||||
'stripe_trial_already_ended' => true,
|
||||
]);
|
||||
send_internal_notification('Subscription cancelled: ' . $subscription->team->id);
|
||||
// send_internal_notification('Subscription cancelled: ' . $subscription->team->id);
|
||||
break;
|
||||
case 'customer.subscription.trial_will_end':
|
||||
$customerId = data_get($data, 'customer');
|
||||
|
||||
@@ -20,6 +20,11 @@ function help {
|
||||
compgen -A function | cat -n
|
||||
}
|
||||
|
||||
function setup:dev {
|
||||
docker exec coolify bash -c "composer install"
|
||||
docker exec coolify bash -c "php artisan key:generate"
|
||||
docker exec coolify bash -c "php artisan migrate:fresh --seed"
|
||||
}
|
||||
function sync:v3 {
|
||||
if [ -z "$1" ]; then
|
||||
echo -e "Please provide a version.\n\nExample: run sync:v3 3.12.32"
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"uptime-kuma": {
|
||||
"documentation": "https://github.com/louislam/uptime-kuma",
|
||||
"slogan": "A free and fancy self-hosted monitoring tool.",
|
||||
"compose": "c2VydmljZXM6CiAgdXB0aW1lLWt1bWE6CiAgICBpbWFnZTogbG91aXNsYW0vdXB0aW1lLWt1bWE6MQogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gU0VSVklDRV9GUUROCiAgICB2b2x1bWVzOgogICAgICAtIHVwdGltZS1rdW1hOi9hcHAvZGF0YQ=="
|
||||
"compose": "c2VydmljZXM6CiAgdXB0aW1lLWt1bWE6CiAgICBpbWFnZTogbG91aXNsYW0vdXB0aW1lLWt1bWE6MQogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gU0VSVklDRV9GUUROCiAgICB2b2x1bWVzOgogICAgICAtIHVwdGltZS1rdW1hOi9hcHAvZGF0YQogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6IFsiQ01ELVNIRUxMIiwgImV4dHJhL2hlYWx0aGNoZWNrIl0KICAgICAgaW50ZXJ2YWw6IDJzCiAgICAgIHRpbWVvdXQ6IDEwcwogICAgICByZXRyaWVzOiAxNQ=="
|
||||
},
|
||||
"appsmith": {
|
||||
"documentation": "https://docs.appsmith.com/",
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"version": "3.12.36"
|
||||
},
|
||||
"v4": {
|
||||
"version": "4.0.0-beta.51"
|
||||
"version": "4.0.0-beta.62"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,9 @@ import vue from "@vitejs/plugin-vue";
|
||||
|
||||
export default defineConfig({
|
||||
server: {
|
||||
watch: {
|
||||
ignored: ['**/_data/**'],
|
||||
},
|
||||
host: "0.0.0.0",
|
||||
hmr: process.env.GITPOD_WORKSPACE_URL
|
||||
? {
|
||||
|
||||
Reference in New Issue
Block a user